| 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;
|
| +}
|
|
|