Index: tests/compiler/dart2js/sourcemaps/diff.dart |
diff --git a/tests/compiler/dart2js/sourcemaps/diff.dart b/tests/compiler/dart2js/sourcemaps/diff.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..6fb4468bd59ea3d617e9b7980d5e58cffb2ab61f |
--- /dev/null |
+++ b/tests/compiler/dart2js/sourcemaps/diff.dart |
@@ -0,0 +1,444 @@ |
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+library sourcemap.diff; |
+ |
+import 'package:compiler/src/io/source_file.dart'; |
+ |
+import 'html_parts.dart'; |
+import 'output_structure.dart'; |
+import 'sourcemap_helper.dart'; |
+ |
+enum DiffKind { |
+ UNMATCHED, |
+ MATCHING, |
+ IDENTICAL, |
+} |
+ |
+/// A list of columns that should align in output. |
+class DiffBlock { |
+ final DiffKind kind; |
+ List<List<HtmlPart>> columns = <List<HtmlPart>>[]; |
+ |
+ DiffBlock(this.kind); |
+ |
+ void addColumn(int index, List<HtmlPart> lines) { |
+ if (index >= columns.length) { |
+ columns.length = index + 1; |
+ } |
+ columns[index] = lines; |
+ } |
+ |
+ List<HtmlPart> getColumn(int index) { |
+ List<HtmlPart> lines; |
+ if (index < columns.length) { |
+ lines = columns[index]; |
+ } |
+ return lines != null ? lines : const <HtmlPart>[]; |
+ } |
+} |
+ |
+ |
+/// Align the content of [list1] and [list2]. |
+/// |
+/// If provided, [range1] and [range2] aligned the subranges of [list1] and |
+/// [list2], otherwise the whole lists are aligned. |
+/// |
+/// If provided, [match] determines the equality between members of [list1] and |
+/// [list2], otherwise `==` is used. |
+/// |
+/// [handleSkew] is called when a subrange of one list is not found in the |
+/// other. |
+/// |
+/// [handleMatched] is called when two indices match up. |
+/// |
+/// [handleUnmatched] is called when two indices don't match up (none are found |
+/// in the other list). |
+void align(List list1, |
+ List list2, |
+ {Interval range1, |
+ Interval range2, |
+ bool match(a, b), |
+ void handleSkew(int listIndex, Interval range), |
+ void handleMatched(List<int> indices), |
+ void handleUnmatched(List<int> indices)}) { |
+ if (match == null) { |
+ match = (a, b) => a == b; |
+ } |
+ |
+ if (range1 == null) { |
+ range1 = new Interval(0, list1.length); |
+ } |
+ if (range2 == null) { |
+ range2 = new Interval(0, list2.length); |
+ } |
+ |
+ Interval findInOther( |
+ List thisLines, Interval thisRange, |
+ List otherLines, Interval otherRange) { |
+ for (int index = otherRange.from; index < otherRange.to; index++) { |
+ if (match(thisLines[thisRange.from], otherLines[index])) { |
+ int offset = 1; |
+ while (thisRange.from + offset < thisRange.to && |
+ otherRange.from + offset < otherRange.to && |
+ match(thisLines[thisRange.from + offset], |
+ otherLines[otherRange.from + offset])) { |
+ offset++; |
+ } |
+ return new Interval(index, index + offset); |
+ } |
+ } |
+ return null; |
+ } |
+ |
+ int start1 = range1.from; |
+ int end1 = range1.to; |
+ int start2 = range2.from; |
+ int end2 = range2.to; |
+ |
+ const int ALIGN1 = -1; |
+ const int UNMATCHED = 0; |
+ const int ALIGN2 = 1; |
+ |
+ while (start1 < end1 && start2 < end2) { |
+ if (match(list1[start1], list2[start2])) { |
+ handleMatched([start1++, start2++]); |
+ } else { |
+ Interval subrange1 = new Interval(start1, end1); |
+ Interval subrange2 = new Interval(start2, end2); |
+ Interval element2inList1 = |
+ findInOther(list1, subrange1, list2, subrange2); |
+ Interval element1inList2 = |
+ findInOther(list2, subrange2, list1, subrange1); |
+ int choice = 0; |
+ if (element2inList1 != null) { |
+ if (element1inList2 != null) { |
+ if (element1inList2.length > 1 && element2inList1.length > 1) { |
+ choice = |
+ element2inList1.from < element1inList2.from ? ALIGN2 : ALIGN1; |
+ } else if (element2inList1.length > 1) { |
+ choice = ALIGN2; |
+ } else if (element1inList2.length > 1) { |
+ choice = ALIGN1; |
+ } else { |
+ choice = |
+ element2inList1.from < element1inList2.from ? ALIGN2 : ALIGN1; |
+ } |
+ } else { |
+ choice = ALIGN2; |
+ } |
+ } else if (element1inList2 != null) { |
+ choice = ALIGN1; |
+ } |
+ switch (choice) { |
+ case ALIGN1: |
+ handleSkew(0, new Interval(start1, element1inList2.from)); |
+ start1 = element1inList2.from; |
+ break; |
+ case ALIGN2: |
+ handleSkew(1, new Interval(start2, element2inList1.from)); |
+ start2 = element2inList1.from; |
+ break; |
+ case UNMATCHED: |
+ handleUnmatched([start1++, start2++]); |
+ break; |
+ } |
+ } |
+ } |
+ if (start1 < end1) { |
+ handleSkew(0, new Interval(start1, end1)); |
+ } |
+ if (start2 < end2) { |
+ handleSkew(1, new Interval(start2, end2)); |
+ } |
+} |
+ |
+/// Create a list of blocks containing the diff of the two output [structures] |
+/// and the corresponding Dart code. |
+List<DiffBlock> createDiffBlocks( |
+ List<OutputStructure> structures, |
+ SourceFileManager sourceFileManager) { |
+ return new DiffCreator(structures, sourceFileManager).computeBlocks(); |
+} |
+ |
+class DiffCreator { |
+ final List<OutputStructure> structures; |
+ final SourceFileManager sourceFileManager; |
+ |
+ List<List<CodeLine>> inputLines; |
+ |
+ List<int> nextInputLine = [0, 0]; |
+ |
+ List<DiffBlock> blocks = <DiffBlock>[]; |
+ |
+ DiffCreator(List<OutputStructure> structures, this.sourceFileManager) |
+ : this.structures = structures, |
+ this.inputLines = structures.map((s) => s.lines).toList(); |
+ |
+ CodeSource codeSourceFromEntities(Iterable<OutputEntity> entities) { |
+ for (OutputEntity entity in entities) { |
+ if (entity.codeSource != null) { |
+ return entity.codeSource; |
+ } |
+ } |
+ return null; |
+ } |
+ |
+ /// Checks that lines are added in sequence without gaps or duplicates. |
+ void checkLineInvariant(int index, Interval range) { |
+ int expectedLineNo = nextInputLine[index]; |
+ if (range.from != expectedLineNo) { |
+ print('Expected line no $expectedLineNo, found ${range.from}'); |
+ if (range.from < expectedLineNo) { |
+ print('Duplicate lines:'); |
+ int i = range.from; |
+ while (i <= expectedLineNo) { |
+ print(inputLines[index][i++].code); |
+ } |
+ } else { |
+ print('Missing lines:'); |
+ int i = expectedLineNo; |
+ while (i <= range.from) { |
+ print(inputLines[index][i++].code); |
+ } |
+ } |
+ } |
+ nextInputLine[index] = range.to; |
+ } |
+ |
+ /// Creates a block containing the code lines in [range] from input number |
+ /// [index]. If [codeSource] is provided, the block will contain a |
+ /// corresponding Dart code column. |
+ void handleSkew(int index, Interval range, [CodeSource codeSource]) { |
+ DiffBlock block = new DiffBlock(DiffKind.UNMATCHED); |
+ checkLineInvariant(index, range); |
+ block.addColumn(index, inputLines[index].sublist(range.from, range.to)); |
+ if (codeSource != null) { |
+ block.addColumn(2, |
+ codeLinesFromCodeSource(sourceFileManager, codeSource)); |
+ } |
+ blocks.add(block); |
+ } |
+ |
+ /// Create a block containing the code lines in [ranges] from the |
+ /// corresponding JavaScript inputs. If [codeSource] is provided, the block |
+ /// will contain a corresponding Dart code column. |
+ void addLines(DiffKind kind, List<Interval> ranges, [CodeSource codeSource]) { |
+ DiffBlock block = new DiffBlock(kind); |
+ for (int i = 0; i < ranges.length; i++) { |
+ checkLineInvariant(i, ranges[i]); |
+ block.addColumn(i, inputLines[i].sublist(ranges[i].from, ranges[i].to)); |
+ } |
+ if (codeSource != null) { |
+ block.addColumn(2, |
+ codeLinesFromCodeSource(sourceFileManager, codeSource)); |
+ } |
+ blocks.add(block); |
+ } |
+ |
+ /// Merge the code lines in [range1] and [range2] of the corresponding input. |
+ void addRaw(Interval range1, Interval range2) { |
+ match(a, b) => a.code == b.code; |
+ |
+ List<Interval> currentMatchedIntervals; |
+ List<Interval> currentUnmatchedIntervals; |
+ |
+ void flushMatching() { |
+ if (currentMatchedIntervals != null) { |
+ addLines(DiffKind.IDENTICAL, currentMatchedIntervals); |
+ } |
+ currentMatchedIntervals = null; |
+ } |
+ |
+ void flushUnmatched() { |
+ if (currentUnmatchedIntervals != null) { |
+ addLines(DiffKind.UNMATCHED, currentUnmatchedIntervals); |
+ } |
+ currentUnmatchedIntervals = null; |
+ } |
+ |
+ List<Interval> updateIntervals(List<Interval> current, List<int> indices) { |
+ if (current == null) { |
+ return [ |
+ new Interval(indices[0], indices[0] + 1), |
+ new Interval(indices[1], indices[1] + 1)]; |
+ } else { |
+ current[0] = |
+ new Interval(current[0].from, indices[0] + 1); |
+ current[1] = |
+ new Interval(current[1].from, indices[1] + 1); |
+ return current; |
+ } |
+ } |
+ |
+ align( |
+ inputLines[0], |
+ inputLines[1], |
+ range1: range1, |
+ range2: range2, |
+ match: match, |
+ handleSkew: (int listIndex, Interval range) { |
+ flushMatching(); |
+ flushUnmatched(); |
+ handleSkew(listIndex, range); |
+ }, |
+ handleMatched: (List<int> indices) { |
+ flushUnmatched(); |
+ currentMatchedIntervals = |
+ updateIntervals(currentMatchedIntervals, indices); |
+ }, |
+ handleUnmatched: (List<int> indices) { |
+ flushMatching(); |
+ currentUnmatchedIntervals = |
+ updateIntervals(currentUnmatchedIntervals, indices); |
+ }); |
+ |
+ flushMatching(); |
+ flushUnmatched(); |
+ } |
+ |
+ /// Adds the top level blocks in [childRange] for structure [index]. |
+ void addBlock(int index, Interval childRange) { |
+ addSkewedChildren(index, structures[index], childRange); |
+ } |
+ |
+ /// Adds the [entity] from structure [index]. If the [entity] supports child |
+ /// entities, these are process individually. Otherwise the lines from |
+ /// [entity] are added directly. |
+ void addSkewedEntity(int index, OutputEntity entity) { |
+ if (entity.canHaveChildren) { |
+ handleSkew(index, entity.header); |
+ addSkewedChildren( |
+ index, entity, new Interval(0, entity.children.length)); |
+ handleSkew(index, entity.footer); |
+ } else { |
+ handleSkew(index, entity.interval, codeSourceFromEntities([entity])); |
+ } |
+ } |
+ |
+ /// Adds the children of [parent] in [childRange] from structure [index]. |
+ void addSkewedChildren(int index, OutputEntity parent, Interval childRange) { |
+ for (int i = childRange.from; i < childRange.to; i++) { |
+ addSkewedEntity(index, parent.getChild(i)); |
+ } |
+ } |
+ |
+ /// Adds the members of the [classes] aligned. |
+ void addMatchingContainers(List<OutputEntity> classes) { |
+ addLines(DiffKind.MATCHING, classes.map((c) => c.header).toList()); |
+ align(classes[0].children, classes[1].children, |
+ match: (a, b) => a.name == b.name, |
+ handleSkew: (int listIndex, Interval childRange) { |
+ addSkewedChildren(listIndex, classes[listIndex], childRange); |
+ }, |
+ handleMatched: (List<int> indices) { |
+ List<BasicEntity> entities = [ |
+ classes[0].getChild(indices[0]), |
+ classes[1].getChild(indices[1])]; |
+ if (entities.every((e) => e is Statics)) { |
+ addMatchingContainers(entities); |
+ } else { |
+ addLines(DiffKind.MATCHING, |
+ entities.map((e) => e.interval).toList(), |
+ codeSourceFromEntities(entities)); |
+ } |
+ }, |
+ handleUnmatched: (List<int> indices) { |
+ List<Interval> intervals = [ |
+ classes[0].getChild(indices[0]).interval, |
+ classes[1].getChild(indices[1]).interval]; |
+ addLines(DiffKind.UNMATCHED, intervals); |
+ }); |
+ addLines(DiffKind.MATCHING, classes.map((c) => c.footer).toList()); |
+ } |
+ |
+ /// Adds the library blocks in [indices] from the corresponding |
+ /// [OutputStructure]s, aligning their content. |
+ void addMatchingBlocks(List<int> indices) { |
+ List<LibraryBlock> blocks = [ |
+ structures[0].getChild(indices[0]), |
+ structures[1].getChild(indices[1])]; |
+ |
+ addLines(DiffKind.MATCHING, blocks.map((b) => b.header).toList()); |
+ align(blocks[0].children, blocks[1].children, |
+ match: (a, b) => a.name == b.name, |
+ handleSkew: (int listIndex, Interval childRange) { |
+ addSkewedChildren( |
+ listIndex, blocks[listIndex], childRange); |
+ }, |
+ handleMatched: (List<int> indices) { |
+ List<BasicEntity> entities = [ |
+ blocks[0].getChild(indices[0]), |
+ blocks[1].getChild(indices[1])]; |
+ if (entities.every((e) => e is LibraryClass)) { |
+ addMatchingContainers(entities); |
+ } else { |
+ addLines( |
+ DiffKind.MATCHING, |
+ entities.map((e) => e.interval).toList(), |
+ codeSourceFromEntities(entities)); |
+ } |
+ }, |
+ handleUnmatched: (List<int> indices) { |
+ List<Interval> intervals = [ |
+ blocks[0].getChild(indices[0]).interval, |
+ blocks[1].getChild(indices[1]).interval]; |
+ addLines(DiffKind.UNMATCHED, intervals); |
+ }); |
+ addLines(DiffKind.MATCHING, blocks.map((b) => b.footer).toList()); |
+ } |
+ |
+ /// Adds the lines of the blocks in [indices] from the corresponding |
+ /// [OutputStructure]s. |
+ void addUnmatchedBlocks(List<int> indices) { |
+ List<LibraryBlock> blocks = [ |
+ structures[0].getChild(indices[0]), |
+ structures[1].getChild(indices[1])]; |
+ addLines(DiffKind.UNMATCHED, [blocks[0].interval, blocks[1].interval]); |
+ } |
+ |
+ /// Computes the diff blocks for [OutputStructure]s. |
+ List<DiffBlock> computeBlocks() { |
+ addRaw(structures[0].header, structures[1].header); |
+ |
+ align(structures[0].children, |
+ structures[1].children, |
+ match: (a, b) => a.name == b.name, |
+ handleSkew: addBlock, |
+ handleMatched: addMatchingBlocks, |
+ handleUnmatched: addUnmatchedBlocks); |
+ |
+ addRaw(structures[0].footer, structures[1].footer); |
+ |
+ return blocks; |
+ } |
+} |
+ |
+/// Creates html lines for code lines in [codeSource]. [sourceFileManager] is |
+/// used to read that text from the source URIs. |
+List<HtmlPart> codeLinesFromCodeSource( |
+ SourceFileManager sourceFileManager, |
+ CodeSource codeSource) { |
+ List<HtmlPart> lines = <HtmlPart>[]; |
+ SourceFile sourceFile = sourceFileManager.getSourceFile(codeSource.uri); |
+ String elementName = codeSource.name; |
+ HtmlLine line = new HtmlLine(); |
+ line.htmlParts.add(new ConstHtmlPart('<span class="comment">')); |
+ line.htmlParts.add(new HtmlText( |
+ '${elementName}: ${sourceFile.filename}')); |
+ line.htmlParts.add(new ConstHtmlPart('</span>')); |
+ lines.add(line); |
+ if (codeSource.begin != null) { |
+ int startLine = sourceFile.getLine(codeSource.begin); |
+ int endLine = sourceFile.getLine(codeSource.end); |
+ for (int lineNo = startLine; lineNo <= endLine; lineNo++) { |
+ String text = sourceFile.getLineText(lineNo); |
+ CodeLine codeLine = new CodeLine(lineNo, sourceFile.getOffset(lineNo, 0)); |
+ codeLine.codeBuffer.write(text); |
+ codeLine.htmlParts.add(new HtmlText(text)); |
+ lines.add(codeLine); |
+ } |
+ } |
+ return lines; |
+} |