123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
- */
- "use strict";
- var Source = require("./Source");
- var SourceNode = require("source-map").SourceNode;
- 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, name) {
- if(typeof newValue !== "string")
- throw new Error("insertion must be a string, but is a " + typeof newValue);
- this.replacements.push(new Replacement(start, end, newValue, this.replacements.length, name));
- }
- 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(new Replacement(pos, pos - 1, newValue, this.replacements.length, name));
- }
- source(options) {
- return this._replaceString(this._source.source());
- }
- original() {
- return this._source;
- }
- _sortReplacements() {
- this.replacements.sort(function(a, b) {
- var diff = b.end - a.end;
- if(diff !== 0)
- return diff;
- diff = b.start - a.start;
- if(diff !== 0)
- return diff;
- return b.insertIndex - a.insertIndex;
- });
- }
- _replaceString(str) {
- if(typeof str !== "string")
- throw new Error("str must be a string, but is a " + typeof str + ": " + str);
- this._sortReplacements();
- var result = [str];
- this.replacements.forEach(function(repl) {
- var remSource = result.pop();
- 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
- let resultStr = "";
- for(let i = result.length - 1; i >= 0; --i) {
- resultStr += result[i];
- }
- return resultStr;
- }
- node(options) {
- var node = this._source.node(options);
- if(this.replacements.length === 0) {
- return node;
- }
- this._sortReplacements();
- 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 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);
- });
- // If any replacements occur after the end of the original file, then we append them
- // directly to the end of the output
- var remaining = replace.footer();
- if(remaining) {
- output.push(remaining);
- }
- result.add(output);
- return result;
- }
- listMap(options) {
- this._sortReplacements();
- var map = this._source.listMap(options);
- var currentIndex = 0;
- var replacements = this.replacements;
- var idxReplacement = replacements.length - 1;
- var removeChars = 0;
- map = map.mapGeneratedCode(function(str) {
- var newCurrentIndex = currentIndex + str.length;
- if(removeChars > str.length) {
- removeChars -= str.length;
- str = "";
- } else {
- if(removeChars > 0) {
- str = str.substr(removeChars);
- currentIndex += removeChars;
- removeChars = 0;
- }
- var finalStr = "";
- while(idxReplacement >= 0 && replacements[idxReplacement].start < newCurrentIndex) {
- var repl = replacements[idxReplacement];
- 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.content;
- str = after;
- currentIndex = Math.max(currentIndex, end);
- } else {
- finalStr += before + repl.content;
- str = "";
- removeChars = end - newCurrentIndex;
- }
- idxReplacement--;
- }
- str = finalStr + str;
- }
- currentIndex = newCurrentIndex;
- return str;
- });
- var extraCode = "";
- while(idxReplacement >= 0) {
- extraCode += replacements[idxReplacement].content;
- idxReplacement--;
- }
- if(extraCode) {
- map.add(extraCode);
- }
- return map;
- }
- _splitString(str, position) {
- return position <= 0 ? ["", str] : [str.substr(0, position), str.substr(position)];
- }
- _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
- // before the current position for the subsequent splits. Ensure it is >= 0
- if(splitPosition < 0) {
- splitPosition = 0;
- }
- if(splitPosition >= node.length || replace.done) {
- if(replace.emit) {
- var nodeEnd = new SourceNode(
- mapping.line,
- mapping.column,
- mapping.source,
- node,
- 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(
- mapping.line,
- originalColumn,
- mapping.source,
- nodePart,
- mapping.name
- );
- output.push(nodeStart);
- }
- // Emit the replacement value
- if(replace.value) {
- output.push(new SourceNode(
- mapping.line,
- mapping.column,
- mapping.source,
- replace.value,
- mapping.name || replace.name
- ));
- }
- }
- // Recurse with remainder of the string as there may be multiple replaces within a single node
- node = node.substr(splitPosition);
- position += splitPosition;
- } while (true);
- }
- }
- class ReplacementEnumerator {
- /**
- * @param {Replacement[]} replacements list of replacements
- */
- constructor(replacements) {
- this.replacements = replacements || [];
- this.index = this.replacements.length;
- this.done = false;
- this.emit = false;
- // Set initial start position
- this.next();
- }
- next() {
- if(this.done)
- return true;
- if(this.emit) {
- // Start point found. stop emitting. set position to find end
- var repl = this.replacements[this.index];
- var end = Math.floor(repl.end + 1);
- this.position = end;
- 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 < 0) {
- this.done = true;
- } else {
- var nextRepl = this.replacements[this.index];
- var start = Math.floor(nextRepl.start);
- this.position = start;
- }
- }
- if(this.position < 0)
- this.position = 0;
- this.emit = !this.emit;
- return this.emit;
- }
- footer() {
- if(!this.done && !this.emit)
- this.next(); // If we finished _replaceInNode mid emit we advance to next entry
- if(this.done) {
- return [];
- } else {
- var resultStr = "";
- for(var i = this.index; i >= 0; i--) {
- var repl = this.replacements[i];
- // this doesn't need to handle repl.name, because in SourceMaps generated code
- // without pointer to original source can't have a name
- resultStr += repl.content;
- }
- return resultStr;
- }
- }
- }
- require("./SourceAndMapMixin")(ReplaceSource.prototype);
- module.exports = ReplaceSource;
|