Skip to content

Commit

Permalink
Merge pull request #41 from Hoten/bugfix/invalid-sourcemaps-whitespace
Browse files Browse the repository at this point in the history
Bugfix/invalid sourcemaps whitespace
  • Loading branch information
sokra authored Sep 20, 2018
2 parents 30d2a80 + 8de1165 commit 9d14ed4
Show file tree
Hide file tree
Showing 6 changed files with 276 additions and 28 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Source.prototype.sourceAndMap(options: Object) -> {
}
```

Returns both, source code (like `Source.prototype.source()` and SourceMap (like `Source.prototype.map()`). This method could have better performance than calling `source()` and `map()` separatly.
Returns both, source code (like `Source.prototype.source()` and SourceMap (like `Source.prototype.map()`). This method could have better performance than calling `source()` and `map()` separately.

See `map()` for `options`.

Expand Down
5 changes: 4 additions & 1 deletion lib/OriginalSource.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ class OriginalSource extends Source {
}
return new SourceNode(null, null, null,
_splitCode(line + (idx != lines.length - 1 ? "\n" : "")).map(function(item) {
if(/^\s*$/.test(item)) return item;
if(/^\s*$/.test(item)) {
pos += item.length;
return item;
}
var res = new SourceNode(idx + 1, pos, name, item);
pos += item.length;
return res;
Expand Down
68 changes: 43 additions & 25 deletions lib/ReplaceSource.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,35 @@ var SourceListMap = require("source-list-map").SourceListMap;
var fromStringWithSourceMap = require("source-list-map").fromStringWithSourceMap;
var SourceMapConsumer = require("source-map").SourceMapConsumer;

class Replacement {
constructor(start, end, content, insertIndex, name) {
this.start = start;
this.end = end;
this.content = content;
this.insertIndex = insertIndex;
this.name = name;
}
}

class ReplaceSource extends Source {
constructor(source, name) {
super();
this._source = source;
this._name = name;
/** @type {Replacement[]} */
this.replacements = [];
}

replace(start, end, newValue) {
replace(start, end, newValue, name) {
if(typeof newValue !== "string")
throw new Error("insertion must be a string, but is a " + typeof newValue);
this.replacements.push([start, end, newValue, this.replacements.length]);
this.replacements.push(new Replacement(start, end, newValue, this.replacements.length, name));
}

insert(pos, newValue) {
insert(pos, newValue, name) {
if(typeof newValue !== "string")
throw new Error("insertion must be a string, but is a " + typeof newValue + ": " + newValue);
this.replacements.push([pos, pos - 1, newValue, this.replacements.length]);
this.replacements.push(new Replacement(pos, pos - 1, newValue, this.replacements.length, name));
}

source(options) {
Expand All @@ -40,13 +51,13 @@ class ReplaceSource extends Source {

_sortReplacements() {
this.replacements.sort(function(a, b) {
var diff = b[1] - a[1];
var diff = b.end - a.end;
if(diff !== 0)
return diff;
diff = b[0] - a[0];
diff = b.start - a.start;
if(diff !== 0)
return diff;
return b[3] - a[3];
return b.insertIndex - a.insertIndex;
});
}

Expand All @@ -57,9 +68,9 @@ class ReplaceSource extends Source {
var result = [str];
this.replacements.forEach(function(repl) {
var remSource = result.pop();
var splitted1 = this._splitString(remSource, Math.floor(repl[1] + 1));
var splitted2 = this._splitString(splitted1[0], Math.floor(repl[0]));
result.push(splitted1[1], repl[2], splitted2[0]);
var splitted1 = this._splitString(remSource, Math.floor(repl.end + 1));
var splitted2 = this._splitString(splitted1[0], Math.floor(repl.start));
result.push(splitted1[1], repl.content, splitted2[0]);
}, this);

// write out result array in reverse order
Expand Down Expand Up @@ -146,18 +157,18 @@ class ReplaceSource extends Source {
removeChars = 0;
}
var finalStr = "";
while(idxReplacement >= 0 && replacements[idxReplacement][0] < newCurrentIndex) {
while(idxReplacement >= 0 && replacements[idxReplacement].start < newCurrentIndex) {
var repl = replacements[idxReplacement];
var start = Math.floor(repl[0]);
var end = Math.floor(repl[1] + 1);
var start = Math.floor(repl.start);
var end = Math.floor(repl.end + 1);
var before = str.substr(0, Math.max(0, start - currentIndex));
if(end <= newCurrentIndex) {
var after = str.substr(Math.max(0, end - currentIndex));
finalStr += before + repl[2];
finalStr += before + repl.content;
str = after;
currentIndex = Math.max(currentIndex, end);
} else {
finalStr += before + repl[2];
finalStr += before + repl.content;
str = "";
removeChars = end - newCurrentIndex;
}
Expand All @@ -170,7 +181,7 @@ class ReplaceSource extends Source {
});
var extraCode = "";
while(idxReplacement >= 0) {
extraCode += replacements[idxReplacement][2];
extraCode += replacements[idxReplacement].content;
idxReplacement--;
}
if(extraCode) {
Expand Down Expand Up @@ -246,7 +257,8 @@ class ReplaceSource extends Source {
mapping.line,
mapping.column,
mapping.source,
replace.value
replace.value,
mapping.name || replace.name
));
}
}
Expand All @@ -259,16 +271,19 @@ class ReplaceSource extends Source {
}

function sortReplacementsAscending(a, b) {
var diff = a[1] - b[1]; // end
var diff = a.end - b.end;
if(diff !== 0)
return diff;
diff = a[0] - b[0]; // start
diff = a.start - b.start;
if(diff !== 0)
return diff;
return a[3] - b[3]; // insert order
return a.insertIndex - b.insertIndex;
}

class ReplacementEnumerator {
/**
* @param {Replacement[]} replacements list of replacements
*/
constructor(replacements) {
this.emit = true;
this.done = !replacements || replacements.length === 0;
Expand All @@ -277,7 +292,7 @@ class ReplacementEnumerator {
if(!this.done) {
// Set initial start position in case .header is not called
var repl = replacements[0];
this.position = Math.floor(repl[0]);
this.position = Math.floor(repl.start);
if(this.position < 0)
this.position = 0;
}
Expand All @@ -289,17 +304,18 @@ class ReplacementEnumerator {
if(this.emit) {
// Start point found. stop emitting. set position to find end
var repl = this.replacements[this.index];
var end = Math.floor(repl[1] + 1);
var end = Math.floor(repl.end + 1);
this.position = end;
this.value = repl[2];
this.value = repl.content;
this.name = repl.name;
} else {
// End point found. start emitting. set position to find next start
this.index++;
if(this.index >= this.replacements.length) {
this.done = true;
} else {
var nextRepl = this.replacements[this.index];
var start = Math.floor(nextRepl[0]);
var start = Math.floor(nextRepl.start);
this.position = start;
}
}
Expand All @@ -313,7 +329,9 @@ class ReplacementEnumerator {
if(!this.done && !this.emit)
this.next(); // If we finished _replaceInNode mid emit we advance to next entry
return this.done ? [] : this.replacements.slice(this.index).map(function(repl) {
return repl[2];
// this doesn't need to handle repl.name, because in SourceMaps generated code
// without pointer to original source can't have a name
return repl.content;
}).join("");
}
}
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"istanbul": "^0.4.1",
"js-beautify": "^1.5.10",
"mocha": "^3.4.2",
"should": "^11.2.1"
"should": "^11.2.1",
"sourcemap-validator": "^1.1.0"
},
"files": [
"lib/"
Expand Down
20 changes: 20 additions & 0 deletions test/ReplaceSource.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ var should = require("should");
var ReplaceSource = require("../lib/ReplaceSource");
var RawSource = require("../lib/RawSource");
var OriginalSource = require("../lib/OriginalSource");
var validate = require('sourcemap-validator');

describe("ReplaceSource", function() {
it("should replace correctly", function() {
Expand Down Expand Up @@ -154,4 +155,23 @@ describe("ReplaceSource", function() {
resultMap.map.mappings.should.be.eql("AAAA");
resultListMap.map.mappings.should.be.eql("AAAA;;");
});

it("should produce correct source map", function() {
var bootstrapCode = ' var hello\n var world\n';

should(function() {
var source = new ReplaceSource(new OriginalSource(bootstrapCode, "file.js"));
source.replace(7, 11, 'h', 'incorrect');
source.replace(20, 24, 'w', 'identifiers');
var resultMap = source.sourceAndMap();
validate(resultMap.source, JSON.stringify(resultMap.map));
}).throw();

var source = new ReplaceSource(new OriginalSource(bootstrapCode, "file.js"));
source.replace(7, 11, 'h', 'hello');
source.replace(20, 24, 'w', 'world');
var resultMap = source.sourceAndMap();
validate(resultMap.source, JSON.stringify(resultMap.map));

});
});
Loading

0 comments on commit 9d14ed4

Please sign in to comment.