Index: tests/compiler/dart2js/sourcemaps/diff_view.dart |
diff --git a/tests/compiler/dart2js/sourcemaps/diff_view.dart b/tests/compiler/dart2js/sourcemaps/diff_view.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..fc9eb6197cf3f0dc9f9ec6d388536cdec4bd765d |
--- /dev/null |
+++ b/tests/compiler/dart2js/sourcemaps/diff_view.dart |
@@ -0,0 +1,871 @@ |
+// Copyright (c) 2015, 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_view; |
+ |
+import 'dart:async'; |
+import 'dart:io'; |
+import 'package:compiler/src/commandline_options.dart'; |
+import 'package:compiler/src/diagnostics/invariant.dart'; |
+import 'package:compiler/src/io/position_information.dart'; |
+import 'package:compiler/src/js/js.dart' as js; |
+import 'sourcemap_helper.dart'; |
+import 'sourcemap_html_helper.dart'; |
+import 'trace_graph.dart'; |
+import 'js_tracer.dart'; |
+ |
+const String WITH_SOURCE_INFO_STYLE = 'background-color:#FF8080;'; |
+const String WITHOUT_SOURCE_INFO_STYLE = 'border: solid 1px #FF8080;'; |
+const String ADDITIONAL_SOURCE_INFO_STYLE = 'border: solid 1px #8080FF;'; |
+ |
+main(List<String> args) async { |
+ DEBUG_MODE = true; |
+ String out = 'out.js.diff_view.html'; |
+ String filename; |
+ List<String> currentOptions = []; |
+ List<List<String>> options = [currentOptions]; |
+ int argGroup = 0; |
+ for (String arg in args) { |
+ if (arg == '--') { |
+ currentOptions = []; |
+ options.add(currentOptions); |
+ argGroup++; |
+ } else if (arg.startsWith('-o')) { |
+ out = arg.substring('-o'.length); |
+ } else if (arg.startsWith('--out=')) { |
+ out = arg.substring('--out='.length); |
+ } else if (arg.startsWith('-')) { |
+ currentOptions.add(arg); |
+ } else { |
+ filename = arg; |
+ } |
+ } |
+ List<String> commonArguments = options[0]; |
+ List<String> options1; |
+ List<String> options2; |
+ if (options.length == 1) { |
+ // Use default options; comparing SSA and CPS output using the new |
+ // source information strategy. |
+ options1 = [USE_NEW_SOURCE_INFO]..addAll(commonArguments); |
+ options2 = [USE_NEW_SOURCE_INFO, Flags.useCpsIr]..addAll(commonArguments); |
+ } else if (options.length == 2) { |
+ // Use alternative options for the second output column. |
+ options1 = commonArguments; |
+ options2 = options[1]..addAll(commonArguments); |
+ } else { |
+ // Use specific options for both output columns. |
+ options1 = options[1]..addAll(commonArguments); |
+ options2 = options[2]..addAll(commonArguments); |
+ } |
+ |
+ print('Compiling ${options1.join(' ')} $filename'); |
+ CodeLinesResult result1 = await computeCodeLines(options1, filename); |
+ print('Compiling ${options2.join(' ')} $filename'); |
+ CodeLinesResult result2 = await computeCodeLines(options2, filename); |
+ |
+ StringBuffer sb = new StringBuffer(); |
+ sb.write(''' |
+<html> |
+<head> |
+<title>Diff for $filename</title> |
+<style> |
+.lineNumber { |
+ font-size: smaller; |
+ color: #888; |
+} |
+.header { |
+ position: fixed; |
+ width: 50%; |
+ background-color: #400000; |
+ color: #FFFFFF; |
+ height: 20px; |
+ top: 0px; |
+ z-index: 1000; |
+} |
+.cell { |
+ max-width:500px; |
+ overflow-x:auto; |
+ vertical-align:top; |
+} |
+.corresponding1 { |
+ background-color: #FFFFE0; |
+} |
+.corresponding2 { |
+ background-color: #EFEFD0; |
+} |
+.identical1 { |
+ background-color: #E0F0E0; |
+} |
+.identical2 { |
+ background-color: #C0E0C0; |
+} |
+</style> |
+</head> |
+<body>'''); |
+ |
+ sb.write(''' |
+<div class="header" style="left: 0px;">[${options1.join(',')}]</div> |
+<div class="header" style="right: 0px;">[${options2.join(',')}]</div> |
+<div style="position:absolute;top:22px;width:100%;height:18px;"> |
+ <span class="identical1"> </span> |
+ <span class="identical2"> </span> |
+ identical blocks |
+ <span class="corresponding1"> </span> |
+ <span class="corresponding2"> </span> |
+ corresponding blocks |
+ <span style="$WITH_SOURCE_INFO_STYLE"> </span> |
+ offset with source information |
+ <span style="$WITHOUT_SOURCE_INFO_STYLE"> </span> |
+ offset without source information |
+ <span style="$ADDITIONAL_SOURCE_INFO_STYLE"> </span> |
+ offset with unneeded source information |
+</div> |
+<table style="position:absolute;top:40px;width:100%;"><tr> |
+'''); |
+ |
+ void addCell(String content) { |
+ sb.write(''' |
+<td class="cell"><pre> |
+'''); |
+ sb.write(content); |
+ sb.write(''' |
+</pre></td> |
+'''); |
+ } |
+ |
+ List<OutputStructure> structures = [ |
+ OutputStructure.parse(result1.codeLines), |
+ OutputStructure.parse(result2.codeLines)]; |
+ List<List<CodeLine>> inputLines = [result1.codeLines, result2.codeLines]; |
+ List<List<HtmlPart>> outputLines = [<HtmlPart>[], <HtmlPart>[]]; |
+ |
+ /// Marker to alternate output colors. |
+ bool alternating = false; |
+ |
+ /// Enable 'corresponding' background colors for [f]. |
+ void withMatching(f()) { |
+ HtmlPart start = new ConstHtmlPart( |
+ '<div class="corresponding${alternating ? '1' : '2'}">'); |
+ HtmlPart end = new ConstHtmlPart('</div>'); |
+ alternating = !alternating; |
+ outputLines[0].add(start); |
+ outputLines[1].add(start); |
+ f(); |
+ outputLines[0].add(end); |
+ outputLines[1].add(end); |
+ } |
+ |
+ /// Enable 'identical' background colors for [f]. |
+ void withIdentical(f()) { |
+ HtmlPart start = new ConstHtmlPart( |
+ '<div class="identical${alternating ? '1' : '2'}">'); |
+ HtmlPart end = new ConstHtmlPart('</div>'); |
+ alternating = !alternating; |
+ outputLines[0].add(start); |
+ outputLines[1].add(start); |
+ f(); |
+ outputLines[0].add(end); |
+ outputLines[1].add(end); |
+ } |
+ |
+ /// Output code lines in [range] from input number [index], padding the other |
+ /// column with empty lines. |
+ void handleSkew(int index, Interval range) { |
+ int from = range.from; |
+ while (from < range.to) { |
+ outputLines[1 - index].add(const ConstHtmlPart('\n')); |
+ outputLines[index].add( |
+ new CodeLineHtmlPart(inputLines[index][from++])); |
+ } |
+ } |
+ |
+ /// Output code lines of the [indices] from the corresponding inputs. |
+ void addBoth(List<int> indices) { |
+ outputLines[0].add(new CodeLineHtmlPart(inputLines[0][indices[0]])); |
+ outputLines[1].add(new CodeLineHtmlPart(inputLines[1][indices[1]])); |
+ } |
+ |
+ /// Output code lines of the [ranges] from the corresponding inputs. |
+ void addBothLines(List<Interval> ranges) { |
+ Interval range1 = ranges[0]; |
+ Interval range2 = ranges[1]; |
+ int offset = 0; |
+ while (range1.from + offset < range1.to && |
+ range2.from + offset < range2.to) { |
+ addBoth([range1.from + offset, range2.from + offset]); |
+ offset++; |
+ } |
+ if (range1.from + offset < range1.to) { |
+ handleSkew(0, new Interval(range1.from + offset, range1.to)); |
+ } |
+ if (range2.from + offset < range2.to) { |
+ handleSkew(1, new Interval(range2.from + offset, range2.to)); |
+ } |
+ } |
+ |
+ /// 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; |
+ |
+ void flushMatching() { |
+ if (currentMatchedIntervals != null) { |
+ withIdentical(() { |
+ addBothLines(currentMatchedIntervals); |
+ }); |
+ } |
+ currentMatchedIntervals = null; |
+ } |
+ |
+ align( |
+ inputLines[0], |
+ inputLines[1], |
+ range1: range1, |
+ range2: range2, |
+ match: match, |
+ handleSkew: (int listIndex, Interval range) { |
+ flushMatching(); |
+ handleSkew(listIndex, range); |
+ }, |
+ handleMatched: (List<int> indices) { |
+ if (currentMatchedIntervals == null) { |
+ currentMatchedIntervals = [ |
+ new Interval(indices[0], indices[0] + 1), |
+ new Interval(indices[1], indices[1] + 1)]; |
+ } else { |
+ currentMatchedIntervals[0] = |
+ new Interval(currentMatchedIntervals[0].from, indices[0] + 1); |
+ currentMatchedIntervals[1] = |
+ new Interval(currentMatchedIntervals[1].from, indices[1] + 1); |
+ } |
+ }, |
+ handleUnmatched: (List<int> indices) { |
+ flushMatching(); |
+ addBoth(indices); |
+ }); |
+ |
+ flushMatching(); |
+ } |
+ |
+ /// Output the lines of the library blocks in [childRange] in |
+ /// `structures[index]`, padding the other column with empty lines. |
+ void addBlock(int index, Interval childRange) { |
+ handleSkew(index, structures[index].getChildInterval(childRange)); |
+ } |
+ |
+ /// Output the members of the [classes] aligned. |
+ void addMatchingClasses(List<LibraryClass> classes) { |
+ withMatching(() { |
+ addBothLines(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) { |
+ handleSkew(listIndex, |
+ classes[listIndex].getChildInterval(childRange)); |
+ }, |
+ handleMatched: (List<int> indices) { |
+ List<Interval> intervals = [ |
+ classes[0].getChild(indices[0]).interval, |
+ classes[1].getChild(indices[1]).interval]; |
+ withMatching(() { |
+ addBothLines(intervals); |
+ }); |
+ }, |
+ handleUnmatched: (List<int> indices) { |
+ List<Interval> intervals = [ |
+ classes[0].getChild(indices[0]).interval, |
+ classes[1].getChild(indices[1]).interval]; |
+ addBothLines(intervals); |
+ }); |
+ withMatching(() { |
+ addBothLines(classes.map((c) => c.footer).toList()); |
+ }); |
+ } |
+ |
+ /// Output 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])]; |
+ |
+ withMatching(() { |
+ addBothLines(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) { |
+ handleSkew(listIndex, blocks[listIndex].getChildInterval(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)) { |
+ addMatchingClasses(entities); |
+ } else { |
+ withMatching(() { |
+ addBothLines(entities.map((e) => e.interval).toList()); |
+ }); |
+ } |
+ }, |
+ handleUnmatched: (List<int> indices) { |
+ List<Interval> intervals = [ |
+ blocks[0].getChild(indices[0]).interval, |
+ blocks[1].getChild(indices[1]).interval]; |
+ addBothLines(intervals); |
+ }); |
+ withMatching(() { |
+ addBothLines(blocks.map((b) => b.footer).toList()); |
+ }); |
+ } |
+ |
+ /// Output 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])]; |
+ addBothLines([blocks[0].interval, blocks[1].interval]); |
+ } |
+ |
+ |
+ 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); |
+ |
+ addCell(htmlPartsToString(outputLines[0], inputLines[0])); |
+ addCell(htmlPartsToString(outputLines[1], inputLines[1])); |
+ |
+ sb.write('''</tr><tr>'''); |
+ addCell(result1.coverage.getCoverageReport()); |
+ addCell(result2.coverage.getCoverageReport()); |
+ |
+ sb.write(''' |
+</tr></table> |
+</body> |
+</html> |
+'''); |
+ |
+ new File(out).writeAsStringSync(sb.toString()); |
+ print('Diff generated in $out'); |
+} |
+ |
+/// 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)); |
+ } |
+} |
+ |
+// Constants used to identify the subsection of the JavaScript output. These |
+// are specifically for the unminified full_emitter output. |
+const String HEAD = ' var dart = ['; |
+const String TAIL = ' }], '; |
+const String END = ' setupProgram(dart'; |
+ |
+final RegExp TOP_LEVEL_VALUE = new RegExp(r'^ (".+?"):'); |
+final RegExp TOP_LEVEL_FUNCTION = |
+ new RegExp(r'^ ([a-zA-Z0-9_$]+): \[?function'); |
+final RegExp TOP_LEVEL_CLASS = new RegExp(r'^ ([a-zA-Z0-9_$]+): \[?\{'); |
+ |
+final RegExp MEMBER_VALUE = new RegExp(r'^ (".+?"):'); |
+final RegExp MEMBER_FUNCTION = |
+ new RegExp(r'^ ([a-zA-Z0-9_$]+): \[?function'); |
+final RegExp MEMBER_OBJECT = new RegExp(r'^ ([a-zA-Z0-9_$]+): \[?\{'); |
+ |
+/// Subrange of the JavaScript output. |
+abstract class OutputEntity { |
+ Interval get interval; |
+ Interval get header; |
+ Interval get footer; |
+ |
+ List<OutputEntity> get children; |
+ |
+ Interval getChildInterval(Interval childIndex) { |
+ return new Interval( |
+ children[childIndex.from].interval.from, |
+ children[childIndex.to - 1].interval.to); |
+ |
+ } |
+ |
+ OutputEntity getChild(int index) { |
+ return children[index]; |
+ } |
+} |
+ |
+/// The whole JavaScript output. |
+class OutputStructure extends OutputEntity { |
+ final List<CodeLine> lines; |
+ final int headerEnd; |
+ final int footerStart; |
+ final List<LibraryBlock> children; |
+ |
+ OutputStructure( |
+ this.lines, |
+ this.headerEnd, |
+ this.footerStart, |
+ this.children); |
+ |
+ Interval get interval => new Interval(0, lines.length); |
+ |
+ Interval get header => new Interval(0, headerEnd); |
+ |
+ Interval get footer => new Interval(footerStart, lines.length); |
+ |
+ /// Compute the structure of the JavaScript [lines]. |
+ static OutputStructure parse(List<CodeLine> lines) { |
+ |
+ int findHeaderStart(List<CodeLine> lines) { |
+ int index = 0; |
+ for (CodeLine line in lines) { |
+ if (line.code.startsWith(HEAD)) { |
+ return index; |
+ } |
+ index++; |
+ } |
+ return lines.length; |
+ } |
+ |
+ int findHeaderEnd(int start, List<CodeLine> lines) { |
+ int index = start; |
+ for (CodeLine line in lines.skip(start)) { |
+ if (line.code.startsWith(END)) { |
+ return index; |
+ } |
+ index++; |
+ } |
+ return lines.length; |
+ } |
+ |
+ String readHeader(CodeLine line) { |
+ String code = line.code; |
+ String ssaLineHeader; |
+ if (code.startsWith(HEAD)) { |
+ return code.substring(HEAD.length); |
+ } else if (code.startsWith(TAIL)) { |
+ return code.substring(TAIL.length); |
+ } |
+ return null; |
+ } |
+ |
+ List<LibraryBlock> computeHeaderMap( |
+ List<CodeLine> lines, int start, int end) { |
+ List<LibraryBlock> libraryBlocks = <LibraryBlock>[]; |
+ LibraryBlock current; |
+ for (int index = start; index < end; index++) { |
+ String header = readHeader(lines[index]); |
+ if (header != null) { |
+ if (current != null) { |
+ current.to = index; |
+ } |
+ libraryBlocks.add(current = new LibraryBlock(header, index)); |
+ } |
+ } |
+ if (current != null) { |
+ current.to = end; |
+ } |
+ return libraryBlocks; |
+ } |
+ |
+ int headerEnd = findHeaderStart(lines); |
+ int footerStart = findHeaderEnd(headerEnd, lines); |
+ List<LibraryBlock> libraryBlocks = |
+ computeHeaderMap(lines, headerEnd, footerStart); |
+ for (LibraryBlock block in libraryBlocks) { |
+ block.preprocess(lines); |
+ } |
+ |
+ return new OutputStructure( |
+ lines, headerEnd, footerStart, libraryBlocks); |
+ } |
+} |
+ |
+abstract class AbstractEntity extends OutputEntity { |
+ final String name; |
+ final int from; |
+ int to; |
+ |
+ AbstractEntity(this.name, this.from); |
+ |
+ Interval get interval => new Interval(from, to); |
+} |
+ |
+/// A block defining the content of a Dart library. |
+class LibraryBlock extends AbstractEntity { |
+ List<BasicEntity> children = <BasicEntity>[]; |
+ int get headerEnd => from + 2; |
+ int get footerStart => to - 1; |
+ |
+ LibraryBlock(String name, int from) : super(name, from); |
+ |
+ Interval get header => new Interval(from, headerEnd); |
+ |
+ Interval get footer => new Interval(footerStart, to); |
+ |
+ void preprocess(List<CodeLine> lines) { |
+ int index = headerEnd; |
+ BasicEntity current; |
+ while (index < footerStart) { |
+ String line = lines[index].code; |
+ BasicEntity next; |
+ Match matchFunction = TOP_LEVEL_FUNCTION.firstMatch(line); |
+ if (matchFunction != null) { |
+ next = new BasicEntity(matchFunction.group(1), index); |
+ } else { |
+ Match matchClass = TOP_LEVEL_CLASS.firstMatch(line); |
+ if (matchClass != null) { |
+ next = new LibraryClass(matchClass.group(1), index); |
+ } else { |
+ Match matchValue = TOP_LEVEL_VALUE.firstMatch(line); |
+ if (matchValue != null) { |
+ next = new BasicEntity(matchValue.group(1), index); |
+ } |
+ } |
+ } |
+ if (next != null) { |
+ if (current != null) { |
+ current.to = index; |
+ } |
+ children.add(current = next); |
+ } else if (index == headerEnd) { |
+ throw 'Failed to match first library block line:\n$line'; |
+ } |
+ |
+ index++; |
+ } |
+ if (current != null) { |
+ current.to = footerStart; |
+ } |
+ |
+ for (BasicEntity entity in children) { |
+ entity.preprocess(lines); |
+ } |
+ } |
+} |
+ |
+/// A simple member of a library or class. |
+class BasicEntity extends AbstractEntity { |
+ BasicEntity(String name, int from) : super(name, from); |
+ |
+ Interval get header => new Interval(from, to); |
+ |
+ Interval get footer => new Interval(to, to); |
+ |
+ List<OutputEntity> get children => const <OutputEntity>[]; |
+ |
+ void preprocess(List<CodeLine> lines) {} |
+} |
+ |
+/// A block defining a Dart class. |
+class LibraryClass extends BasicEntity { |
+ List<BasicEntity> children = <BasicEntity>[]; |
+ int get headerEnd => from + 1; |
+ int get footerStart => to - 1; |
+ |
+ LibraryClass(String name, int from) : super(name, from); |
+ |
+ Interval get header => new Interval(from, headerEnd); |
+ |
+ Interval get footer => new Interval(footerStart, to); |
+ |
+ void preprocess(List<CodeLine> lines) { |
+ int index = headerEnd; |
+ BasicEntity current; |
+ while (index < footerStart) { |
+ String line = lines[index].code; |
+ BasicEntity next; |
+ Match matchFunction = MEMBER_FUNCTION.firstMatch(line); |
+ if (matchFunction != null) { |
+ next = new BasicEntity(matchFunction.group(1), index); |
+ } else { |
+ Match matchClass = MEMBER_OBJECT.firstMatch(line); |
+ if (matchClass != null) { |
+ next = new BasicEntity(matchClass.group(1), index); |
+ } else { |
+ Match matchValue = MEMBER_VALUE.firstMatch(line); |
+ if (matchValue != null) { |
+ next = new BasicEntity(matchValue.group(1), index); |
+ } |
+ } |
+ } |
+ if (next != null) { |
+ if (current != null) { |
+ current.to = index; |
+ } |
+ children.add(current = next); |
+ } else if (index == headerEnd) { |
+ throw 'Failed to match first library block line:\n$line'; |
+ } |
+ |
+ index++; |
+ } |
+ if (current != null) { |
+ current.to = footerStart; |
+ } |
+ } |
+} |
+ |
+class Interval { |
+ final int from; |
+ final int to; |
+ |
+ const Interval(this.from, this.to); |
+ |
+ int get length => to - from; |
+} |
+ |
+class HtmlPart { |
+ void printHtmlOn(StringBuffer buffer) {} |
+} |
+ |
+class ConstHtmlPart implements HtmlPart { |
+ final String html; |
+ |
+ const ConstHtmlPart(this.html); |
+ |
+ @override |
+ void printHtmlOn(StringBuffer buffer) { |
+ buffer.write(html); |
+ } |
+} |
+ |
+class CodeLineHtmlPart implements HtmlPart { |
+ final CodeLine line; |
+ |
+ CodeLineHtmlPart(this.line); |
+ |
+ @override |
+ void printHtmlOn(StringBuffer buffer, [int lineNoWidth]) { |
+ line.printHtmlOn(buffer, lineNoWidth); |
+ } |
+} |
+ |
+/// Convert [parts] to an HTML string while checking invariants for [lines]. |
+String htmlPartsToString(List<HtmlPart> parts, List<CodeLine> lines) { |
+ int lineNoWidth; |
+ if (lines.isNotEmpty) { |
+ lineNoWidth = '${lines.last.lineNo + 1}'.length; |
+ } |
+ StringBuffer buffer = new StringBuffer(); |
+ int expectedLineNo = 0; |
+ for (HtmlPart part in parts) { |
+ if (part is CodeLineHtmlPart) { |
+ if (part.line.lineNo != expectedLineNo) { |
+ print('Expected line no $expectedLineNo, found ${part.line.lineNo}'); |
+ if (part.line.lineNo < expectedLineNo) { |
+ print('Duplicate lines:'); |
+ int index = part.line.lineNo; |
+ while (index <= expectedLineNo) { |
+ print(lines[index++].code); |
+ } |
+ } else { |
+ print('Missing lines:'); |
+ int index = expectedLineNo; |
+ while (index <= part.line.lineNo) { |
+ print(lines[index++].code); |
+ } |
+ } |
+ expectedLineNo = part.line.lineNo; |
+ } |
+ expectedLineNo++; |
+ part.printHtmlOn(buffer, lineNoWidth); |
+ } else { |
+ part.printHtmlOn(buffer); |
+ } |
+ } |
+ return buffer.toString(); |
+} |
+ |
+class CodeLinesResult { |
+ final List<CodeLine> codeLines; |
+ final Coverage coverage; |
+ |
+ CodeLinesResult(this.codeLines, this.coverage); |
+} |
+ |
+/// Compute [CodeLine]s and [Coverage] for [filename] using the given [options]. |
+Future<CodeLinesResult> computeCodeLines( |
+ List<String> options, |
+ String filename) async { |
+ SourceMapProcessor processor = new SourceMapProcessor(filename); |
+ List<SourceMapInfo> sourceMapInfoList = |
+ await processor.process(options, perElement: false); |
+ |
+ const int WITH_SOURCE_INFO = 0; |
+ const int WITHOUT_SOURCE_INFO = 1; |
+ const int ADDITIONAL_SOURCE_INFO = 2; |
+ |
+ for (SourceMapInfo info in sourceMapInfoList) { |
+ if (info.element != null) continue; |
+ |
+ List<CodeLine> codeLines; |
+ Coverage coverage = new Coverage(); |
+ List<Annotation> annotations = <Annotation>[]; |
+ String code = info.code; |
+ TraceGraph graph = createTraceGraph(info, coverage); |
+ Set<js.Node> mappedNodes = new Set<js.Node>(); |
+ for (TraceStep step in graph.steps) { |
+ int offset; |
+ if (options.contains(USE_NEW_SOURCE_INFO)) { |
+ offset = step.offset.codeOffset; |
+ } else { |
+ offset = info.jsCodePositions[step.node].startPosition; |
+ } |
+ if (offset != null) { |
+ int id = step.sourceLocation != null |
+ ? WITH_SOURCE_INFO : WITHOUT_SOURCE_INFO; |
+ annotations.add( |
+ new Annotation(id, offset, null)); |
+ } |
+ } |
+ if (!options.contains(USE_NEW_SOURCE_INFO)) { |
+ for (js.Node node in info.nodeMap.nodes) { |
+ if (!mappedNodes.contains(node)) { |
+ int offset = info.jsCodePositions[node].startPosition; |
+ annotations.add( |
+ new Annotation(ADDITIONAL_SOURCE_INFO, offset, null)); |
+ } |
+ } |
+ } |
+ codeLines = convertAnnotatedCodeToCodeLines( |
+ code, |
+ annotations, |
+ colorScheme: new CustomColorScheme( |
+ single: (int id) { |
+ if (id == WITH_SOURCE_INFO) { |
+ return WITH_SOURCE_INFO_STYLE; |
+ } else if (id == ADDITIONAL_SOURCE_INFO) { |
+ return ADDITIONAL_SOURCE_INFO_STYLE; |
+ } |
+ return WITHOUT_SOURCE_INFO_STYLE; |
+ }, |
+ multi: (List ids) { |
+ if (ids.contains(WITH_SOURCE_INFO)) { |
+ return WITH_SOURCE_INFO_STYLE; |
+ } else if (ids.contains(ADDITIONAL_SOURCE_INFO)) { |
+ return ADDITIONAL_SOURCE_INFO_STYLE; |
+ } |
+ return WITHOUT_SOURCE_INFO_STYLE; |
+ } |
+ )); |
+ return new CodeLinesResult(codeLines, coverage); |
+ } |
+} |