Better method name parsing and mapping (#76)

* Fix method name association, add Webpack + TypeScript example test
This commit is contained in:
Ian Craig 2019-08-15 13:16:03 -07:00 коммит произвёл GitHub
Родитель 0394d00ff7
Коммит 3fb57e94f5
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
11 изменённых файлов: 145 добавлений и 21 удалений

2
.gitignore поставляемый
Просмотреть файл

@ -267,3 +267,5 @@ __pycache__/
# Minified JavaScript
*.min.js
*.min.js.map
!**/webpackapp/**

Просмотреть файл

@ -49,7 +49,7 @@ namespace SourcemapToolkit.CallstackDeminifier
BinaryOperator parentBinaryOperator = node.Parent as BinaryOperator;
if (parentBinaryOperator != null)
{
result.Add(ExtractBindingsFromBinaryOperator(parentBinaryOperator));
result.AddRange(ExtractBindingsFromBinaryOperator(parentBinaryOperator));
return result;
}
@ -61,7 +61,7 @@ namespace SourcemapToolkit.CallstackDeminifier
ObjectLiteral objectLiteralParent = parentObjectLiteralProperty.Parent?.Parent as ObjectLiteral;
if (objectLiteralParent != null && objectLiteralParent.Parent is BinaryOperator)
{
result.Add(ExtractBindingsFromBinaryOperator((BinaryOperator)objectLiteralParent.Parent));
result.AddRange(ExtractBindingsFromBinaryOperator((BinaryOperator)objectLiteralParent.Parent));
}
result.Add(
@ -110,15 +110,47 @@ namespace SourcemapToolkit.CallstackDeminifier
return null;
}
private BindingInformation ExtractBindingsFromBinaryOperator(BinaryOperator parentBinaryOperator)
private IEnumerable<BindingInformation> ExtractBindingsFromBinaryOperator(BinaryOperator parentBinaryOperator)
{
// If the operand has a dot in the name it's a Member. e.g. a.b, a.prototype, a.prototype.b
Member member = parentBinaryOperator.Operand1 as Member;
if (member != null)
{
// Split members into two parts, on the last dot, so a.prototype.b becomes [a.prototype, b]
// This separates the generated location for the property/method name (b), and allows deminification
// to resolve both the class and method names.
yield return ExtractBindingsFromNode(member.Root);
// a.prototype splits into [a, prototype], but we throw away the prototype as this doesn't map to anything useful in the original source
if (member.Name != "prototype")
{
int offset = member.NameContext.Code.StartsWith(".") ? 1 : 0;
yield return new BindingInformation
{
Name = member.Name,
SourcePosition = new SourcePosition
{
ZeroBasedLineNumber = member.NameContext.StartLineNumber - 1,
ZeroBasedColumnNumber = member.NameContext.StartColumn + offset
}
};
}
}
else
{
yield return ExtractBindingsFromNode(parentBinaryOperator.Operand1);
}
}
private BindingInformation ExtractBindingsFromNode(AstNode node)
{
return new BindingInformation
{
Name = parentBinaryOperator.Operand1.Context.Code,
Name = node.Context.Code,
SourcePosition = new SourcePosition
{
ZeroBasedLineNumber = parentBinaryOperator.Operand1.Context.StartLineNumber - 1,
ZeroBasedColumnNumber = parentBinaryOperator.Operand1.Context.StartColumn
ZeroBasedLineNumber = node.Context.StartLineNumber - 1,
ZeroBasedColumnNumber = node.Context.StartColumn
}
};
}

Просмотреть файл

@ -78,12 +78,11 @@ namespace SourcemapToolkit.CallstackDeminifier
if (wrappingFunction.Bindings != null && wrappingFunction.Bindings.Count > 0)
{
MappingEntry objectProtoypeMappingEntry = null;
if (wrappingFunction.Bindings.Count == 2)
{
MappingEntry objectProtoypeMappingEntry =
objectProtoypeMappingEntry =
sourceMap.GetMappingEntryForGeneratedSourcePosition(wrappingFunction.Bindings[0].SourcePosition);
methodName = objectProtoypeMappingEntry?.OriginalName;
}
MappingEntry mappingEntry =
@ -91,9 +90,20 @@ namespace SourcemapToolkit.CallstackDeminifier
if (mappingEntry?.OriginalName != null)
{
if (methodName != null)
if (objectProtoypeMappingEntry?.OriginalName != null)
{
methodName = methodName + "." + mappingEntry.OriginalName;
string objectName = objectProtoypeMappingEntry.OriginalName;
if (objectProtoypeMappingEntry.OriginalSourcePosition?.ZeroBasedColumnNumber == mappingEntry.OriginalSourcePosition?.ZeroBasedColumnNumber
&& objectProtoypeMappingEntry.OriginalSourcePosition?.ZeroBasedLineNumber == mappingEntry.OriginalSourcePosition?.ZeroBasedLineNumber
&& objectName.EndsWith($".{mappingEntry.OriginalName}"))
{
// The object name already contains the method name, so do not append it
methodName = objectName;
}
else
{
methodName = $"{objectName}.{mappingEntry.OriginalName}";
}
}
else
{

Просмотреть файл

@ -190,7 +190,8 @@ namespace SourcemapToolkit.CallstackDeminifier.UnitTests
// Assert
Assert.Equal(2, functionMap.Count);
Assert.Equal("foo.bar", functionMap[0].Bindings[0].Name);
Assert.Equal("foo", functionMap[0].Bindings[0].Name);
Assert.Equal("bar", functionMap[0].Bindings[1].Name);
Assert.Equal(0, functionMap[0].Bindings[0].SourcePosition.ZeroBasedLineNumber);
Assert.Equal(23, functionMap[0].Bindings[0].SourcePosition.ZeroBasedColumnNumber);
Assert.Equal(0, functionMap[0].StartSourcePosition.ZeroBasedLineNumber);
@ -220,7 +221,8 @@ namespace SourcemapToolkit.CallstackDeminifier.UnitTests
// Assert
Assert.Equal(2, functionMap.Count);
Assert.Equal("foo.prototype.bar", functionMap[0].Bindings[0].Name);
Assert.Equal("foo.prototype", functionMap[0].Bindings[0].Name);
Assert.Equal("bar", functionMap[0].Bindings[1].Name);
Assert.Equal(0, functionMap[0].Bindings[0].SourcePosition.ZeroBasedLineNumber);
Assert.Equal(23, functionMap[0].Bindings[0].SourcePosition.ZeroBasedColumnNumber);
Assert.Equal(0, functionMap[0].StartSourcePosition.ZeroBasedLineNumber);
@ -250,7 +252,7 @@ namespace SourcemapToolkit.CallstackDeminifier.UnitTests
// Assert
Assert.Equal(2, functionMap.Count);
Assert.Equal("foo.prototype", functionMap[0].Bindings[0].Name);
Assert.Equal("foo", functionMap[0].Bindings[0].Name);
Assert.Equal(0, functionMap[0].Bindings[0].SourcePosition.ZeroBasedLineNumber);
Assert.Equal(23, functionMap[0].Bindings[0].SourcePosition.ZeroBasedColumnNumber);
Assert.Equal("bar", functionMap[0].Bindings[1].Name);
@ -305,7 +307,8 @@ namespace SourcemapToolkit.CallstackDeminifier.UnitTests
// Assert
Assert.Equal(2, functionMap.Count);
Assert.Equal("foo.bar", functionMap[0].Bindings[0].Name);
Assert.Equal("foo", functionMap[0].Bindings[0].Name);
Assert.Equal("bar", functionMap[0].Bindings[1].Name);
Assert.Equal(0, functionMap[0].Bindings[0].SourcePosition.ZeroBasedLineNumber);
Assert.Equal(23, functionMap[0].Bindings[0].SourcePosition.ZeroBasedColumnNumber);
Assert.Equal(0, functionMap[0].StartSourcePosition.ZeroBasedLineNumber);
@ -335,7 +338,8 @@ namespace SourcemapToolkit.CallstackDeminifier.UnitTests
// Assert
Assert.Equal(2, functionMap.Count);
Assert.Equal("foo.prototype.bar", functionMap[0].Bindings[0].Name);
Assert.Equal("foo.prototype", functionMap[0].Bindings[0].Name);
Assert.Equal("bar", functionMap[0].Bindings[1].Name);
Assert.Equal(0, functionMap[0].Bindings[0].SourcePosition.ZeroBasedLineNumber);
Assert.Equal(23, functionMap[0].Bindings[0].SourcePosition.ZeroBasedColumnNumber);
Assert.Equal(0, functionMap[0].StartSourcePosition.ZeroBasedLineNumber);
@ -365,7 +369,7 @@ namespace SourcemapToolkit.CallstackDeminifier.UnitTests
// Assert
Assert.Equal(2, functionMap.Count);
Assert.Equal("foo.prototype", functionMap[0].Bindings[0].Name);
Assert.Equal("foo", functionMap[0].Bindings[0].Name);
Assert.Equal(0, functionMap[0].Bindings[0].SourcePosition.ZeroBasedLineNumber);
Assert.Equal(23, functionMap[0].Bindings[0].SourcePosition.ZeroBasedColumnNumber);
Assert.Equal("bar", functionMap[0].Bindings[1].Name);

Просмотреть файл

@ -97,6 +97,7 @@
<Compile Include="KeyValueCacheUnitTests.cs" />
<Compile Include="StackFrameDeminifierUnitTests.cs" />
<Compile Include="StackTraceDeminifierClosureEndToEndTests.cs" />
<Compile Include="StackTraceDeminifierWebpackEndToEndTests.cs" />
<Compile Include="StackTraceDeminifierEndToEndTests.cs" />
<Compile Include="StackTraceDeminifierUnitTests.cs" />
<Compile Include="StackTraceParserUnitTests.cs" />
@ -150,6 +151,13 @@
<Error Condition="!Exists('..\..\packages\xunit.core.2.4.1\build\xunit.core.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\xunit.core.2.4.1\build\xunit.core.props'))" />
<Error Condition="!Exists('..\..\packages\xunit.core.2.4.1\build\xunit.core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\xunit.core.2.4.1\build\xunit.core.targets'))" />
</Target>
<Target Name="AfterBuild">
<ItemGroup>
<TestFiles Include="$(ProjectDir)webpackapp\*.*" />
</ItemGroup>
<Copy SourceFiles="@(TestFiles)" DestinationFolder="$(OutDir)webpackapp\">
</Copy>
</Target>
<Import Project="..\..\packages\xunit.core.2.4.1\build\xunit.core.targets" Condition="Exists('..\..\packages\xunit.core.2.4.1\build\xunit.core.targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

Просмотреть файл

@ -30,7 +30,7 @@ namespace SourcemapToolkit.CallstackDeminifier.UnitTests
Assert.Equal("mynamespace.objectWithMethods.propertyMethodLevel2", results.DeminifiedStackFrameResults[0].DeminifiedStackFrame.MethodName);
Assert.Equal("mynamespace.objectWithMethods.prototypeMethodLevel1", results.DeminifiedStackFrameResults[1].DeminifiedStackFrame.MethodName);
Assert.Equal("GlobalFunction", results.DeminifiedStackFrameResults[2].DeminifiedStackFrame.MethodName);
Assert.Equal("window", results.DeminifiedStackFrameResults[3].DeminifiedStackFrame.MethodName);
Assert.Equal("window.onload", results.DeminifiedStackFrameResults[3].DeminifiedStackFrame.MethodName);
}
[Fact]

Просмотреть файл

@ -31,7 +31,7 @@ namespace SourcemapToolkit.CallstackDeminifier.UnitTests
Assert.Equal("level2", results.DeminifiedStackFrameResults[2].DeminifiedStackFrame.MethodName);
Assert.Equal("level1", results.DeminifiedStackFrameResults[3].DeminifiedStackFrame.MethodName);
Assert.Equal("causeCrash", results.DeminifiedStackFrameResults[4].DeminifiedStackFrame.MethodName);
Assert.Equal("window", results.DeminifiedStackFrameResults[5].DeminifiedStackFrame.MethodName);
Assert.Equal("window.onload", results.DeminifiedStackFrameResults[5].DeminifiedStackFrame.MethodName);
}
[Fact]
@ -132,7 +132,7 @@ window.onload/<@http://localhost:11323/crashcauser.min.js:1:445";
at level2 in crashcauser.js:10:8
at level1 in crashcauser.js:5:8
at causeCrash in crashcauser.js:27:4
at window in crashcauser.js:32:8";
at window.onload in crashcauser.js:32:8";
// Act
string formatted = results.ToString();

Просмотреть файл

@ -0,0 +1,60 @@
using Xunit;
using System.IO;
using System;
namespace SourcemapToolkit.CallstackDeminifier.UnitTests
{
public class WebpackTestProvider : ISourceMapProvider, ISourceCodeProvider
{
private static readonly string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "webpackapp");
private StreamReader GetStreamOrNull(string fileName)
{
string filePath = Path.Combine(basePath, fileName);
if (File.Exists(filePath))
{
return new StreamReader(File.OpenRead(filePath));
}
return null;
}
public StreamReader GetSourceCode(string sourceUrl)
{
return GetStreamOrNull(Path.GetFileName(sourceUrl));
}
public StreamReader GetSourceMapContentsForCallstackUrl(string sourceUrl)
{
return GetStreamOrNull($"{Path.GetFileName(sourceUrl)}.map");
}
}
public class StackTraceDeminifierWebpackEndToEndTests
{
private StackTraceDeminifier GetStackTraceDeminifierWithDependencies()
{
var provider = new WebpackTestProvider();
return StackTraceDeminfierFactory.GetStackTraceDeminfier(provider, provider);
}
[Fact]
public void DeminifyStackTrace_MinifiedStackTrace_CorrectDeminificationWhenPossible()
{
// Arrange
StackTraceDeminifier stackTraceDeminifier = GetStackTraceDeminifierWithDependencies();
string chromeStackTrace = @"TypeError: Cannot read property 'nonExistantmember' of undefined
at t.onButtonClick (http://localhost:3000/js/bundle.ffe51781aee314a37903.min.js:1:3573)
at Object.sh (https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js:164:410)";
string deminifiedStackTrace = @"TypeError: Cannot read property 'nonExistantmember' of undefined
at _this.onButtonClick in webpack:///./components/App.tsx:10:45
at Object.sh in https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js:163:409";
// Act
DeminifyStackTraceResult results = stackTraceDeminifier.DeminifyStackTrace(chromeStackTrace);
// Assert
Assert.Equal(deminifiedStackTrace.Replace("\r", ""), results.ToString().Replace("\r", ""));
}
}
}

Просмотреть файл

@ -0,0 +1,5 @@
# Example Webpack App Source
This is an example of minified JS from a Webpack + TypeScript + React app.
It was generated from https://github.com/vikpe/react-webpack-typescript-starter

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Различия файлов скрыты, потому что одна или несколько строк слишком длинны