| /* | 
|     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; |