| Index: Source/devtools/front_end/bindings/SourceMapEditor.js
|
| diff --git a/Source/devtools/front_end/bindings/SourceMapEditor.js b/Source/devtools/front_end/bindings/SourceMapEditor.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..fd1e0fd8a30b24af6c7634493e4c36b29f51b0a3
|
| --- /dev/null
|
| +++ b/Source/devtools/front_end/bindings/SourceMapEditor.js
|
| @@ -0,0 +1,260 @@
|
| +/**
|
| + * @constructor
|
| + * @param {!Map.<string, string>} sources
|
| + * @param {!WebInspector.SourceMapEditorFlavor} flavor
|
| + */
|
| +WebInspector.SourceMapEditor = function(sources, flavor)
|
| +{
|
| + this._sources = sources;
|
| + this._editFlavor = flavor;
|
| +}
|
| +/**
|
| + * @param {string} sourceURL
|
| + * @param {string} contentV1
|
| + * @param {string} contentV2
|
| + * @return {!Array.<!WebInspector.SourceMapEdit>}
|
| + */
|
| +WebInspector.SourceMapEditor.computeEdits = function(sourceURL, contentV1, contentV2)
|
| +{
|
| + var differ = new diff_match_patch();
|
| + var diff = differ.diff_main(contentV1, contentV2);
|
| + differ.diff_cleanupEfficiency(diff);
|
| + var oldSourceOffset = 0;
|
| + var edits = [];
|
| + var i = 0;
|
| + while (i < diff.length) {
|
| + var token = diff[i];
|
| + if (token[0] === 0) {
|
| + oldSourceOffset += token[1].length;
|
| + ++i;
|
| + continue;
|
| + }
|
| +
|
| + var nextToken = diff[i + 1];
|
| + if (nextToken && nextToken[0] === -token[0]) {
|
| + // If this is an EDIT command.
|
| + var add, remove;
|
| + if (token[0] === 1) {
|
| + add = token;
|
| + remove = nextToken;
|
| + } else {
|
| + add = nextToken;
|
| + remove = token;
|
| + }
|
| + var sourceRange = new WebInspector.SourceRange(oldSourceOffset, remove[1].length);
|
| + var oldRange = WebInspector.TextRange.createFromSourceRange(contentV1, sourceRange);
|
| + edits.push(new WebInspector.SourceMapEdit(sourceURL, oldRange, remove[1], add[1]));
|
| + oldSourceOffset += remove[1].length;
|
| + i += 2;
|
| + } else if (token[0] === 1) {
|
| + // If this is an ADD command.
|
| + var sourceRange = new WebInspector.SourceRange(oldSourceOffset, 0);
|
| + var oldRange = WebInspector.TextRange.createFromSourceRange(contentV1, sourceRange);
|
| + edits.push(new WebInspector.SourceMapEdit(sourceURL, oldRange, "", token[1]));
|
| + i += 1;
|
| + } else {
|
| + // If this is a REMOVE command.
|
| + var sourceRange = new WebInspector.SourceRange(oldSourceOffset, token[1].length);
|
| + var oldRange = WebInspector.TextRange.createFromSourceRange(contentV1, sourceRange);
|
| + edits.push(new WebInspector.SourceMapEdit(sourceURL, oldRange, token[1], ""));
|
| + oldSourceOffset += token[1].length;
|
| + i += 1;
|
| + }
|
| + }
|
| + return edits;
|
| +}
|
| +
|
| +WebInspector.SourceMapEditor.prototype = {
|
| + /**
|
| + * @param {!WebInspector.SourceMap} map
|
| + * @param {!Array<!WebInspector.SourceMapEdit>} edits
|
| + * @return {!Set<string, string>}
|
| + */
|
| + handleCompiledEdits: function(map, edits)
|
| + {
|
| + var backEditInfos = [];
|
| + for (var i = 0; i < edits.length; ++i)
|
| + backEditInfos.push(this._doCompiledEdit(map, edits[i]));
|
| +
|
| + var didBackEdits = false;
|
| + for (var i = 0; i < edits.length; ++i)
|
| + didBackEdits = this._doBackEdits(map, edits[i], backEditInfos[i]) || didBackEdits;
|
| +
|
| + var result = new Set();
|
| + if (didBackEdits)
|
| + result.add(edits[0].sourceURL);
|
| + for (var i = 0; i < backEditInfos.length; ++i) {
|
| + var sourceURL = backEditInfos[i].compiledBaseMapping.sourceURL;
|
| + result.add(sourceURL);
|
| + }
|
| + return result;
|
| + },
|
| +
|
| + /**
|
| + * @param {!WebInspector.SourceMap} map
|
| + * @param {!WebInspector.SourceMapEdit} edit
|
| + * @return {!WebInspector.SourceMapEditor.BackEditInfo}
|
| + */
|
| + _doCompiledEdit: function(map, edit)
|
| + {
|
| + var startMapping = map.findEntry(edit.oldRange.startLine, edit.oldRange.startColumn);
|
| + var endMapping = map.findEntry(edit.oldRange.endLine, edit.oldRange.endColumn);
|
| + if (startMapping.sourceURL !== endMapping.sourceURL)
|
| + throw new Error("An edit spans multiple source files.");
|
| +
|
| + var sourceURL = startMapping.sourceURL;
|
| + var compiledText = this._sources.get(edit.sourceURL);
|
| + var sourceText = this._sources.get(sourceURL);
|
| +
|
| + // Return if we don't have source for mapped entries.
|
| + if (!compiledText || !sourceText)
|
| + throw new Error("Source of " + sourceURL + " is missing");
|
| +
|
| + var startLocation = this._mapCompiledLocation(startMapping, edit.oldRange.startLine, edit.oldRange.startColumn);
|
| + var endLocation = this._mapCompiledLocation(endMapping, edit.oldRange.endLine, edit.oldRange.endColumn);
|
| + var sourceOldRange = new WebInspector.TextRange(startLocation.line, startLocation.column, endLocation.line, endLocation.column);
|
| + var sourceOldText = sourceOldRange.extract(sourceText);
|
| + // If the text in compiled and original differs, then we cannot edit it.
|
| + var sourceNewText = this._editFlavor.sourceEditText(edit.oldText, sourceOldText, edit.newText)
|
| + if (sourceNewText === null)
|
| + throw new Error("The edited range is not equal in origin and source");
|
| +
|
| + var newTextLineCount = sourceNewText.lineEndings().length;
|
| + if (newTextLineCount > 1)
|
| + throw new Error("Not implemented for multi-line edit");
|
| +
|
| + if (!map.compiledRangeEdited(edit.oldRange, edit.newRange()))
|
| + throw new Error("Failed to update compiled locations in sourcemap.");
|
| +
|
| + var sourceEdit = new WebInspector.SourceMapEdit(sourceURL, sourceOldRange, sourceOldText, sourceNewText);
|
| + if (!map.sourceRangeEdited(sourceURL, sourceEdit.oldRange, sourceEdit.newRange()))
|
| + throw new Error("Failed to update source locations in sourcemap.");
|
| +
|
| + this._sources.set(sourceURL, sourceEdit.applyToText(sourceText));
|
| +
|
| + var offsetRange = edit.oldRange.relativeTo(startMapping.lineNumber, startMapping.columnNumber);
|
| + var sourceNewRange = sourceEdit.newRange();
|
| + var reversedMappings = map.findEntriesReversed(sourceEdit.sourceURL, sourceNewRange.startLine, sourceNewRange.startColumn + 1);
|
| + return new WebInspector.SourceMapEditor.BackEditInfo(startMapping, offsetRange, reversedMappings);
|
| + },
|
| +
|
| + _doBackEdits: function(map, compiledEdit, backEditInfo)
|
| + {
|
| + // There is no multimapping from source position to compiled position.
|
| + if (backEditInfo.reversedMappings.length < 2)
|
| + return false;
|
| + var didEdit = false;
|
| + var compiledText = this._sources.get(compiledEdit.sourceURL);
|
| + for (var i = 0; i < backEditInfo.reversedMappings.length; ++i) {
|
| + var mapping = backEditInfo.reversedMappings[i];
|
| + if (mapping === backEditInfo.compiledBaseMapping)
|
| + continue;
|
| + var oldEditRange = rebaseOffsetRange(backEditInfo.offsetRange, mapping);
|
| + var reverseEdit = new WebInspector.SourceMapEdit(compiledEdit.sourceURL, oldEditRange, oldEditRange.extract(compiledText), compiledEdit.newText);
|
| + if (reverseEdit.oldText !== compiledEdit.oldText) {
|
| + console.error("compiled texts differ.");
|
| + continue;
|
| + }
|
| + if (!map.compiledRangeEdited(reverseEdit.oldRange, reverseEdit.newRange()))
|
| + throw new Error("Failed to update reversed compiled locations in sourcemap.");
|
| + compiledText = reverseEdit.applyToText(compiledText);
|
| + didEdit = true;
|
| + }
|
| + if (didEdit)
|
| + this._sources.set(compiledEdit.sourceURL, compiledText);
|
| +
|
| + return didEdit;
|
| +
|
| + function rebaseOffsetRange(range, mapping)
|
| + {
|
| + return new WebInspector.TextRange(
|
| + range.startLine + mapping.lineNumber,
|
| + range.startColumn + mapping.columnNumber,
|
| + range.endLine + mapping.lineNumber,
|
| + range.endColumn + mapping.columnNumber
|
| + );
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * @param {!WebInspector.SourceMap.Entry} entry
|
| + * @param {number} lineNumber
|
| + * @param {number} columnNumber
|
| + * @return {!{line: number, column: number}}
|
| + */
|
| + _mapCompiledLocation: function(entry, lineNumber, columnNumber)
|
| + {
|
| + var lineOffset = lineNumber - entry.lineNumber;
|
| + var columnOffset = columnNumber - entry.columnNumber;
|
| + return {line: entry.sourceLineNumber + lineOffset, column: entry.sourceColumnNumber + columnOffset};
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * @constructor
|
| + * @param {string} sourceURL
|
| + * @param {!WebInspector.TextRange} oldRange
|
| + * @param {string} oldText
|
| + * @param {string} newText
|
| + */
|
| +WebInspector.SourceMapEdit = function(sourceURL, oldRange, oldText, newText)
|
| +{
|
| + this.sourceURL = sourceURL;
|
| + this.oldRange = oldRange;
|
| + this.oldText = oldText;
|
| + this.newText = newText;
|
| +}
|
| +
|
| +WebInspector.SourceMapEdit.prototype = {
|
| + /**
|
| + * @return {!WebInspector.TextRange}
|
| + */
|
| + newRange: function()
|
| + {
|
| + var endLine = this.oldRange.startLine;
|
| + var endColumn = this.oldRange.startColumn + this.newText.length;
|
| + var lineEndings = this.newText.lineEndings();
|
| + if (lineEndings.length > 1) {
|
| + endLine = this.oldRange.startLine + lineEndings.length - 1;
|
| + var len = lineEndings.length;
|
| + endColumn = lineEndings[len - 1] - lineEndings[len - 2] - 1;
|
| + }
|
| + return new WebInspector.TextRange(
|
| + this.oldRange.startLine,
|
| + this.oldRange.startColumn,
|
| + endLine,
|
| + endColumn);
|
| + },
|
| +
|
| + applyToText: function(text)
|
| + {
|
| + return this.oldRange.replaceInText(text, this.newText);
|
| + },
|
| +}
|
| +
|
| +WebInspector.SourceMapEditor.BackEditInfo = function(compiledBaseMapping, offsetRange, reversedMappings)
|
| +{
|
| + this.compiledBaseMapping = compiledBaseMapping;
|
| + this.offsetRange = offsetRange;
|
| + this.reversedMappings = reversedMappings;
|
| +}
|
| +
|
| +/**
|
| + * @interface
|
| + */
|
| +WebInspector.SourceMapEditorFlavor = function() { }
|
| +
|
| +WebInspector.SourceMapEditorFlavor.prototype = {
|
| + /**
|
| + * @param {string} compiledURL
|
| + * @param {string} compiledText
|
| + * @param {!WebInspector.TextRange} compiledRange
|
| + * @param {string} sourceURL
|
| + * @param {string} sourceText
|
| + * @param {!WebInspector.TextRange} sourceRange
|
| + * @return {?string}
|
| + */
|
| + sourceEditText: function(compiledURL, compiledText, compiledRange, sourceText, sourceURL, sourceRange)
|
| + {
|
| + },
|
| +}
|
|
|