From cf11460a68846c4637361b472df8756d1a78ea5f Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Wed, 29 Aug 2018 13:45:53 +0200 Subject: [PATCH] Improve the quality of the source maps in some cases --- lib/ReplaceSource.js | 83 ++++++++++++++++++++++++++++++++----------- test/ReplaceSource.js | 18 ++++++---- 2 files changed, 74 insertions(+), 27 deletions(-) diff --git a/lib/ReplaceSource.js b/lib/ReplaceSource.js index a8235c6..f28a0fe 100644 --- a/lib/ReplaceSource.js +++ b/lib/ReplaceSource.js @@ -79,11 +79,38 @@ class ReplaceSource extends Source { var replace = new ReplacementEnumerator(this.replacements); var output = []; var position = 0; + var sources = Object.create(null); + var sourcesInLines = Object.create(null); // We build a new list of SourceNodes in "output" // from the original mapping data - var replaceInStringNode = this._replaceInStringNode.bind(this, output, replace); + var result = new SourceNode(); + + // We need to add source contents manually + // because "walk" will not handle it + node.walkSourceContents(function(sourceFile, sourceContent) { + result.setSourceContent(sourceFile, sourceContent); + sources["$" + sourceFile] = sourceContent; + }); + + var replaceInStringNode = this._replaceInStringNode.bind(this, output, replace, function getOriginalSource(mapping) { + var key = "$" + mapping.source; + var lines = sourcesInLines[key]; + if(!lines) { + var source = sources[key]; + if(!source) return null; + lines = source.split("\n").map(function(line) { + return line + "\n"; + }); + sourcesInLines[key] = lines; + } + // line is 1-based + if(mapping.line > lines.length) return null; + var line = lines[mapping.line - 1]; + return line.substr(mapping.column); + }); + node.walk(function(chunk, mapping) { position = replaceInStringNode(chunk, position, mapping); }); @@ -95,12 +122,7 @@ class ReplaceSource extends Source { output.push(remaining); } - var result = new SourceNode(null, null, null, output); - - // We need to add source contents afterwards - node.walkSourceContents(function(sourceFile, sourceContent) { - result.setSourceContent(sourceFile, sourceContent); - }); + result.add(output); return result; } @@ -161,7 +183,9 @@ class ReplaceSource extends Source { return position <= 0 ? ["", str] : [str.substr(0, position), str.substr(position)]; } - _replaceInStringNode(output, replace, node, position, parent) { + _replaceInStringNode(output, replace, getOriginalSource, node, position, mapping) { + var original = undefined; + do { var splitPosition = replace.position - position; // If multiple replaces occur in the same location then the splitPosition may be @@ -172,27 +196,46 @@ class ReplaceSource extends Source { if(splitPosition >= node.length || replace.done) { if(replace.emit) { var nodeEnd = new SourceNode( - parent.line, - parent.column, - parent.source, + mapping.line, + mapping.column, + mapping.source, node, - parent.name + mapping.name ); output.push(nodeEnd); } return position + node.length; } + + var originalColumn = mapping.column; + + // Try to figure out if generated code matches original code of this segement + // If this is the case we assume that it's allowed to move mapping.column + // Because getOriginalSource can be expensive we only do it when neccessary + + var nodePart; + if(splitPosition > 0) { + nodePart = node.slice(0, splitPosition); + if(original === undefined) { + original = getOriginalSource(mapping); + } + if(original && original.length >= splitPosition && original.startsWith(nodePart)) { + mapping.column += splitPosition; + original = original.substr(splitPosition); + } + } + var emit = replace.next(); if(!emit) { // Stop emitting when we have found the beginning of the string to replace. // Emit the part of the string before splitPosition if(splitPosition > 0) { var nodeStart = new SourceNode( - parent.line, - parent.column, - parent.source, - node.substr(0, splitPosition), - parent.name + mapping.line, + originalColumn, + mapping.source, + nodePart, + mapping.name ); output.push(nodeStart); } @@ -200,9 +243,9 @@ class ReplaceSource extends Source { // Emit the replacement value if(replace.value) { output.push(new SourceNode( - parent.line, - parent.column, - parent.source, + mapping.line, + mapping.column, + mapping.source, replace.value )); } diff --git a/test/ReplaceSource.js b/test/ReplaceSource.js index 228641b..d11f14f 100644 --- a/test/ReplaceSource.js +++ b/test/ReplaceSource.js @@ -44,8 +44,8 @@ describe("ReplaceSource", function() { resultListMap.map.version.should.be.eql(resultMap.map.version); resultListMap.map.sources.should.be.eql(resultMap.map.sources); resultListMap.map.sourcesContent.should.be.eql(resultMap.map.sourcesContent); - resultMap.map.mappings.should.be.eql("AAAA;AACA;AAAA;AAAA;AAIA,KACA"); - resultListMap.map.mappings.should.be.eql(resultMap.map.mappings); + resultMap.map.mappings.should.be.eql("AAAA,CAAC,EAAI,KAAE,IAAC;AACR,CAAC;AAAA;AAAA;AAID,IAAI,CACJ"); + resultListMap.map.mappings.should.be.eql("AAAA;AACA;AAAA;AAAA;AAIA,KACA"); }); it("should replace multiple items correctly", function() { @@ -65,6 +65,7 @@ describe("ReplaceSource", function() { var resultListMap = source.sourceAndMap({ columns: false }); + resultText.should.be.eql("Message: Hey Ad!"); resultMap.source.should.be.eql(resultText); resultListMap.source.should.be.eql(resultText); @@ -72,8 +73,8 @@ describe("ReplaceSource", function() { resultListMap.map.version.should.be.eql(resultMap.map.version); resultListMap.map.sources.should.be.eql(resultMap.map.sources); resultListMap.map.sourcesContent.should.be.eql(resultMap.map.sourcesContent); - resultMap.map.mappings.should.be.eql("AAAA,cACA"); - resultListMap.map.mappings.should.be.eql(resultMap.map.mappings); + resultMap.map.mappings.should.be.eql("AAAA,WAAE,GACE"); + resultListMap.map.mappings.should.be.eql("AAAA,cACA"); }); it("should prepend items correctly", function() { @@ -89,6 +90,7 @@ describe("ReplaceSource", function() { var resultListMap = source.sourceAndMap({ columns: false }); + resultText.should.be.eql("Line -1\nLine 0\nLine 1"); resultMap.source.should.be.eql(resultText); resultListMap.source.should.be.eql(resultText); @@ -97,7 +99,7 @@ describe("ReplaceSource", function() { resultListMap.map.sources.should.be.eql(resultMap.map.sources); resultListMap.map.sourcesContent.should.be.eql(resultMap.map.sourcesContent); resultMap.map.mappings.should.be.eql("AAAA;AAAA;AAAA"); - resultListMap.map.mappings.should.be.eql(resultMap.map.mappings); + resultListMap.map.mappings.should.be.eql("AAAA;AAAA;AAAA"); }); it("should prepend items with replace at start correctly", function() { @@ -116,6 +118,7 @@ describe("ReplaceSource", function() { var resultListMap = source.sourceAndMap({ columns: false }); + resultText.should.be.eql("Line 0\nHello\nLine 2"); resultMap.source.should.be.eql(resultText); resultListMap.source.should.be.eql(resultText); @@ -123,8 +126,8 @@ describe("ReplaceSource", function() { resultListMap.map.version.should.be.eql(resultMap.map.version); resultListMap.map.sources.should.be.eql(resultMap.map.sources); resultListMap.map.sourcesContent.should.be.eql(resultMap.map.sourcesContent); - resultMap.map.mappings.should.be.eql("AAAA;AAAA;AACA"); - resultListMap.map.mappings.should.be.eql(resultMap.map.mappings); + resultMap.map.mappings.should.be.eql("AAAA;AAAA,KAAM;AACN"); + resultListMap.map.mappings.should.be.eql("AAAA;AAAA;AACA"); }); it("should append items correctly", function() { @@ -140,6 +143,7 @@ describe("ReplaceSource", function() { var resultListMap = source.sourceAndMap({ columns: false }); + resultText.should.be.eql("Line 1\nLine 2\n"); resultMap.source.should.be.eql(resultText); resultListMap.source.should.be.eql(resultText);