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 |
index b689360c5271bba122aab5981182e099295287e4..c58a21edaddaa94eb75f99cb5963b52b54bce5c8 100644 |
--- a/tests/compiler/dart2js/sourcemaps/diff_view.dart |
+++ b/tests/compiler/dart2js/sourcemaps/diff_view.dart |
@@ -1,40 +1,61 @@ |
-// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
+// 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_view; |
import 'dart:async'; |
+import 'dart:convert'; |
import 'dart:io'; |
+ |
import 'package:compiler/src/commandline_options.dart'; |
import 'package:compiler/src/diagnostics/invariant.dart'; |
+import 'package:compiler/src/elements/elements.dart'; |
import 'package:compiler/src/io/position_information.dart'; |
+import 'package:compiler/src/io/source_information.dart'; |
+import 'package:compiler/src/io/source_file.dart'; |
import 'package:compiler/src/js/js.dart' as js; |
+import 'package:compiler/src/js/js_debug.dart'; |
+ |
+import 'diff.dart'; |
+import 'html_parts.dart'; |
+import 'js_tracer.dart'; |
+import 'output_structure.dart'; |
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;'; |
+const String WITH_SOURCE_INFO_STYLE = 'border: solid 1px #FF8080;'; |
+const String WITHOUT_SOURCE_INFO_STYLE = 'background-color: #8080FF;'; |
+const String ADDITIONAL_SOURCE_INFO_STYLE = 'border: solid 1px #80FF80;'; |
+const String UNUSED_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]; |
+ List<List<String>> optionSegments = [currentOptions]; |
+ Map<int, String> loadFrom = {}; |
+ Map<int, String> saveTo = {}; |
int argGroup = 0; |
bool addAnnotations = true; |
for (String arg in args) { |
if (arg == '--') { |
currentOptions = []; |
- options.add(currentOptions); |
+ optionSegments.add(currentOptions); |
argGroup++; |
} else if (arg == '-h') { |
addAnnotations = false; |
print('Hiding annotations'); |
+ } else if (arg == '-l') { |
+ loadFrom[argGroup] = 'out.js.diff$argGroup.json'; |
+ } else if (arg.startsWith('--load=')) { |
+ loadFrom[argGroup] = arg.substring('--load='.length); |
+ } else if (arg == '-s') { |
+ saveTo[argGroup] = 'out.js.diff$argGroup.json'; |
+ } else if (arg.startsWith('--save=')) { |
+ saveTo[argGroup] = arg.substring('--save='.length); |
} else if (arg.startsWith('-o')) { |
out = arg.substring('-o'.length); |
} else if (arg.startsWith('--out=')) { |
@@ -45,54 +66,184 @@ main(List<String> args) async { |
filename = arg; |
} |
} |
- List<String> commonArguments = options[0]; |
- List<String> options1; |
- List<String> options2; |
- if (options.length == 1) { |
+ List<String> commonArguments = optionSegments[0]; |
+ List<List<String>> options = <List<String>>[]; |
+ if (optionSegments.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) { |
+ options.add([USE_NEW_SOURCE_INFO]..addAll(commonArguments)); |
+ options.add([USE_NEW_SOURCE_INFO, Flags.useCpsIr]..addAll(commonArguments)); |
+ } else if (optionSegments.length == 2) { |
// Use alternative options for the second output column. |
- options1 = commonArguments; |
- options2 = options[1]..addAll(commonArguments); |
+ options.add(commonArguments); |
+ options.add(optionSegments[1]..addAll(commonArguments)); |
} else { |
// Use specific options for both output columns. |
- options1 = options[1]..addAll(commonArguments); |
- options2 = options[2]..addAll(commonArguments); |
+ options.add(optionSegments[1]..addAll(commonArguments)); |
+ options.add(optionSegments[2]..addAll(commonArguments)); |
+ } |
+ |
+ SourceFileManager sourceFileManager = new IOSourceFileManager(Uri.base); |
+ List<AnnotatedOutput> outputs = <AnnotatedOutput>[]; |
+ for (int i = 0; i < 2; i++) { |
+ AnnotatedOutput output; |
+ if (loadFrom.containsKey(i)) { |
+ output = AnnotatedOutput.loadOutput(loadFrom[i]); |
+ } else { |
+ print('Compiling ${options[i].join(' ')} $filename'); |
+ CodeLinesResult result = await computeCodeLines( |
+ options[i], filename, addAnnotations: addAnnotations); |
+ OutputStructure structure = OutputStructure.parse(result.codeLines); |
+ computeEntityCodeSources(result, structure); |
+ output = new AnnotatedOutput( |
+ filename, |
+ options[i], |
+ structure, |
+ result.coverage.getCoverageReport()); |
+ } |
+ if (saveTo.containsKey(i)) { |
+ AnnotatedOutput.saveOutput(output, saveTo[i]); |
+ } |
+ outputs.add(output); |
+ } |
+ |
+ List<DiffBlock> blocks = createDiffBlocks( |
+ outputs.map((o) => o.structure).toList(), |
+ sourceFileManager); |
+ |
+ outputDiffView( |
+ out, outputs, blocks, addAnnotations: addAnnotations); |
+} |
+ |
+/// Attaches [CodeSource]s to the entities in [structure] using the |
+/// element-to-offset in [result]. |
+void computeEntityCodeSources( |
+ CodeLinesResult result, OutputStructure structure) { |
+ result.elementMap.forEach((int line, Element element) { |
+ OutputEntity entity = structure.getEntityForLine(line); |
+ if (entity != null) { |
+ entity.codeSource = codeSourceFromElement(element); |
+ } |
+ }); |
+} |
+ |
+/// The structured output of a compilation. |
+class AnnotatedOutput { |
+ final String filename; |
+ final List<String> options; |
+ final OutputStructure structure; |
+ final String coverage; |
+ |
+ AnnotatedOutput(this.filename, this.options, this.structure, this.coverage); |
+ |
+ List<CodeLine> get codeLines => structure.lines; |
+ |
+ Map toJson() { |
+ return { |
+ 'filename': filename, |
+ 'options': options, |
+ 'structure': structure.toJson(), |
+ 'coverage': coverage, |
+ }; |
+ } |
+ |
+ static AnnotatedOutput fromJson(Map json) { |
+ String filename = json['filename']; |
+ List<String> options = json['options']; |
+ OutputStructure structure = OutputStructure.fromJson(json['structure']); |
+ String coverage = json['coverage']; |
+ return new AnnotatedOutput(filename, options, structure, coverage); |
+ } |
+ |
+ static AnnotatedOutput loadOutput(filename) { |
+ AnnotatedOutput output = AnnotatedOutput.fromJson( |
+ JSON.decode(new File(filename).readAsStringSync())); |
+ print('Output loaded from $filename'); |
+ return output; |
} |
- print('Compiling ${options1.join(' ')} $filename'); |
- CodeLinesResult result1 = await computeCodeLines( |
- options1, filename, addAnnotations: addAnnotations); |
- print('Compiling ${options2.join(' ')} $filename'); |
- CodeLinesResult result2 = await computeCodeLines( |
- options2, filename, addAnnotations: addAnnotations); |
+ static void saveOutput(AnnotatedOutput output, String filename) { |
+ if (filename != null) { |
+ new File(filename).writeAsStringSync( |
+ const JsonEncoder.withIndent(' ').convert(output.toJson())); |
+ print('Output saved in $filename'); |
+ } |
+ } |
+} |
+ |
+void outputDiffView( |
+ String out, |
+ List<AnnotatedOutput> outputs, |
+ List<DiffBlock> blocks, |
+ {bool addAnnotations: true}) { |
+ assert(outputs[0].filename == outputs[1].filename); |
+ bool usePre = true; |
StringBuffer sb = new StringBuffer(); |
sb.write(''' |
<html> |
<head> |
-<title>Diff for $filename</title> |
+<title>Diff for ${outputs[0].filename}</title> |
<style> |
.lineNumber { |
font-size: smaller; |
color: #888; |
} |
+.comment { |
+ font-size: smaller; |
+ color: #888; |
+ font-family: initial; |
+} |
.header { |
position: fixed; |
- width: 50%; |
- background-color: #400000; |
- color: #FFFFFF; |
- height: 20px; |
+ width: 100%; |
+ background-color: #FFFFFF; |
+ left: 0px; |
top: 0px; |
+ height: 42px; |
z-index: 1000; |
} |
+.header-table { |
+ width: 100%; |
+ background-color: #400000; |
+ color: #FFFFFF; |
+ border-spacing: 0px; |
+} |
+.header-column { |
+ width: 34%; |
+} |
+.legend { |
+ padding: 2px; |
+} |
+.table { |
+ position: absolute; |
+ left: 0px; |
+ top: 42px; |
+ width: 100%; |
+ border-spacing: 0px; |
+} |
.cell { |
- max-width:500px; |
- overflow-x:auto; |
- vertical-align:top; |
+ max-width: 500px; |
+ overflow-y: hidden; |
+ vertical-align: top; |
+ border-top: 1px solid #F0F0F0; |
+ border-left: 1px solid #F0F0F0; |
+'''); |
+ if (usePre) { |
+ sb.write(''' |
+ overflow-x: hidden; |
+ white-space: pre-wrap; |
+'''); |
+ } else { |
+ sb.write(''' |
+ overflow-x: hidden; |
+ padding-left: 100px; |
+ text-indent: -100px; |
+'''); |
+ } |
+ sb.write(''' |
+ font-family: monospace; |
+ padding: 0px; |
} |
.corresponding1 { |
background-color: #FFFFE0; |
@@ -106,14 +257,29 @@ main(List<String> args) async { |
.identical2 { |
background-color: #C0E0C0; |
} |
+.line { |
+ padding-left: 7em; |
+ text-indent: -7em; |
+ margin: 0px; |
+} |
+.column0 { |
+} |
+.column1 { |
+} |
+.column2 { |
+} |
</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;left:0px;top:22px;width:100%;height:18px;"> |
+<div class="header"> |
+<table class="header-table"><tr> |
+<td class="header-column">[${outputs[0].options.join(',')}]</td> |
+<td class="header-column">[${outputs[1].options.join(',')}]</td> |
+<td class="header-column">Dart code</td> |
+</tr></table> |
+<div class="legend"> |
<span class="identical1"> </span> |
<span class="identical2"> </span> |
identical blocks |
@@ -121,19 +287,36 @@ main(List<String> args) async { |
<span class="corresponding2"> </span> |
corresponding blocks |
'''); |
+ |
if (addAnnotations) { |
sb.write(''' |
<span style="$WITH_SOURCE_INFO_STYLE"> </span> |
- offset with source information |
+ <span title="'offset with source information' means that source information |
+is available for an offset which is expected to have a source location |
+attached. This offset has source information as intended."> |
+ offset with source information</span> |
<span style="$WITHOUT_SOURCE_INFO_STYLE"> </span> |
- offset without source information |
+ <span title="'offset without source information' means that _no_ source |
+information is available for an offset which was expected to have a source |
+location attached. Source information must be found for this offset."> |
+ offset without source information</span> |
<span style="$ADDITIONAL_SOURCE_INFO_STYLE"> </span> |
- offset with unneeded source information |
+ <span title="'offset with unneeded source information' means that a source |
+location was attached to an offset which was _not_ expected to have a source |
+location attached. The source location should be removed from this offset."> |
+ offset with unneeded source information</span> |
+ <span style="$UNUSED_SOURCE_INFO_STYLE"> </span> |
+ <span title="'offset with unused source information' means that source |
+information is available for an offset which is _not_ expected to have a source |
+location attached. This source information _could_ be used by a parent AST node |
+offset that is an 'offset without source information'."> |
+ offset with unused source information</span> |
'''); |
} |
+ |
sb.write(''' |
-</div> |
-<table style="position:absolute;left:0px;top:40px;width:100%;"><tr> |
+</div></div> |
+<table class="table"> |
'''); |
void addCell(String content) { |
@@ -146,225 +329,60 @@ main(List<String> args) async { |
'''); |
} |
- 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++])); |
+ List<HtmlPrintContext> printContexts = <HtmlPrintContext>[]; |
+ for (int i = 0; i < 2; i++) { |
+ int lineNoWidth; |
+ if (outputs[i].codeLines.isNotEmpty) { |
+ lineNoWidth = '${outputs[i].codeLines.last.lineNo + 1}'.length; |
} |
+ printContexts.add(new HtmlPrintContext(lineNoWidth: lineNoWidth)); |
} |
- /// 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)); |
+ for (DiffBlock block in blocks) { |
+ String className; |
+ switch (block.kind) { |
+ case DiffKind.UNMATCHED: |
+ className = 'cell'; |
+ break; |
+ case DiffKind.MATCHING: |
+ className = 'cell corresponding${alternating ? '1' : '2'}'; |
+ alternating = !alternating; |
+ break; |
+ case DiffKind.IDENTICAL: |
+ className = 'cell identical${alternating ? '1' : '2'}'; |
+ alternating = !alternating; |
+ break; |
} |
- 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); |
+ sb.write('<tr>'); |
+ for (int index = 0; index < 3; index++) { |
+ sb.write('''<td class="$className column$index">'''); |
+ List<HtmlPart> lines = block.getColumn(index); |
+ if (lines.isNotEmpty) { |
+ for (HtmlPart line in lines) { |
+ sb.write('<p class="line">'); |
+ if (index < printContexts.length) { |
+ line.printHtmlOn(sb, printContexts[index]); |
} else { |
- withMatching(() { |
- addBothLines(entities.map((e) => e.interval).toList()); |
- }); |
+ line.printHtmlOn(sb, new HtmlPrintContext()); |
} |
- }, |
- 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]); |
+ sb.write('</p>'); |
+ } |
+ } |
+ sb.write('''</td>'''); |
+ } |
+ sb.write('</tr>'); |
} |
- |
- 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()); |
+ |
+ addCell(outputs[0].coverage); |
+ addCell(outputs[1].coverage); |
sb.write(''' |
-</tr></table> |
+</table> |
</body> |
</html> |
'''); |
@@ -373,515 +391,178 @@ main(List<String> args) async { |
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; |
+class CodeLinesResult { |
+ final List<CodeLine> codeLines; |
+ final Coverage coverage; |
+ final Map<int, Element> elementMap; |
+ final SourceFileManager sourceFileManager; |
- 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)); |
- } |
+ CodeLinesResult(this.codeLines, this.coverage, |
+ this.elementMap, this.sourceFileManager); |
} |
-// 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; |
+/// Compute [CodeLine]s and [Coverage] for [filename] using the given [options]. |
+Future<CodeLinesResult> computeCodeLines( |
+ List<String> options, |
+ String filename, |
+ {bool addAnnotations: true}) async { |
+ SourceMapProcessor processor = new SourceMapProcessor(filename); |
+ SourceMaps sourceMaps = |
+ await processor.process(options, perElement: true, forMain: true); |
- List<OutputEntity> get children; |
+ const int WITH_SOURCE_INFO = 0; |
+ const int WITHOUT_SOURCE_INFO = 1; |
+ const int ADDITIONAL_SOURCE_INFO = 2; |
+ const int UNUSED_SOURCE_INFO = 3; |
- Interval getChildInterval(Interval childIndex) { |
- return new Interval( |
- children[childIndex.from].interval.from, |
- children[childIndex.to - 1].interval.to); |
+ SourceMapInfo info = sourceMaps.mainSourceMapInfo; |
- } |
+ List<CodeLine> codeLines; |
+ Coverage coverage = new Coverage(); |
+ List<Annotation> annotations = <Annotation>[]; |
- OutputEntity getChild(int index) { |
- return children[index]; |
+ void addAnnotation(int id, int offset, String title) { |
+ annotations.add(new Annotation(id, offset, title)); |
} |
-} |
- |
-/// 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; |
- } |
+ String code = info.code; |
+ TraceGraph graph = createTraceGraph(info, coverage); |
+ if (addAnnotations) { |
+ Set<js.Node> mappedNodes = new Set<js.Node>(); |
- 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; |
- } |
+ void addSourceLocations( |
+ int kind, int offset, List<SourceLocation> locations, String prefix) { |
- 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; |
+ addAnnotation(kind, offset, |
+ '${prefix}${locations |
+ .where((l) => l != null) |
+ .map((l) => l.shortText) |
+ .join('\n')}'); |
} |
- 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)); |
- } |
+ bool addSourceLocationsForNode(int kind, js.Node node, String prefix) { |
+ Map<int, List<SourceLocation>> locations = info.nodeMap[node]; |
+ if (locations == null || locations.isEmpty) { |
+ return false; |
} |
- if (current != null) { |
- current.to = end; |
- } |
- return libraryBlocks; |
+ locations.forEach( |
+ (int offset, List<SourceLocation> locations) { |
+ addSourceLocations(kind, offset, locations, |
+ '${prefix}\n${truncate(nodeToString(node), 80)}\n'); |
+ }); |
+ mappedNodes.add(node); |
+ return true; |
} |
- 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); |
+ for (TraceStep step in graph.steps) { |
+ String title = '${step.id}:${step.kind}:${step.offset}'; |
+ if (!addSourceLocationsForNode(WITH_SOURCE_INFO, step.node, title)) { |
+ int offset; |
+ if (options.contains(USE_NEW_SOURCE_INFO)) { |
+ offset = step.offset.subexpressionOffset; |
} else { |
- Match matchValue = TOP_LEVEL_VALUE.firstMatch(line); |
- if (matchValue != null) { |
- next = new BasicEntity(matchValue.group(1), index); |
- } |
+ offset = info.jsCodePositions[step.node].startPosition; |
} |
- } |
- if (next != null) { |
- if (current != null) { |
- current.to = index; |
+ if (offset != null) { |
+ addAnnotation(WITHOUT_SOURCE_INFO, offset, title); |
} |
- 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); |
+ for (js.Node node in info.nodeMap.nodes) { |
+ if (!mappedNodes.contains(node)) { |
+ addSourceLocationsForNode(ADDITIONAL_SOURCE_INFO, node, ''); |
+ } |
} |
+ SourceLocationCollector collector = new SourceLocationCollector(); |
+ info.node.accept(collector); |
+ collector.sourceLocations.forEach( |
+ (js.Node node, List<SourceLocation> locations) { |
+ if (!mappedNodes.contains(node)) { |
+ int offset = info.jsCodePositions[node].startPosition; |
+ addSourceLocations(UNUSED_SOURCE_INFO, offset, locations, ''); |
+ } |
+ }); |
} |
-} |
- |
-/// 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); |
+ StringSourceFile sourceFile = new StringSourceFile.fromName(filename, code); |
+ Map<int, Element> elementMap = <int, Element>{}; |
+ sourceMaps.elementSourceMapInfos.forEach( |
+ (Element element, SourceMapInfo info) { |
+ CodePosition position = info.jsCodePositions[info.node]; |
+ elementMap[sourceFile.getLine(position.startPosition)] = element; |
+ }); |
+ |
+ 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; |
+ } else if (id == UNUSED_SOURCE_INFO) { |
+ return UNUSED_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; |
+ } else if (ids.contains(UNUSED_SOURCE_INFO)) { |
+ return UNUSED_SOURCE_INFO_STYLE; |
+ } |
+ return WITHOUT_SOURCE_INFO_STYLE; |
} |
- } |
- 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); |
- } |
+ )); |
+ return new CodeLinesResult(codeLines, coverage, elementMap, |
+ sourceMaps.sourceFileManager); |
} |
-class CodeLineHtmlPart implements HtmlPart { |
- final CodeLine line; |
- |
- CodeLineHtmlPart(this.line); |
+/// Visitor that computes a map from [js.Node]s to all attached source |
+/// locations. |
+class SourceLocationCollector extends js.BaseVisitor { |
+ Map<js.Node, List<SourceLocation>> sourceLocations = |
+ <js.Node, List<SourceLocation>>{}; |
@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); |
+ visitNode(js.Node node) { |
+ SourceInformation sourceInformation = node.sourceInformation; |
+ if (sourceInformation != null) { |
+ sourceLocations[node] = sourceInformation.sourceLocations; |
} |
+ node.visitChildren(this); |
} |
- 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, |
- {bool addAnnotations: true}) 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); |
- if (addAnnotations) { |
- 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.subexpressionOffset; |
- } 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)); |
- } |
- } |
- } |
+/// Compute a [CodeSource] for source span of [element]. |
+CodeSource codeSourceFromElement(Element element) { |
+ CodeKind kind; |
+ Uri uri; |
+ String name; |
+ int begin; |
+ int end; |
+ if (element.isLibrary) { |
+ LibraryElement library = element; |
+ kind = CodeKind.LIBRARY; |
+ name = library.libraryOrScriptName; |
+ uri = library.entryCompilationUnit.script.resourceUri; |
+ } else if (element.isClass) { |
+ kind = CodeKind.CLASS; |
+ name = element.name; |
+ uri = element.compilationUnit.script.resourceUri; |
+ } else { |
+ AstElement astElement = element.implementation; |
+ kind = CodeKind.MEMBER; |
+ uri = astElement.compilationUnit.script.resourceUri; |
+ name = computeElementNameForSourceMaps(astElement); |
+ if (astElement.hasNode) { |
+ begin = astElement.node.getBeginToken().charOffset; |
+ end = astElement.node.getEndToken().charEnd; |
} |
- 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); |
} |
-} |
+ return new CodeSource(kind, uri, name, begin, end); |
+} |