From 3fb57e94f5a1abbcdfab40be0d5b5180b9269de2 Mon Sep 17 00:00:00 2001 From: Ian Craig Date: Thu, 15 Aug 2019 13:16:03 -0700 Subject: [PATCH] Better method name parsing and mapping (#76) * Fix method name association, add Webpack + TypeScript example test --- .gitignore | 4 +- .../FunctionFinderVisitor.cs | 44 ++++++++++++-- .../FunctionMapGenerator.cs | 20 +++++-- .../FunctionMapGeneratorUnitTests.cs | 16 +++-- ...olkit.CallstackDeminifier.UnitTests.csproj | 8 +++ ...tackTraceDeminifierClosureEndToEndTests.cs | 2 +- .../StackTraceDeminifierEndToEndTests.cs | 4 +- ...tackTraceDeminifierWebpackEndToEndTests.cs | 60 +++++++++++++++++++ .../webpackapp/Readme.md | 5 ++ .../bundle.ffe51781aee314a37903.min.js | 2 + .../bundle.ffe51781aee314a37903.min.js.map | 1 + 11 files changed, 145 insertions(+), 21 deletions(-) create mode 100644 tests/SourcemapToolkit.CallstackDeminifier.UnitTests/StackTraceDeminifierWebpackEndToEndTests.cs create mode 100644 tests/SourcemapToolkit.CallstackDeminifier.UnitTests/webpackapp/Readme.md create mode 100644 tests/SourcemapToolkit.CallstackDeminifier.UnitTests/webpackapp/bundle.ffe51781aee314a37903.min.js create mode 100644 tests/SourcemapToolkit.CallstackDeminifier.UnitTests/webpackapp/bundle.ffe51781aee314a37903.min.js.map diff --git a/.gitignore b/.gitignore index e0c8be1..e5bcabc 100644 --- a/.gitignore +++ b/.gitignore @@ -266,4 +266,6 @@ __pycache__/ # Minified JavaScript *.min.js -*.min.js.map \ No newline at end of file +*.min.js.map + +!**/webpackapp/** \ No newline at end of file diff --git a/src/SourceMapToolkit.CallstackDeminifier/FunctionFinderVisitor.cs b/src/SourceMapToolkit.CallstackDeminifier/FunctionFinderVisitor.cs index efb32a8..a5aca36 100644 --- a/src/SourceMapToolkit.CallstackDeminifier/FunctionFinderVisitor.cs +++ b/src/SourceMapToolkit.CallstackDeminifier/FunctionFinderVisitor.cs @@ -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 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 } }; } diff --git a/src/SourceMapToolkit.CallstackDeminifier/FunctionMapGenerator.cs b/src/SourceMapToolkit.CallstackDeminifier/FunctionMapGenerator.cs index 7a3d243..f232447 100644 --- a/src/SourceMapToolkit.CallstackDeminifier/FunctionMapGenerator.cs +++ b/src/SourceMapToolkit.CallstackDeminifier/FunctionMapGenerator.cs @@ -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 { diff --git a/tests/SourcemapToolkit.CallstackDeminifier.UnitTests/FunctionMapGeneratorUnitTests.cs b/tests/SourcemapToolkit.CallstackDeminifier.UnitTests/FunctionMapGeneratorUnitTests.cs index e52cbaf..be7328a 100644 --- a/tests/SourcemapToolkit.CallstackDeminifier.UnitTests/FunctionMapGeneratorUnitTests.cs +++ b/tests/SourcemapToolkit.CallstackDeminifier.UnitTests/FunctionMapGeneratorUnitTests.cs @@ -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); diff --git a/tests/SourcemapToolkit.CallstackDeminifier.UnitTests/SourcemapToolkit.CallstackDeminifier.UnitTests.csproj b/tests/SourcemapToolkit.CallstackDeminifier.UnitTests/SourcemapToolkit.CallstackDeminifier.UnitTests.csproj index dc7705a..89df485 100644 --- a/tests/SourcemapToolkit.CallstackDeminifier.UnitTests/SourcemapToolkit.CallstackDeminifier.UnitTests.csproj +++ b/tests/SourcemapToolkit.CallstackDeminifier.UnitTests/SourcemapToolkit.CallstackDeminifier.UnitTests.csproj @@ -97,6 +97,7 @@ + @@ -150,6 +151,13 @@ + + + + + + +