Chromium Code Reviews| 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..f95af01134cf79f620a3757278f6230ad21659f9 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.mainDart}, |
| +.${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.mainDart} { |
| +} |
| +.${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,257 @@ 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]; |
|
Siggi Cherem (dart-lang)
2016/03/07 18:08:36
nti: format here and below
Johnni Winther
2016/03/08 09:15:18
Done.
|
| + 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 +877,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 +927,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; |
| +} |