Skip to content

Commit 098b15d

Browse files
committed
Add specific exception and enable nullable reference types
1 parent 6ae1301 commit 098b15d

File tree

10 files changed

+173
-85
lines changed

10 files changed

+173
-85
lines changed

SourceMaps.StackTraces/SourceMaps.StackTraces.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
<PropertyGroup>
44
<TargetFramework>netstandard2.0</TargetFramework>
5+
<LangVersion>8.0</LangVersion>
6+
<Nullable>enable</Nullable>
57

68
<PackageId>SourceMaps.StackTraces</PackageId>
79
<Version>0.1.0</Version>

SourceMaps.StackTraces/StackFrame.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
using System;
2+
13
namespace SourceMaps.StackTraces
24
{
35
public class StackFrame
46
{
5-
public string File { get; set; }
6-
public string Method { get; set; }
7-
public string[] Arguments { get; set; }
7+
public string? File { get; set; }
8+
public string? Method { get; set; }
9+
public string[] Arguments { get; set; } = Array.Empty<string>();
810
public int? LineNumber { get; set; }
911
public int? ColumnNumber { get; set; }
1012

SourceMaps.StackTraces/StackTraceParser.cs

Lines changed: 39 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,24 @@ namespace SourceMaps.StackTraces
55
{
66
public static class StackTraceParser
77
{
8-
public static string ReTrace(SourceMap sourceMap, string stacktrace, string sourceRoot = null)
8+
public static string ReTrace(SourceMap sourceMap, string stacktrace, string? sourceRoot = null)
99
{
1010
var collection = new SourceMapCollection();
1111
collection.Register(sourceMap);
1212
return ReTrace(collection, stacktrace, sourceRoot);
1313
}
1414

15-
public static string ReTrace(SourceMapCollection sourceMaps, string stacktrace, string sourceRoot = null)
15+
public static string ReTrace(SourceMapCollection sourceMaps, string stacktrace, string? sourceRoot = null)
1616
{
1717
var trace = Parse(stacktrace);
1818

1919
foreach (var frame in trace.Frames)
2020
{
2121
if (!string.IsNullOrEmpty(sourceRoot))
22-
frame.File = frame.File.Replace(sourceRoot, "");
22+
frame.File = frame.File?.Replace(sourceRoot, "");
2323

2424
var sourceMap = sourceMaps.GetSourceMapFor(frame.File);
25-
if (sourceMap == null && frame.File.IndexOf('?') >= 0)
25+
if (sourceMap == null && frame.File?.IndexOf('?') >= 0) // Allow querystring versioning missing from the registered filename
2626
sourceMap = sourceMaps.GetSourceMapFor(frame.File.Substring(0, frame.File.IndexOf('?')));
2727

2828
if (frame.LineNumber == null || frame.ColumnNumber == null)
@@ -32,10 +32,10 @@ public static string ReTrace(SourceMapCollection sourceMaps, string stacktrace,
3232
if (originalPosition == null)
3333
continue;
3434

35-
frame.File = originalPosition?.OriginalFileName ?? frame.File;
36-
frame.Method = originalPosition?.OriginalName ?? frame.Method;
37-
frame.LineNumber = (originalPosition?.OriginalLineNumber + 1) ?? frame.LineNumber;
38-
frame.ColumnNumber = (originalPosition?.OriginalColumnNumber + 1) ?? frame.ColumnNumber;
35+
frame.File = originalPosition?.OriginalFileName;
36+
frame.Method = originalPosition?.OriginalName;
37+
frame.LineNumber = originalPosition?.OriginalLineNumber + 1;
38+
frame.ColumnNumber = originalPosition?.OriginalColumnNumber + 1;
3939
}
4040

4141
return trace.ToString();
@@ -48,7 +48,7 @@ public static StackTrace Parse(string stacktrace)
4848
var result = new StackTrace();
4949
foreach (var line in lines)
5050
{
51-
StackFrame frame;
51+
StackFrame? frame;
5252
var success =
5353
TryParseChrome(line, out frame) ||
5454
TryParseWinJs(line, out frame) ||
@@ -57,15 +57,15 @@ public static StackTrace Parse(string stacktrace)
5757
TryParseJsc(line, out frame);
5858

5959
if (success)
60-
result.Append(frame);
60+
result.Append(frame!);
6161
}
6262

6363
return result;
6464
}
6565

6666
private static readonly Regex ChromeRe = new Regex(@"^\s*at (.*?) ?\(((?:file|https?|blob|chrome-extension|native|eval|webpack|<anonymous>|\/).*?)(?::(\d+))?(?::(\d+))?\)?\s*$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
6767
private static readonly Regex ChromeEvalRe = new Regex(@"\((\S*)(?::(\d+))(?::(\d+))\)", RegexOptions.Compiled);
68-
internal static bool TryParseChrome(string line, out StackFrame frame)
68+
internal static bool TryParseChrome(string line, out StackFrame? frame)
6969
{
7070
frame = null;
7171

@@ -76,12 +76,14 @@ internal static bool TryParseChrome(string line, out StackFrame frame)
7676
var isNative = match.Groups[2].Value?.IndexOf("native", StringComparison.Ordinal) == 0;
7777
var isEval = match.Groups[2].Value?.IndexOf("eval", StringComparison.Ordinal) == 0;
7878

79-
frame = new StackFrame();
80-
frame.File = !isNative ? match.Groups[2].Value : null;
81-
frame.Method = !string.IsNullOrEmpty(match.Groups[1]?.Value) ? match.Groups[1].Value : null;
82-
frame.Arguments = isNative ? new[] { match.Groups[2].Value } : Array.Empty<string>();
83-
frame.LineNumber = !string.IsNullOrEmpty(match.Groups[3].Value) ? int.Parse(match.Groups[3].Value) : (int?)null;
84-
frame.ColumnNumber = !string.IsNullOrEmpty(match.Groups[4].Value) ? int.Parse(match.Groups[4].Value) : (int?) null;
79+
frame = new StackFrame
80+
{
81+
File = !isNative ? match.Groups[2].Value : null,
82+
Method = !string.IsNullOrEmpty(match.Groups[1]?.Value) ? match.Groups[1].Value : null,
83+
Arguments = isNative ? new[] { match.Groups[2].Value } : Array.Empty<string>(),
84+
LineNumber = !string.IsNullOrEmpty(match.Groups[3].Value) ? int.Parse(match.Groups[3].Value) : (int?) null,
85+
ColumnNumber = !string.IsNullOrEmpty(match.Groups[4].Value) ? int.Parse(match.Groups[4].Value) : (int?) null
86+
};
8587

8688
var submatch = ChromeEvalRe.Match(match.Groups[2].Value);
8789
if (isEval && submatch.Success)
@@ -95,7 +97,7 @@ internal static bool TryParseChrome(string line, out StackFrame frame)
9597
}
9698

9799
private static readonly Regex WinJsRe = new Regex(@"^\s*at (?:((?:\[object object\])?.+) )?\(?((?:file|ms-appx|https?|webpack|blob):.*?):(\d+)(?::(\d+))?\)?\s*$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
98-
internal static bool TryParseWinJs(string line, out StackFrame frame)
100+
internal static bool TryParseWinJs(string line, out StackFrame? frame)
99101
{
100102
frame = null;
101103

@@ -117,7 +119,7 @@ internal static bool TryParseWinJs(string line, out StackFrame frame)
117119

118120
private static readonly Regex GeckoRe = new Regex(@"^\s*(.*?)(?:\((.*?)\))?(?:^|@)((?:file|https?|blob|chrome|webpack|resource|\[native|/).*?|[^@]*bundle)(?::(\d+))?(?::(\d+))?\s*$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
119121
private static readonly Regex GeckoEvalRe = new Regex(@"(?:^\s*(?:.*?)(?:\((?:.*?)\))?@|^)(\S+) line (\d+)(?: > eval line \d+)* > eval", RegexOptions.IgnoreCase | RegexOptions.Compiled);
120-
internal static bool TryParseGecko(string line, out StackFrame frame)
122+
internal static bool TryParseGecko(string line, out StackFrame? frame)
121123
{
122124
frame = null;
123125

@@ -148,40 +150,44 @@ internal static bool TryParseGecko(string line, out StackFrame frame)
148150
}
149151

150152
private static readonly Regex NodeRe = new Regex(@"^\s*at (?:((?:\[object object\])?[^\\/]+(?: \[as \S+\])?) )?\(?(.*?):(\d+)(?::(\d+))?\)?\s*$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
151-
internal static bool TryParseNode(string line, out StackFrame frame)
153+
internal static bool TryParseNode(string line, out StackFrame? frame)
152154
{
153155
frame = null;
154156

155157
var match = NodeRe.Match(line);
156158
if (!match.Success)
157159
return false;
158160

159-
frame = new StackFrame();
160-
frame.File = match.Groups[2].Value;
161-
frame.Method = !string.IsNullOrEmpty(match.Groups[1]?.Value) ? match.Groups[1].Value : null;
162-
frame.Arguments = Array.Empty<string>();
163-
frame.LineNumber = int.Parse(match.Groups[3].Value);
164-
frame.ColumnNumber = !string.IsNullOrEmpty(match.Groups[4].Value) ? int.Parse(match.Groups[4].Value) : (int?)null;
161+
frame = new StackFrame
162+
{
163+
File = match.Groups[2].Value,
164+
Method = !string.IsNullOrEmpty(match.Groups[1]?.Value) ? match.Groups[1].Value : null,
165+
Arguments = Array.Empty<string>(),
166+
LineNumber = int.Parse(match.Groups[3].Value),
167+
ColumnNumber = !string.IsNullOrEmpty(match.Groups[4].Value) ? int.Parse(match.Groups[4].Value) : (int?) null
168+
};
165169

166170
return true;
167171
}
168172

169173
private static readonly Regex JscRe = new Regex(@"^\s*(?:([^@]*)(?:\((.*?)\))?@)?(\S.*?):(\d+)(?::(\d+))?\s*$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
170174

171-
internal static bool TryParseJsc(string line, out StackFrame frame)
175+
internal static bool TryParseJsc(string line, out StackFrame? frame)
172176
{
173177
frame = null;
174178

175179
var match = JscRe.Match(line);
176180
if (!match.Success)
177181
return false;
178182

179-
frame = new StackFrame();
180-
frame.File = match.Groups[3].Value;
181-
frame.Method = !string.IsNullOrEmpty(match.Groups[1].Value) ? match.Groups[1].Value : null;
182-
frame.Arguments = Array.Empty<string>();
183-
frame.LineNumber = int.Parse(match.Groups[4].Value);
184-
frame.ColumnNumber = !string.IsNullOrEmpty(match.Groups[5].Value) ? int.Parse(match.Groups[5].Value) : (int?)null;
183+
frame = new StackFrame
184+
{
185+
File = match.Groups[3].Value,
186+
Method = !string.IsNullOrEmpty(match.Groups[1].Value) ? match.Groups[1].Value : null,
187+
Arguments = Array.Empty<string>(),
188+
LineNumber = int.Parse(match.Groups[4].Value),
189+
ColumnNumber = !string.IsNullOrEmpty(match.Groups[5].Value) ? int.Parse(match.Groups[5].Value) : (int?) null
190+
};
185191

186192
return true;
187193
}

SourceMaps.Tests/SourceMapParserTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public class SourceMapParserTests
1111
[Fact]
1212
public void ApplyMappingSegment_CorrectlyUpdatesState()
1313
{
14-
var state = new MappingParserState(1, 2, 3, 4, 5, 6);
14+
var state = new MappingParserState { GeneratedLineNumber = 1, GeneratedColumnNumber = 2, SourcesListIndex = 3, OriginalLineNumber = 4, OriginalColumnNumber = 5, NamesListIndex = 6 };
1515

1616
var segmentFields = new List<int> { 4 }; // Only column number
1717
SourceMapParser.ApplyMappingSegment(segmentFields, ref state);
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System;
2+
3+
namespace SourceMaps
4+
{
5+
public class InvalidSourceMapException : Exception
6+
{
7+
public InvalidSourceMapException()
8+
{
9+
}
10+
11+
public InvalidSourceMapException(string message)
12+
: base(message)
13+
{
14+
}
15+
16+
public InvalidSourceMapException(string message, Exception inner)
17+
: base(message, inner)
18+
{
19+
}
20+
}
21+
}

SourceMaps/SourceMap.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,25 @@ public sealed class SourceMap
99
public int Version { get; set; }
1010

1111
[JsonPropertyName("file")]
12-
public string File { get; set; }
12+
public string? File { get; set; }
1313

1414
[JsonPropertyName("sourceRoot")]
15-
public string SourceRoot { get; set; }
15+
public string? SourceRoot { get; set; }
1616

1717
[JsonPropertyName("sources")]
18-
public List<string> Sources { get; set; }
18+
public List<string> Sources { get; set; } = new List<string>();
1919

2020
[JsonPropertyName("sourcesContent")]
21-
public List<string> SourcesContent { get; set; }
21+
public List<string>? SourcesContent { get; set; }
2222

2323
[JsonPropertyName("names")]
24-
public List<string> Names { get; set; }
24+
public List<string> Names { get; set; } = new List<string>();
2525

2626
[JsonPropertyName("mappings")]
27-
public string MappingsString { get; set; }
27+
public string MappingsString { get; set; } = "";
2828

2929
[JsonIgnore]
30-
public List<SourceMapMappingEntry> Mappings { get; set; }
30+
public List<SourceMapMappingEntry>? Mappings { get; set; }
3131

3232
/// <summary>
3333
/// Returns the original source, line and column information for the generated

SourceMaps/SourceMapCollection.cs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,69 @@
1+
using System;
12
using System.Collections.Generic;
23

34
namespace SourceMaps
45
{
6+
/// <summary>
7+
/// Collection of multiple sourcemaps.
8+
/// </summary>
59
public class SourceMapCollection
610
{
711
private Dictionary<string, SourceMap> _sourceMaps = new Dictionary<string, SourceMap>();
812

13+
/// <summary>
14+
/// Register the given sourcemap in the collection using the SourceMap's File as name.
15+
/// </summary>
16+
/// <param name="sourceMap">A <see cref="SourceMap" /></param>
917
public void Register(SourceMap sourceMap)
1018
{
11-
_sourceMaps.Add(sourceMap.File, sourceMap);
19+
if (string.IsNullOrEmpty(sourceMap.File))
20+
throw new ArgumentException("sourceMap has no associated filename.");
21+
22+
_sourceMaps.Add(sourceMap.File!, sourceMap);
1223
}
1324

25+
/// <summary>
26+
/// Register the given sourcemap in the collection using the given name.
27+
/// </summary>
28+
/// <param name="name">The name to register the SourceMap as</param>
29+
/// <param name="sourceMap">A <see cref="SourceMap" /></param>
1430
public void Register(string name, SourceMap sourceMap)
1531
{
1632
_sourceMaps.Add(name, sourceMap);
1733
}
1834

35+
/// <summary>
36+
/// Parse and register the given sourcemap in the collection using the SourceMap's File as name.
37+
/// </summary>
38+
/// <param name="sourceMapContent">A string containing an encoded SourceMap.</param>
1939
public void ParseAndRegister(string sourceMapContent)
2040
{
2141
var sourceMap = SourceMapParser.Parse(sourceMapContent);
22-
_sourceMaps.Add(sourceMap.File, sourceMap);
42+
43+
if (string.IsNullOrEmpty(sourceMap.File))
44+
throw new ArgumentException("sourceMap has no associated filename.");
45+
46+
_sourceMaps.Add(sourceMap.File!, sourceMap);
2347
}
2448

49+
/// <summary>
50+
/// Parse and register the given sourcemap in the collection using the given name.
51+
/// </summary>
52+
/// <param name="name">The name to register the SourceMap as</param>
53+
/// <param name="sourceMapContent">A string containing an encoded SourceMap.</param>
2554
public void ParseAndRegister(string name, string sourceMapContent)
2655
{
2756
_sourceMaps.Add(name, SourceMapParser.Parse(sourceMapContent));
2857
}
2958

30-
public SourceMap GetSourceMapFor(string filename)
59+
/// <summary>
60+
/// Get the <see cref="SourceMap" /> that corresponds to the given filename.
61+
/// </summary>
62+
/// <param name="filename">The filename to get the SourceMap for.</param>
63+
/// <returns>The SourceMap, or null if none were found.</returns>
64+
public SourceMap? GetSourceMapFor(string? filename)
3165
{
32-
if (_sourceMaps.TryGetValue(filename, out var sourceMap))
66+
if (filename != null && _sourceMaps.TryGetValue(filename, out var sourceMap))
3367
return sourceMap;
3468

3569
return null;

SourceMaps/SourceMapMappingEntry.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ namespace SourceMaps
44
{
55
public readonly struct SourceMapMappingEntry : IEquatable<SourceMapMappingEntry>
66
{
7-
public SourceMapMappingEntry(int generatedLineNumber, int generatedColumnNumber, int? originalLineNumber, int? originalColumnNumber, string originalName, string originalFileName)
7+
public SourceMapMappingEntry(int generatedLineNumber, int generatedColumnNumber, int? originalLineNumber, int? originalColumnNumber, string? originalName, string? originalFileName)
88
{
99
this.GeneratedLineNumber = generatedLineNumber;
1010
this.GeneratedColumnNumber = generatedColumnNumber;
@@ -37,12 +37,12 @@ public SourceMapMappingEntry(int generatedLineNumber, int generatedColumnNumber,
3737
/// <summary>
3838
/// Original element name
3939
/// </summary>
40-
public string OriginalName { get; }
40+
public string? OriginalName { get; }
4141

4242
/// <summary>
4343
/// Original file name
4444
/// </summary>
45-
public string OriginalFileName { get; }
45+
public string? OriginalFileName { get; }
4646

4747
public static bool operator ==(SourceMapMappingEntry left, SourceMapMappingEntry right)
4848
=> Equals(left, right);

0 commit comments

Comments
 (0)