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 c58a21edaddaa94eb75f99cb5963b52b54bce5c8..918cc44f1763b5514ba0f900b67f8f6375b0ac42 100644 |
--- a/tests/compiler/dart2js/sourcemaps/diff_view.dart |
+++ b/tests/compiler/dart2js/sourcemaps/diff_view.dart |
@@ -8,6 +8,7 @@ import 'dart:async'; |
import 'dart:convert'; |
import 'dart:io'; |
+import 'package:compiler/src/common.dart'; |
import 'package:compiler/src/commandline_options.dart'; |
import 'package:compiler/src/diagnostics/invariant.dart'; |
import 'package:compiler/src/elements/elements.dart'; |
@@ -25,11 +26,6 @@ import 'sourcemap_helper.dart'; |
import 'sourcemap_html_helper.dart'; |
import 'trace_graph.dart'; |
-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'; |
@@ -39,14 +35,14 @@ main(List<String> args) async { |
Map<int, String> loadFrom = {}; |
Map<int, String> saveTo = {}; |
int argGroup = 0; |
- bool addAnnotations = true; |
+ bool showAnnotations = true; |
for (String arg in args) { |
if (arg == '--') { |
currentOptions = []; |
optionSegments.add(currentOptions); |
argGroup++; |
} else if (arg == '-h') { |
- addAnnotations = false; |
+ showAnnotations = false; |
print('Hiding annotations'); |
} else if (arg == '-l') { |
loadFrom[argGroup] = 'out.js.diff$argGroup.json'; |
@@ -92,7 +88,7 @@ main(List<String> args) async { |
} else { |
print('Compiling ${options[i].join(' ')} $filename'); |
CodeLinesResult result = await computeCodeLines( |
- options[i], filename, addAnnotations: addAnnotations); |
+ options[i], filename); |
OutputStructure structure = OutputStructure.parse(result.codeLines); |
computeEntityCodeSources(result, structure); |
output = new AnnotatedOutput( |
@@ -112,7 +108,9 @@ main(List<String> args) async { |
sourceFileManager); |
outputDiffView( |
- out, outputs, blocks, addAnnotations: addAnnotations); |
+ out, outputs, blocks, |
+ showMarkers: showAnnotations, |
+ showSourceMapped: showAnnotations); |
} |
/// Attaches [CodeSource]s to the entities in [structure] using the |
@@ -127,6 +125,44 @@ void computeEntityCodeSources( |
}); |
} |
+class CodeLineAnnotationJsonStrategy implements JsonStrategy { |
+ const CodeLineAnnotationJsonStrategy(); |
+ |
+ Map encodeAnnotation(Annotation annotation) { |
+ CodeLineAnnotation data = annotation.data; |
+ return { |
+ 'id': annotation.id, |
+ 'codeOffset': annotation.codeOffset, |
+ 'title': annotation.title, |
+ 'data': data.toJson(this), |
+ }; |
+ } |
+ |
+ Annotation decodeAnnotation(Map json) { |
+ return new Annotation( |
+ json['id'], |
+ json['codeOffset'], |
+ json['title'], |
+ data: CodeLineAnnotation.fromJson(json['data'], this)); |
+ } |
+ |
+ @override |
+ decodeLineAnnotation(json) { |
+ if (json != null) { |
+ return CodeSource.fromJson(json); |
+ } |
+ return null; |
+ } |
+ |
+ @override |
+ encodeLineAnnotation(CodeSource lineAnnotation) { |
+ if (lineAnnotation != null) { |
+ return lineAnnotation.toJson(); |
+ } |
+ return null; |
+ } |
+} |
+ |
/// The structured output of a compilation. |
class AnnotatedOutput { |
final String filename; |
@@ -142,7 +178,7 @@ class AnnotatedOutput { |
return { |
'filename': filename, |
'options': options, |
- 'structure': structure.toJson(), |
+ 'structure': structure.toJson(const CodeLineAnnotationJsonStrategy()), |
'coverage': coverage, |
}; |
} |
@@ -150,7 +186,8 @@ class AnnotatedOutput { |
static AnnotatedOutput fromJson(Map json) { |
String filename = json['filename']; |
List<String> options = json['options']; |
- OutputStructure structure = OutputStructure.fromJson(json['structure']); |
+ OutputStructure structure = OutputStructure.fromJson( |
+ json['structure'], const CodeLineAnnotationJsonStrategy()); |
String coverage = json['coverage']; |
return new AnnotatedOutput(filename, options, structure, coverage); |
} |
@@ -175,7 +212,8 @@ void outputDiffView( |
String out, |
List<AnnotatedOutput> outputs, |
List<DiffBlock> blocks, |
- {bool addAnnotations: true}) { |
+ {bool showMarkers: true, |
+ bool showSourceMapped: true}) { |
assert(outputs[0].filename == outputs[1].filename); |
bool usePre = true; |
@@ -185,16 +223,16 @@ void outputDiffView( |
<head> |
<title>Diff for ${outputs[0].filename}</title> |
<style> |
-.lineNumber { |
+.${ClassNames.lineNumber} { |
font-size: smaller; |
color: #888; |
} |
-.comment { |
+.${ClassNames.comment} { |
font-size: smaller; |
color: #888; |
font-family: initial; |
} |
-.header { |
+.${ClassNames.header} { |
position: fixed; |
width: 100%; |
background-color: #FFFFFF; |
@@ -203,31 +241,39 @@ void outputDiffView( |
height: 42px; |
z-index: 1000; |
} |
-.header-table { |
+.${ClassNames.headerTable} { |
width: 100%; |
background-color: #400000; |
color: #FFFFFF; |
border-spacing: 0px; |
} |
-.header-column { |
- width: 34%; |
+.${ClassNames.headerColumn} { |
} |
-.legend { |
+.${ClassNames.legend} { |
padding: 2px; |
} |
-.table { |
+.${ClassNames.buttons} { |
+ position: fixed; |
+ right: 0px; |
+ top: 0px; |
+ width: 220px; |
+ background-color: #FFFFFF; |
+ border: 1px solid #C0C0C0; |
+ z-index: 2000; |
+} |
+.${ClassNames.table} { |
position: absolute; |
left: 0px; |
top: 42px; |
width: 100%; |
border-spacing: 0px; |
} |
-.cell { |
- max-width: 500px; |
+.${ClassNames.cell}, |
+.${ClassNames.innerCell}, |
+.${ClassNames.originalDart}, |
+.${ClassNames.inlinedDart} { |
overflow-y: hidden; |
vertical-align: top; |
- border-top: 1px solid #F0F0F0; |
- border-left: 1px solid #F0F0F0; |
'''); |
if (usePre) { |
sb.write(''' |
@@ -245,89 +291,191 @@ void outputDiffView( |
font-family: monospace; |
padding: 0px; |
} |
-.corresponding1 { |
+.${ClassNames.cell} { |
+ border-top: 1px solid #F0F0F0; |
+ border-left: 1px solid #C0C0C0; |
+} |
+.${ClassNames.innerCell} { |
+ /*border-top: 1px solid #F8F8F8;*/ |
+ width: 50%; |
+ max-width: 250px; |
+} |
+.${ClassNames.corresponding(false)} { |
background-color: #FFFFE0; |
} |
-.corresponding2 { |
+.${ClassNames.corresponding(true)} { |
background-color: #EFEFD0; |
} |
-.identical1 { |
+.${ClassNames.identical(false)} { |
background-color: #E0F0E0; |
} |
-.identical2 { |
+.${ClassNames.identical(true)} { |
background-color: #C0E0C0; |
} |
-.line { |
+.${ClassNames.line} { |
padding-left: 7em; |
text-indent: -7em; |
margin: 0px; |
} |
-.column0 { |
+.${ClassNames.column(column_js0)} { |
+ max-width: 500px; |
+ width: 500px; |
+} |
+.${ClassNames.column(column_js1)} { |
+ max-width: 500px; |
+ width: 500px; |
+} |
+.${ClassNames.column(column_dart)} { |
+ max-width: 300px; |
+ width: 300px; |
+} |
+.${ClassNames.colored(0)} { |
+ color: #FF0000; |
+} |
+.${ClassNames.colored(1)} { |
+ color: #C0C000; |
+} |
+.${ClassNames.colored(2)} { |
+ color: #008000; |
+} |
+.${ClassNames.colored(3)} { |
+ color: #00C0C0; |
+} |
+.${ClassNames.withSourceInfo} { |
+ border: solid 1px #FF8080; |
+} |
+.${ClassNames.withoutSourceInfo} { |
+ background-color: #8080FF; |
} |
-.column1 { |
+.${ClassNames.additionalSourceInfo} { |
+ border: solid 1px #80FF80; |
} |
-.column2 { |
+.${ClassNames.unusedSourceInfo} { |
+ border: solid 1px #8080FF; |
+} |
+.${ClassNames.originalDart} { |
+} |
+.${ClassNames.inlinedDart} { |
+} |
+'''); |
+ for (int i = 0; i < HUE_COUNT; i++) { |
+ sb.write(''' |
+.${ClassNames.sourceMappingIndex(i)} { |
+ background-color: ${toColorCss(i)}; |
+} |
+'''); |
+ } |
+ sb.write(''' |
+.${ClassNames.sourceMapped} { |
+ ${showSourceMapped ? '' : 'display: none;'} |
+} |
+.${ClassNames.sourceMapping} { |
+ ${showSourceMapped ? '' : 'border: 0px;'} |
+ ${showSourceMapped ? '' : 'background-color: transparent;'} |
+} |
+.${ClassNames.markers} { |
+ ${showMarkers ? '' : 'display: none;'} |
+} |
+.${ClassNames.marker} { |
+ ${showMarkers ? '' : 'border: 0px;'} |
+ ${showMarkers ? '' : 'background-color: transparent;'} |
} |
</style> |
+<script> |
+function isChecked(name) { |
+ var box = document.getElementById('box-' + name); |
+ return box.checked; |
+} |
+function toggleDisplay(name) { |
+ var checked = isChecked(name); |
+ var styleSheet = document.styleSheets[0]; |
+ for (var index = 0; index < styleSheet.cssRules.length; index++) { |
+ var cssRule = styleSheet.cssRules[index]; |
+ if (cssRule.selectorText == '.' + name) { |
+ if (checked) { |
+ cssRule.style.removeProperty('display'); |
+ } else { |
+ cssRule.style.display = 'none'; |
+ } |
+ } |
+ } |
+ return checked; |
+} |
+function toggle${ClassNames.sourceMapped}() { |
+ var checked = toggleDisplay('${ClassNames.sourceMapped}'); |
+ toggleAnnotations(checked, '${ClassNames.sourceMapping}'); |
+} |
+function toggle${ClassNames.markers}() { |
+ var checked = toggleDisplay('${ClassNames.markers}'); |
+ toggleAnnotations(checked, '${ClassNames.marker}'); |
+} |
+function toggleAnnotations(show, name) { |
+ var styleSheet = document.styleSheets[0]; |
+ for (var index = 0; index < styleSheet.cssRules.length; index++) { |
+ var cssRule = styleSheet.cssRules[index]; |
+ if (cssRule.selectorText == '.' + name) { |
+ if (show) { |
+ cssRule.style.removeProperty('border'); |
+ cssRule.style.removeProperty('background-color'); |
+ } else { |
+ cssRule.style.border = '0px'; |
+ cssRule.style.backgroundColor = 'transparent'; |
+ } |
+ } |
+ } |
+} |
+</script> |
</head> |
<body>'''); |
sb.write(''' |
-<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> |
+<div class="${ClassNames.header}"> |
+<div class="${ClassNames.legend}"> |
+ <span class="${ClassNames.identical(false)}"> </span> |
+ <span class="${ClassNames.identical(true)}"> </span> |
identical blocks |
- <span class="corresponding1"> </span> |
- <span class="corresponding2"> </span> |
+ <span class="${ClassNames.corresponding(false)}"> </span> |
+ <span class="${ClassNames.corresponding(true)}"> </span> |
corresponding blocks |
'''); |
- if (addAnnotations) { |
- sb.write(''' |
- <span style="$WITH_SOURCE_INFO_STYLE"> </span> |
+ sb.write(''' |
+ <span class="${ClassNames.markers}"> |
+ <span class="${ClassNames.withSourceInfo}"> </span> |
<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> |
+ <span class="${ClassNames.withoutSourceInfo}"> </span> |
<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> |
+ <span class="${ClassNames.additionalSourceInfo}"> </span> |
<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 class="${ClassNames.unusedSourceInfo}"> </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> |
+ </span> |
+ <span class="${ClassNames.sourceMapped}"> |
'''); |
+ for (int i = 0; i < HUE_COUNT; i++) { |
+ sb.write(''' |
+<span class="${ClassNames.sourceMappingIndex(i)}"> </span>'''); |
} |
- |
sb.write(''' |
-</div></div> |
-<table class="table"> |
+ <span title="JavaScript offsets and their corresponding Dart Code offset |
+as mapped through source-maps."> |
+ mapped source locations</span> |
+ </span> |
'''); |
- void addCell(String content) { |
- sb.write(''' |
-<td class="cell"><pre> |
-'''); |
- sb.write(content); |
- sb.write(''' |
-</pre></td> |
-'''); |
- } |
/// Marker to alternate output colors. |
bool alternating = false; |
@@ -338,39 +486,73 @@ offset that is an 'offset without source information'."> |
if (outputs[i].codeLines.isNotEmpty) { |
lineNoWidth = '${outputs[i].codeLines.last.lineNo + 1}'.length; |
} |
- printContexts.add(new HtmlPrintContext(lineNoWidth: lineNoWidth)); |
+ printContexts.add(new HtmlPrintContext( |
+ lineNoWidth: lineNoWidth, |
+ getAnnotationData: getAnnotationData, |
+ getLineData: getLineData)); |
+ } |
+ |
+ Set<DiffColumn> allColumns = new Set<DiffColumn>(); |
+ for (DiffBlock block in blocks) { |
+ allColumns.addAll(block.columns); |
+ } |
+ |
+ List<DiffColumn> columns = [column_js0, column_js1, column_dart] |
+ .where((c) => allColumns.contains(c)).toList(); |
+ |
+ sb.write(''' |
+</div> |
+<table class="${ClassNames.headerTable}"><tr>'''); |
+ for (DiffColumn column in columns) { |
+ sb.write(''' |
+<td class="${ClassNames.headerColumn} ${ClassNames.column(column)}">'''); |
+ if (column.type == 'js') { |
+ sb.write('''[${outputs[column.index].options.join(',')}]'''); |
+ } else { |
+ sb.write('''Dart code'''); |
+ } |
+ sb.write('''</td>'''); |
} |
+ sb.write(''' |
+</tr></table> |
+</div> |
+<table class="${ClassNames.table}"> |
+'''); |
+ |
for (DiffBlock block in blocks) { |
String className; |
switch (block.kind) { |
case DiffKind.UNMATCHED: |
- className = 'cell'; |
+ className = '${ClassNames.cell}'; |
break; |
case DiffKind.MATCHING: |
- className = 'cell corresponding${alternating ? '1' : '2'}'; |
+ className = |
+ '${ClassNames.cell} ${ClassNames.corresponding(alternating)}'; |
alternating = !alternating; |
break; |
case DiffKind.IDENTICAL: |
- className = 'cell identical${alternating ? '1' : '2'}'; |
+ className = |
+ '${ClassNames.cell} ${ClassNames.identical(alternating)}'; |
alternating = !alternating; |
break; |
} |
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 { |
- line.printHtmlOn(sb, new HtmlPrintContext()); |
- } |
- sb.write('</p>'); |
- } |
+ for (DiffColumn column in columns) { |
+ sb.write('''<td class="$className ${ClassNames.column(column)}">'''); |
+ HtmlPrintContext context = new HtmlPrintContext( |
+ lineNoWidth: 4, |
+ includeAnnotation: (Annotation annotation) { |
+ CodeLineAnnotation data = annotation.data; |
+ return data.annotationType == AnnotationType.WITH_SOURCE_INFO || |
+ data.annotationType == AnnotationType.ADDITIONAL_SOURCE_INFO; |
+ }, |
+ getAnnotationData: getAnnotationData, |
+ getLineData: getLineData); |
+ if (column.type == 'js') { |
+ context = printContexts[column.index]; |
} |
+ block.printHtmlOn(column, sb, context); |
sb.write('''</td>'''); |
} |
sb.write('</tr>'); |
@@ -378,11 +560,51 @@ offset that is an 'offset without source information'."> |
sb.write('''</tr><tr>'''); |
- addCell(outputs[0].coverage); |
- addCell(outputs[1].coverage); |
+ for (DiffColumn column in columns) { |
+ sb.write(''' |
+<td class="${ClassNames.cell} ${ClassNames.column(column)}"><pre>'''); |
+ if (column.type == 'js') { |
+ sb.write(outputs[column.index].coverage); |
+ } |
+ sb.write('''</td>'''); |
+ } |
sb.write(''' |
</table> |
+<div class="${ClassNames.buttons}"> |
+<input type="checkbox" id="box-${ClassNames.column(column_js0)}" |
+ onclick="javascript:toggleDisplay('${ClassNames.column(column_js0)}')" |
+ checked> |
+Left JavaScript code<br/> |
+ |
+<input type="checkbox" id="box-${ClassNames.column(column_js1)}" |
+ onclick="javascript:toggleDisplay('${ClassNames.column(column_js1)}')" |
+ checked> |
+Right JavaScript code<br/> |
+ |
+<input type="checkbox" id="box-${ClassNames.column(column_dart)}" |
+ onclick="javascript:toggleDisplay('${ClassNames.column(column_dart)}')" |
+ checked> |
+<span title="Show column with Dart code corresponding to the block."> |
+Dart code</span><br/> |
+ |
+<input type="checkbox" id="box-${ClassNames.inlinedDart}" |
+ onclick="javascript:toggleDisplay('${ClassNames.inlinedDart}')" checked> |
+<span title="Show Dart code inlined into the block."> |
+Inlined Dart code</span><br/> |
+ |
+<input type="checkbox" id="box-${ClassNames.markers}" |
+ onclick="javascript:toggle${ClassNames.markers}()" |
+ ${showMarkers ? 'checked' : ''}> |
+<span title="Show markers for JavaScript offsets with source information."> |
+Source information markers</span><br/> |
+ |
+<input type="checkbox" id="box-${ClassNames.sourceMapped}" |
+ onclick="javascript:toggle${ClassNames.sourceMapped}()" |
+ ${showSourceMapped ? 'checked' : ''}> |
+<span title="Show line-per-line mappings of JavaScript to Dart code."> |
+Source mapped Dart code</span><br/> |
+</div> |
</body> |
</html> |
'''); |
@@ -396,95 +618,259 @@ class CodeLinesResult { |
final Coverage coverage; |
final Map<int, Element> elementMap; |
final SourceFileManager sourceFileManager; |
+ final CodeSources codeSources; |
+ |
+ CodeLinesResult( |
+ this.codeLines, |
+ this.coverage, |
+ this.elementMap, |
+ this.sourceFileManager, |
+ this.codeSources); |
+} |
- CodeLinesResult(this.codeLines, this.coverage, |
- this.elementMap, this.sourceFileManager); |
+class CodeSources { |
+ Map<Element, CodeSource> codeSourceMap = <Element, CodeSource>{}; |
+ Map<Uri, Map<Interval, CodeSource>> uriCodeSourceMap = |
+ <Uri, Map<Interval, CodeSource>>{}; |
+ |
+ CodeSources( |
+ SourceMapProcessor processor, |
+ SourceMaps sourceMaps) { |
+ |
+ CodeSource computeCodeSource(Element element) { |
+ return codeSourceMap.putIfAbsent(element, () { |
+ CodeSource codeSource = codeSourceFromElement(element); |
+ if (codeSource.begin != null) { |
+ Interval interval = new Interval(codeSource.begin, codeSource.end); |
+ Map<Interval, CodeSource> intervals = |
+ uriCodeSourceMap[codeSource.uri]; |
+ if (intervals == null) { |
+ intervals = <Interval, CodeSource>{}; |
+ uriCodeSourceMap[codeSource.uri] = intervals; |
+ } else { |
+ for (Interval existingInterval in intervals.keys.toList()) { |
+ if (existingInterval.contains(interval.from)) { |
+ CodeSource existingCodeSource = intervals[existingInterval]; |
+ intervals.remove(existingInterval); |
+ if (existingInterval.from < interval.from) { |
+ Interval preInterval = new Interval( |
+ existingInterval.from, interval.from); |
+ intervals[preInterval] = existingCodeSource; |
+ } |
+ if (interval.to < existingInterval.to) { |
+ Interval postInterval = new Interval( |
+ interval.to, existingInterval.to); |
+ intervals[postInterval] = existingCodeSource; |
+ } |
+ } |
+ } |
+ } |
+ intervals[interval] = codeSource; |
+ } |
+ if (element is ClassElement) { |
+ element.forEachLocalMember((Element member) { |
+ codeSource.members.add(computeCodeSource(member)); |
+ }); |
+ element.implementation.forEachLocalMember((Element member) { |
+ codeSource.members.add(computeCodeSource(member)); |
+ }); |
+ } else if (element is MemberElement) { |
+ element.nestedClosures.forEach((Element closure) { |
+ codeSource.members.add(computeCodeSource(closure)); |
+ }); |
+ } |
+ return codeSource; |
+ }); |
+ } |
+ |
+ for (LibraryElement library in |
+ sourceMaps.compiler.libraryLoader.libraries) { |
+ library.forEachLocalMember(computeCodeSource); |
+ library.implementation.forEachLocalMember(computeCodeSource); |
+ } |
+ |
+ uriCodeSourceMap.forEach((Uri uri, Map<Interval, CodeSource> intervals) { |
+ List<Interval> sortedKeys = intervals.keys.toList()..sort( |
+ (i1, i2) => i1.from.compareTo(i2.from)); |
+ Map<Interval, CodeSource> sortedintervals = <Interval, CodeSource>{}; |
+ sortedKeys.forEach((Interval interval) { |
+ sortedintervals[interval] = intervals[interval]; |
+ }); |
+ uriCodeSourceMap[uri] = sortedintervals; |
+ }); |
+ } |
+ |
+ CodeSource sourceLocationToCodeSource(SourceLocation sourceLocation) { |
+ Map<Interval, CodeSource> intervals = |
+ uriCodeSourceMap[sourceLocation.sourceUri]; |
+ if (intervals == null) { |
+ print('No code source for $sourceLocation(${sourceLocation.offset})'); |
+ print(' -- no intervals for ${sourceLocation.sourceUri}'); |
+ return null; |
+ } |
+ for (Interval interval in intervals.keys) { |
+ if (interval.contains(sourceLocation.offset)) { |
+ return intervals[interval]; |
+ } |
+ } |
+ print('No code source for $sourceLocation(${sourceLocation.offset})'); |
+ intervals.forEach((k, v) => print(' $k: ${v.name}')); |
+ return null; |
+ } |
} |
/// Compute [CodeLine]s and [Coverage] for [filename] using the given [options]. |
Future<CodeLinesResult> computeCodeLines( |
List<String> options, |
- String filename, |
- {bool addAnnotations: true}) async { |
+ String filename) async { |
SourceMapProcessor processor = new SourceMapProcessor(filename); |
SourceMaps sourceMaps = |
await processor.process(options, perElement: true, forMain: true); |
- const int WITH_SOURCE_INFO = 0; |
- const int WITHOUT_SOURCE_INFO = 1; |
- const int ADDITIONAL_SOURCE_INFO = 2; |
- const int UNUSED_SOURCE_INFO = 3; |
+ CodeSources codeSources = new CodeSources(processor, sourceMaps); |
SourceMapInfo info = sourceMaps.mainSourceMapInfo; |
+ int nextAnnotationId = 0; |
List<CodeLine> codeLines; |
Coverage coverage = new Coverage(); |
- List<Annotation> annotations = <Annotation>[]; |
- |
- void addAnnotation(int id, int offset, String title) { |
- annotations.add(new Annotation(id, offset, title)); |
+ Map<int, List<CodeLineAnnotation>> codeLineAnnotationMap = |
+ <int, List<CodeLineAnnotation>>{}; |
+ |
+ /// Create a [CodeLineAnnotation] for [codeOffset]. |
+ void addCodeLineAnnotation( |
+ {AnnotationType annotationType, |
+ int codeOffset, |
+ List<SourceLocation> locations: const <SourceLocation>[], |
+ String stepInfo}) { |
+ if (annotationType == AnnotationType.WITHOUT_SOURCE_INFO || |
+ annotationType == AnnotationType.UNUSED_SOURCE_INFO) { |
+ locations = []; |
+ } |
+ List<CodeLocation> codeLocations = locations |
+ .map((l) => new CodeLocation(l.sourceUri, l.sourceName, l.offset)) |
+ .toList(); |
+ List<CodeSource> codeSourceList = locations |
+ .map(codeSources.sourceLocationToCodeSource) |
+ .where((c) => c != null) |
+ .toList(); |
+ CodeLineAnnotation data = new CodeLineAnnotation( |
+ annotationId: nextAnnotationId++, |
+ annotationType: annotationType, |
+ codeLocations: codeLocations, |
+ codeSources: codeSourceList, |
+ stepInfo: stepInfo); |
+ codeLineAnnotationMap.putIfAbsent( |
+ codeOffset, () => <CodeLineAnnotation>[]).add(data); |
} |
String code = info.code; |
TraceGraph graph = createTraceGraph(info, coverage); |
- if (addAnnotations) { |
- Set<js.Node> mappedNodes = new Set<js.Node>(); |
- void addSourceLocations( |
- int kind, int offset, List<SourceLocation> locations, String prefix) { |
+ Set<js.Node> mappedNodes = new Set<js.Node>(); |
+ |
+ /// Add an annotation for [codeOffset] pointing to [locations]. |
+ void addSourceLocations( |
+ {AnnotationType annotationType, |
+ int codeOffset, |
+ List<SourceLocation> locations, |
+ String stepInfo}) { |
+ locations = locations.where((l) => l != null).toList(); |
+ addCodeLineAnnotation( |
+ annotationType: annotationType, |
+ codeOffset: codeOffset, |
+ stepInfo: stepInfo, |
+ locations: locations); |
+ } |
- addAnnotation(kind, offset, |
- '${prefix}${locations |
- .where((l) => l != null) |
- .map((l) => l.shortText) |
- .join('\n')}'); |
+ /// Add annotations for all mappings created for [node]. |
+ bool addSourceLocationsForNode( |
+ {AnnotationType annotationType, |
+ js.Node node, |
+ String stepInfo}) { |
+ Map<int, List<SourceLocation>> locations = info.nodeMap[node]; |
+ if (locations == null || locations.isEmpty) { |
+ return false; |
} |
+ locations.forEach((int offset, List<SourceLocation> locations) { |
+ addSourceLocations( |
+ annotationType: annotationType, |
+ codeOffset: offset, |
+ locations: locations, |
+ stepInfo: stepInfo); |
+ }); |
+ mappedNodes.add(node); |
+ return true; |
+ } |
- bool addSourceLocationsForNode(int kind, js.Node node, String prefix) { |
- Map<int, List<SourceLocation>> locations = info.nodeMap[node]; |
- if (locations == null || locations.isEmpty) { |
- return false; |
+ // Add annotations based on trace steps. |
+ for (TraceStep step in graph.steps) { |
+ String stepInfo = '${step.id}:${step.kind}:${step.offset}'; |
+ bool added = addSourceLocationsForNode( |
+ annotationType: AnnotationType.WITH_SOURCE_INFO, |
+ node: step.node, |
+ stepInfo: stepInfo); |
+ if (!added) { |
+ int offset; |
+ if (options.contains(USE_NEW_SOURCE_INFO)) { |
+ offset = step.offset.subexpressionOffset; |
+ } else { |
+ offset = info.jsCodePositions[step.node].startPosition; |
+ } |
+ if (offset != null) { |
+ addCodeLineAnnotation( |
+ annotationType: AnnotationType.WITHOUT_SOURCE_INFO, |
+ codeOffset: offset, |
+ stepInfo: stepInfo); |
} |
- locations.forEach( |
- (int offset, List<SourceLocation> locations) { |
- addSourceLocations(kind, offset, locations, |
- '${prefix}\n${truncate(nodeToString(node), 80)}\n'); |
- }); |
- mappedNodes.add(node); |
- return true; |
} |
+ } |
+ // Add additional annotations for mappings created for particular nodes. |
+ for (js.Node node in info.nodeMap.nodes) { |
+ if (!mappedNodes.contains(node)) { |
+ addSourceLocationsForNode( |
+ annotationType: AnnotationType.ADDITIONAL_SOURCE_INFO, |
+ node: node); |
+ } |
+ } |
- 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 { |
- offset = info.jsCodePositions[step.node].startPosition; |
- } |
- if (offset != null) { |
- addAnnotation(WITHOUT_SOURCE_INFO, offset, title); |
- } |
- } |
+ // Add annotations for unused source information associated with nodes. |
+ 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( |
+ annotationType: AnnotationType.UNUSED_SOURCE_INFO, |
+ codeOffset: offset, |
+ locations: locations); |
} |
- for (js.Node node in info.nodeMap.nodes) { |
- if (!mappedNodes.contains(node)) { |
- addSourceLocationsForNode(ADDITIONAL_SOURCE_INFO, node, ''); |
+ }); |
+ |
+ // Assign consecutive ids to source mappings. |
+ int nextSourceMappedLocationIndex = 0; |
+ List<Annotation> annotations = <Annotation>[]; |
+ for (int codeOffset in codeLineAnnotationMap.keys.toList()..sort()) { |
+ bool hasSourceMappedLocation = false; |
+ for (CodeLineAnnotation data in codeLineAnnotationMap[codeOffset]) { |
+ if (data.annotationType.isSourceMapped) { |
+ data.sourceMappingIndex = nextSourceMappedLocationIndex; |
+ hasSourceMappedLocation = true; |
} |
+ annotations.add(new Annotation( |
+ data.annotationType.index, |
+ codeOffset, |
+ 'id=${data.annotationId}', |
+ data: data)); |
+ } |
+ if (hasSourceMappedLocation) { |
+ nextSourceMappedLocationIndex++; |
} |
- 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, ''); |
- } |
- }); |
} |
+ // Associate JavaScript offsets with [Element]s. |
StringSourceFile sourceFile = new StringSourceFile.fromName(filename, code); |
Map<int, Element> elementMap = <int, Element>{}; |
sourceMaps.elementSourceMapInfos.forEach( |
@@ -493,33 +879,11 @@ Future<CodeLinesResult> computeCodeLines( |
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; |
- } |
- )); |
- return new CodeLinesResult(codeLines, coverage, elementMap, |
- sourceMaps.sourceFileManager); |
+ codeLines = convertAnnotatedCodeToCodeLines(code, annotations); |
+ return new CodeLinesResult( |
+ codeLines, coverage, elementMap, |
+ sourceMaps.sourceFileManager, |
+ codeSources); |
} |
/// Visitor that computes a map from [js.Node]s to all attached source |
@@ -565,4 +929,70 @@ CodeSource codeSourceFromElement(Element element) { |
} |
} |
return new CodeSource(kind, uri, name, begin, end); |
-} |
+} |
+ |
+/// Create [LineData] that colors line numbers according to the [CodeSource]s |
+/// origin if available. |
+LineData getLineData(CodeSource lineAnnotation) { |
+ if (lineAnnotation != null) { |
+ return new LineData( |
+ lineClass: ClassNames.line, |
+ lineNumberClass: |
+ '${ClassNames.lineNumber} ' |
+ '${ClassNames.colored(lineAnnotation.hashCode % 4)}'); |
+ } |
+ return new LineData( |
+ lineClass: ClassNames.line, |
+ lineNumberClass: ClassNames.lineNumber); |
+} |
+ |
+AnnotationData getAnnotationData(Iterable<Annotation> annotations, |
+ {bool forSpan}) { |
+ for (Annotation annotation in annotations) { |
+ CodeLineAnnotation data = annotation.data; |
+ if (data.annotationType.isSourceMapped) { |
+ if (forSpan) { |
+ int index = data.sourceMappingIndex; |
+ return new AnnotationData( |
+ tag: 'span', |
+ properties: { |
+ 'class': |
+ '${ClassNames.sourceMapping} ' |
+ '${ClassNames.sourceMappingIndex(index % HUE_COUNT)}', |
+ 'title': 'index=$index', |
+ }); |
+ } else { |
+ return new AnnotationData( |
+ tag: 'span', |
+ properties: { |
+ 'title': annotation.title, |
+ 'class': '${ClassNames.marker} ' |
+ '${data.annotationType.className}'}); |
+ } |
+ } |
+ } |
+ if (forSpan) return null; |
+ for (Annotation annotation in annotations) { |
+ CodeLineAnnotation data = annotation.data; |
+ if (data.annotationType == AnnotationType.UNUSED_SOURCE_INFO) { |
+ return new AnnotationData( |
+ tag: 'span', |
+ properties: { |
+ 'title': annotation.title, |
+ 'class': '${ClassNames.marker} ' |
+ '${data.annotationType.className}'}); |
+ } |
+ } |
+ for (Annotation annotation in annotations) { |
+ CodeLineAnnotation data = annotation.data; |
+ if (data.annotationType == AnnotationType.WITHOUT_SOURCE_INFO) { |
+ return new AnnotationData( |
+ tag: 'span', |
+ properties: { |
+ 'title': annotation.title, |
+ 'class': '${ClassNames.marker} ' |
+ '${data.annotationType.className}'}); |
+ } |
+ } |
+ return null; |
+} |