Skip to content

Commit c714ba1

Browse files
committed
In JavaScriptEngineSwitcher.NiL the JS run-time exception now contains a script call stack
1 parent 8b43a1c commit c714ba1

File tree

5 files changed

+161
-10
lines changed

5 files changed

+161
-10
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Text.RegularExpressions;
5+
6+
using JavaScriptEngineSwitcher.Core.Extensions;
7+
using JavaScriptEngineSwitcher.Core.Helpers;
8+
9+
using CoreStrings = JavaScriptEngineSwitcher.Core.Resources.Strings;
10+
11+
namespace JavaScriptEngineSwitcher.NiL.Helpers
12+
{
13+
/// <summary>
14+
/// JS error helpers
15+
/// </summary>
16+
internal static class NiLJsErrorHelpers
17+
{
18+
#region Error location
19+
20+
private const string AtLinePrefix = " at ";
21+
private const string DotNetStackTraceLinePrefix = AtLinePrefix + "NiL.JS.";
22+
23+
private const string OriginalGlobalCode = "anonymous";
24+
private const string OriginalAnonymousFunctionName = "<anonymous method>";
25+
private const string WrapperGlobalCode = "Global code";
26+
private const string WrapperAnonymousFunctionName = "Anonymous function";
27+
28+
/// <summary>
29+
/// Regular expression for working with line of the script error location
30+
/// </summary>
31+
private static readonly Regex _errorLocationLineRegex =
32+
new Regex(@"^" + AtLinePrefix +
33+
@"(?<functionName>" +
34+
@"[\w][\w ]*" +
35+
@"|" +
36+
CommonRegExps.JsFullNamePattern +
37+
@"|" +
38+
Regex.Escape(OriginalAnonymousFunctionName) +
39+
@")" +
40+
@"(?::line (?<lineNumber>\d+):(?<columnNumber>\d+))?$");
41+
42+
43+
/// <summary>
44+
/// Parses a string representation of the script error location to produce an array of
45+
/// <see cref="ErrorLocationItem"/> instances
46+
/// </summary>
47+
/// <param name="errorLocation">String representation of the script error location</param>
48+
/// <returns>An array of <see cref="ErrorLocationItem"/> instances</returns>
49+
public static ErrorLocationItem[] ParseErrorLocation(string errorLocation)
50+
{
51+
if (string.IsNullOrWhiteSpace(errorLocation))
52+
{
53+
return new ErrorLocationItem[0];
54+
}
55+
56+
var errorLocationItems = new List<ErrorLocationItem>();
57+
string[] lines = errorLocation.SplitToLines();
58+
int lineCount = lines.Length;
59+
60+
for (int lineIndex = 0; lineIndex < lineCount; lineIndex++)
61+
{
62+
string line = lines[lineIndex];
63+
64+
if (line.Length == 0)
65+
{
66+
continue;
67+
}
68+
69+
// Completing parsing when a .NET stack trace is found
70+
if (line.StartsWith(DotNetStackTraceLinePrefix, StringComparison.Ordinal))
71+
{
72+
break;
73+
}
74+
75+
Match lineMatch = _errorLocationLineRegex.Match(line);
76+
if (lineMatch.Success)
77+
{
78+
GroupCollection lineGroups = lineMatch.Groups;
79+
Group lineNumberGroup = lineGroups["lineNumber"];
80+
Group columnNumberGroup = lineGroups["columnNumber"];
81+
82+
var errorLocationItem = new ErrorLocationItem
83+
{
84+
FunctionName = lineGroups["functionName"].Value,
85+
LineNumber = lineNumberGroup.Success ? int.Parse(lineNumberGroup.Value) : 0,
86+
ColumnNumber = columnNumberGroup.Success ? int.Parse(columnNumberGroup.Value) : 0,
87+
};
88+
errorLocationItems.Add(errorLocationItem);
89+
}
90+
else
91+
{
92+
Debug.WriteLine(string.Format(CoreStrings.Runtime_InvalidErrorLocationLineFormat, line));
93+
return new ErrorLocationItem[0];
94+
}
95+
}
96+
97+
return errorLocationItems.ToArray();
98+
}
99+
100+
/// <summary>
101+
/// Fixes a error location items
102+
/// </summary>
103+
/// <param name="errorLocationItems">An array of <see cref="ErrorLocationItem"/> instances</param>
104+
public static void FixErrorLocationItems(ErrorLocationItem[] errorLocationItems)
105+
{
106+
foreach (ErrorLocationItem errorLocationItem in errorLocationItems)
107+
{
108+
string functionName = errorLocationItem.FunctionName;
109+
if (functionName.Length == 0 || functionName == OriginalGlobalCode)
110+
{
111+
errorLocationItem.FunctionName = WrapperGlobalCode;
112+
}
113+
else if (functionName == OriginalAnonymousFunctionName)
114+
{
115+
errorLocationItem.FunctionName = WrapperAnonymousFunctionName;
116+
}
117+
}
118+
}
119+
120+
#endregion
121+
}
122+
}

src/JavaScriptEngineSwitcher.NiL/JavaScriptEngineSwitcher.NiL.csproj

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@
1919
<Description>JavaScriptEngineSwitcher.NiL contains adapter `NiLJsEngine` (wrapper for the NiL JavaScript Engine (https://github.com/nilproject/NiL.JS) version 2.5.1591).</Description>
2020
<PackageTags>$(PackageCommonTags);NiL</PackageTags>
2121
<PackageIconFullPath>../../Icons/JavaScriptEngineSwitcher_NiL_Logo128x128.png</PackageIconFullPath>
22-
<PackageReleaseNotes>1. NiL.JS was updated to version 2.5.1591;
23-
2. No longer supports a .NET Standard.</PackageReleaseNotes>
22+
<PackageReleaseNotes>JS run-time exception now contains a script call stack.</PackageReleaseNotes>
2423
</PropertyGroup>
2524

2625
<ItemGroup>

src/JavaScriptEngineSwitcher.NiL/NiLJsEngine.cs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
using WrapperRuntimeException = JavaScriptEngineSwitcher.Core.JsRuntimeException;
2525
using WrapperScriptException = JavaScriptEngineSwitcher.Core.JsScriptException;
2626

27+
using JavaScriptEngineSwitcher.NiL.Helpers;
28+
2729
namespace JavaScriptEngineSwitcher.NiL
2830
{
2931
/// <summary>
@@ -206,12 +208,35 @@ private static WrapperException WrapJsException(OriginalException originalExcept
206208
lineNumber = codeCoordinates.Line;
207209
columnNumber = codeCoordinates.Column;
208210
}
211+
209212
sourceFragment = TextHelpers.GetTextFragment(sourceCode, lineNumber, columnNumber);
210-
message = JsErrorHelpers.GenerateScriptErrorMessage(type, description, string.Empty,
211-
lineNumber, columnNumber, sourceFragment);
213+
string callStack = string.Empty;
214+
ErrorLocationItem[] callStackItems = NiLJsErrorHelpers.ParseErrorLocation(
215+
originalException.StackTrace);
216+
if (callStackItems.Length > 0)
217+
{
218+
NiLJsErrorHelpers.FixErrorLocationItems(callStackItems);
219+
220+
ErrorLocationItem firstCallStackItem = callStackItems[0];
221+
firstCallStackItem.SourceFragment = sourceFragment;
212222

213-
wrapperScriptException = new WrapperRuntimeException(message, EngineName, EngineVersion,
223+
callStack = JsErrorHelpers.StringifyErrorLocationItems(callStackItems, true);
224+
string callStackWithSourceFragment = JsErrorHelpers.StringifyErrorLocationItems(
225+
callStackItems);
226+
message = JsErrorHelpers.GenerateScriptErrorMessage(type, description,
227+
callStackWithSourceFragment);
228+
}
229+
else
230+
{
231+
message = JsErrorHelpers.GenerateScriptErrorMessage(type, description, string.Empty,
232+
lineNumber, columnNumber, sourceFragment);
233+
}
234+
235+
var wrapperRuntimeException = new WrapperRuntimeException(message, EngineName, EngineVersion,
214236
originalException);
237+
wrapperRuntimeException.CallStack = callStack;
238+
239+
wrapperScriptException = wrapperRuntimeException;
215240
}
216241
wrapperScriptException.Type = type;
217242
wrapperScriptException.LineNumber = lineNumber;

src/JavaScriptEngineSwitcher.NiL/readme.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@
1717
=============
1818
RELEASE NOTES
1919
=============
20-
1. NiL.JS was updated to version 2.5.1591;
21-
2. No longer supports a .NET Standard.
20+
JS run-time exception now contains a script call stack.
2221

2322
=============
2423
DOCUMENTATION

test/JavaScriptEngineSwitcher.Tests/NiL/CommonTests.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public void MappingRuntimeErrorDuringEvaluationOfExpressionIsCorrect()
8888
Assert.Equal(5, exception.LineNumber);
8989
Assert.Equal(15, exception.ColumnNumber);
9090
Assert.Equal("$variable1 + -variable2 - variable3;", exception.SourceFragment);
91-
Assert.Empty(exception.CallStack);
91+
Assert.Equal(" at Global code (5:15)", exception.CallStack);
9292
}
9393

9494
[Fact]
@@ -176,7 +176,11 @@ public void MappingRuntimeErrorDuringExecutionOfCodeIsCorrect()
176176
" throw new Error(\"The value must be greater than or equal to zero.\");",
177177
exception.SourceFragment
178178
);
179-
Assert.Empty(exception.CallStack);
179+
Assert.Equal(
180+
" at factorial (3:3)" + Environment.NewLine +
181+
" at Global code (10:1)",
182+
exception.CallStack
183+
);
180184
}
181185

182186
#endregion
@@ -231,7 +235,9 @@ public void GenerationOfRuntimeErrorMessageIsCorrect()
231235
foo(a, b);
232236
})(foo);";
233237
string targetOutput = "ReferenceError: Variable \"bar\" is not defined" + Environment.NewLine +
234-
" at 4:3 -> bar();"
238+
" at foo (4:3) -> bar();" + Environment.NewLine +
239+
" at Anonymous function (12:2)" + Environment.NewLine +
240+
" at Global code (8:1)"
235241
;
236242

237243
JsRuntimeException exception = null;

0 commit comments

Comments
 (0)