| Index: tests/compiler/dart2js/sourcemaps/sourcemap_html_helper.dart
|
| diff --git a/tests/compiler/dart2js/sourcemaps/sourcemap_html_helper.dart b/tests/compiler/dart2js/sourcemaps/sourcemap_html_helper.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..eb1399a60628b27a89c1a1d210f2e53afb719c38
|
| --- /dev/null
|
| +++ b/tests/compiler/dart2js/sourcemaps/sourcemap_html_helper.dart
|
| @@ -0,0 +1,430 @@
|
| +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
|
| +// for details. All rights reserved. Use of this source code is governed by a
|
| +// BSD-style license that can be found in the LICENSE file.
|
| +
|
| +/// Helper for creating HTML visualization of the source map information
|
| +/// generated by a [SourceMapProcessor].
|
| +
|
| +library sourcemap.html.helper;
|
| +
|
| +import 'dart:convert';
|
| +
|
| +import 'package:compiler/src/io/source_file.dart';
|
| +import 'package:compiler/src/io/source_information.dart';
|
| +import 'package:compiler/src/js/js.dart' as js;
|
| +
|
| +import 'colors.dart';
|
| +import 'sourcemap_helper.dart';
|
| +import 'sourcemap_html_templates.dart';
|
| +
|
| +/// Returns the [index]th color for visualization.
|
| +String toColor(int index) {
|
| + int hueCount = 24;
|
| + double h = 360.0 * (index % hueCount) / hueCount;
|
| + double v = 1.0;
|
| + double s = 0.5;
|
| + HSV hsv = new HSV(h, s, v);
|
| + RGB rgb = HSV.toRGB(hsv);
|
| + return rgb.toHex;
|
| +}
|
| +
|
| +/// Return the html for the [index] line number.
|
| +String lineNumber(int index) {
|
| + return '<span class="lineNumber">${index + 1} </span>';
|
| +}
|
| +
|
| +/// Return the html escaped [text].
|
| +String escape(String text) {
|
| + return const HtmlEscape().convert(text);
|
| +}
|
| +
|
| +/// Information needed to generate HTML for a single [SourceMapInfo].
|
| +class SourceMapHtmlInfo {
|
| + final SourceMapInfo sourceMapInfo;
|
| + final CodeProcessor codeProcessor;
|
| + final SourceLocationCollection sourceLocationCollection;
|
| +
|
| + SourceMapHtmlInfo(this.sourceMapInfo,
|
| + this.codeProcessor,
|
| + this.sourceLocationCollection);
|
| +}
|
| +
|
| +/// A collection of source locations.
|
| +///
|
| +/// Used to index source locations for visualization and linking.
|
| +class SourceLocationCollection {
|
| + List<SourceLocation> sourceLocations = [];
|
| + Map<SourceLocation, int> sourceLocationIndexMap;
|
| +
|
| + SourceLocationCollection([SourceLocationCollection parent])
|
| + : sourceLocationIndexMap =
|
| + parent == null ? {} : parent.sourceLocationIndexMap;
|
| +
|
| + int registerSourceLocation(SourceLocation sourceLocation) {
|
| + return sourceLocationIndexMap.putIfAbsent(sourceLocation, () {
|
| + sourceLocations.add(sourceLocation);
|
| + return sourceLocationIndexMap.length;
|
| + });
|
| + }
|
| +
|
| + int getIndex(SourceLocation sourceLocation) {
|
| + return sourceLocationIndexMap[sourceLocation];
|
| + }
|
| +}
|
| +
|
| +/// Processor that computes the HTML representation of a block of JavaScript
|
| +/// code and collects the source locations mapped in the code.
|
| +class CodeProcessor {
|
| + int lineIndex = 0;
|
| + final String onclick;
|
| + int currentJsSourceOffset = 0;
|
| + final SourceLocationCollection collection;
|
| + final Map<int, List<SourceLocation>> codeLocations = {};
|
| +
|
| + CodeProcessor(this.onclick, this.collection);
|
| +
|
| + void addSourceLocation(int targetOffset, SourceLocation sourceLocation) {
|
| + codeLocations.putIfAbsent(targetOffset, () => []).add(sourceLocation);
|
| + collection.registerSourceLocation(sourceLocation);
|
| + }
|
| +
|
| + String convertToHtml(String text) {
|
| + StringBuffer htmlBuffer = new StringBuffer();
|
| + int offset = 0;
|
| + int lineIndex = 0;
|
| + bool pendingSourceLocationsEnd = false;
|
| + htmlBuffer.write(lineNumber(lineIndex));
|
| + SourceLocation currentLocation;
|
| +
|
| + void endCurrentLocation() {
|
| + if (currentLocation != null) {
|
| + htmlBuffer.write('</a>');
|
| + }
|
| + currentLocation = null;
|
| + }
|
| +
|
| + void addSubstring(int until) {
|
| + if (until <= offset) return;
|
| +
|
| + String substring = text.substring(offset, until);
|
| + offset = until;
|
| + bool first = true;
|
| + for (String line in substring.split('\n')) {
|
| + if (!first) {
|
| + endCurrentLocation();
|
| + htmlBuffer.write('\n');
|
| + lineIndex++;
|
| + htmlBuffer.write(lineNumber(lineIndex));
|
| + }
|
| + htmlBuffer.write(escape(line));
|
| + first = false;
|
| + }
|
| + }
|
| +
|
| + void insertSourceLocations(List<SourceLocation> lastSourceLocations) {
|
| + endCurrentLocation();
|
| +
|
| + String color;
|
| + int index;
|
| + String title;
|
| + if (lastSourceLocations.length == 1) {
|
| + SourceLocation sourceLocation = lastSourceLocations.single;
|
| + if (sourceLocation != null) {
|
| + index = collection.getIndex(sourceLocation);
|
| + color = "background:#${toColor(index)};";
|
| + title = sourceLocation.shortText;
|
| + currentLocation = sourceLocation;
|
| + }
|
| + } else {
|
| +
|
| + index = collection.getIndex(lastSourceLocations.first);
|
| + StringBuffer sb = new StringBuffer();
|
| + double delta = 100.0 / (lastSourceLocations.length);
|
| + double position = 0.0;
|
| +
|
| + void addColor(String color) {
|
| + sb.write(', ${color} ${position.toInt()}%');
|
| + position += delta;
|
| + sb.write(', ${color} ${position.toInt()}%');
|
| + }
|
| +
|
| + for (SourceLocation sourceLocation in lastSourceLocations) {
|
| + if (sourceLocation == null) continue;
|
| + int colorIndex = collection.getIndex(sourceLocation);
|
| + addColor('#${toColor(colorIndex)}');
|
| + currentLocation = sourceLocation;
|
| + }
|
| + color = 'background: linear-gradient(to right${sb}); '
|
| + 'background-size: 10px 10px;';
|
| + title = lastSourceLocations.map((l) => l.shortText).join(',');
|
| + }
|
| + if (index != null) {
|
| + Set<int> indices =
|
| + lastSourceLocations.map((l) => collection.getIndex(l)).toSet();
|
| + String onmouseover = indices.map((i) => '\'$i\'').join(',');
|
| + htmlBuffer.write(
|
| + '<a name="js$index" href="#${index}" style="$color" title="$title" '
|
| + 'onclick="${onclick}" onmouseover="highlight([${onmouseover}]);"'
|
| + 'onmouseout="highlight([]);">');
|
| + pendingSourceLocationsEnd = true;
|
| + }
|
| + if (lastSourceLocations.last == null) {
|
| + endCurrentLocation();
|
| + }
|
| + }
|
| +
|
| + for (int targetOffset in codeLocations.keys.toList()..sort()) {
|
| + List<SourceLocation> sourceLocations = codeLocations[targetOffset];
|
| + addSubstring(targetOffset);
|
| + insertSourceLocations(sourceLocations);
|
| + }
|
| +
|
| + addSubstring(text.length);
|
| + endCurrentLocation();
|
| + return htmlBuffer.toString();
|
| + }
|
| +}
|
| +
|
| +/// Computes the HTML representation for a collection of JavaScript code blocks.
|
| +String computeJsHtml(Iterable<SourceMapHtmlInfo> infoList) {
|
| +
|
| + StringBuffer jsCodeBuffer = new StringBuffer();
|
| + for (SourceMapHtmlInfo info in infoList) {
|
| + String name = info.sourceMapInfo.name;
|
| + String html = info.codeProcessor.convertToHtml(info.sourceMapInfo.code);
|
| + String onclick = 'show(\'$name\');';
|
| + jsCodeBuffer.write(
|
| + '<h3 onclick="$onclick">JS code for: ${escape(name)}</h3>\n');
|
| + jsCodeBuffer.write('''
|
| +<pre>
|
| +$html
|
| +</pre>
|
| +''');
|
| + }
|
| + return jsCodeBuffer.toString();
|
| +}
|
| +
|
| +/// Computes the HTML representation of the source mapping information for a
|
| +/// collection of JavaScript code blocks.
|
| +String computeJsTraceHtml(Iterable<SourceMapHtmlInfo> infoList) {
|
| + StringBuffer jsTraceBuffer = new StringBuffer();
|
| + for (SourceMapHtmlInfo info in infoList) {
|
| + String name = info.sourceMapInfo.name;
|
| + String jsTrace = computeJsTraceHtmlPart(
|
| + info.sourceMapInfo.codePoints, info.sourceLocationCollection);
|
| + jsTraceBuffer.write('''
|
| +<div name="$name" class="js-trace-buffer" style="display:none;">
|
| +<h3>Trace for: ${escape(name)}</h3>
|
| +$jsTrace
|
| +</div>
|
| +''');
|
| + }
|
| + return jsTraceBuffer.toString();
|
| +}
|
| +
|
| +/// Computes the HTML information for the [info].
|
| +SourceMapHtmlInfo createHtmlInfo(SourceLocationCollection collection,
|
| + SourceMapInfo info) {
|
| + js.Node node = info.node;
|
| + String code = info.code;
|
| + String name = info.name;
|
| + String onclick = 'show(\'$name\');';
|
| + SourceLocationCollection subcollection =
|
| + new SourceLocationCollection(collection);
|
| + CodeProcessor codeProcessor = new CodeProcessor(onclick, subcollection);
|
| + for (js.Node node in info.nodeMap.nodes) {
|
| + info.nodeMap[node].forEach(
|
| + (int targetOffset, List<SourceLocation> sourceLocations) {
|
| + for (SourceLocation sourceLocation in sourceLocations) {
|
| + codeProcessor.addSourceLocation(targetOffset, sourceLocation);
|
| + }
|
| + });
|
| + }
|
| + return new SourceMapHtmlInfo(info, codeProcessor, subcollection);
|
| +}
|
| +
|
| +/// Outputs a HTML file in [jsMapHtmlUri] containing an interactive
|
| +/// visualization of the source mapping information in [infoList] computed
|
| +/// with the [sourceMapProcessor].
|
| +void createTraceSourceMapHtml(Uri jsMapHtmlUri,
|
| + SourceMapProcessor sourceMapProcessor,
|
| + Iterable<SourceMapInfo> infoList) {
|
| + SourceFileManager sourceFileManager = sourceMapProcessor.sourceFileManager;
|
| + SourceLocationCollection collection = new SourceLocationCollection();
|
| + List<SourceMapHtmlInfo> htmlInfoList = <SourceMapHtmlInfo>[];
|
| + for (SourceMapInfo info in infoList) {
|
| + htmlInfoList.add(createHtmlInfo(collection, info));
|
| + }
|
| +
|
| + String jsCode = computeJsHtml(htmlInfoList);
|
| + String dartCode = computeDartHtml(sourceFileManager, htmlInfoList);
|
| +
|
| + String jsTraceHtml = computeJsTraceHtml(htmlInfoList);
|
| + outputJsDartTrace(jsMapHtmlUri, jsCode, dartCode, jsTraceHtml);
|
| + print('Trace source map html generated: $jsMapHtmlUri');
|
| +}
|
| +
|
| +/// Computes the HTML representation for the Dart code snippets referenced in
|
| +/// [infoList].
|
| +String computeDartHtml(
|
| + SourceFileManager sourceFileManager,
|
| + Iterable<SourceMapHtmlInfo> infoList) {
|
| +
|
| + StringBuffer dartCodeBuffer = new StringBuffer();
|
| + for (SourceMapHtmlInfo info in infoList) {
|
| + dartCodeBuffer.write(computeDartHtmlPart(info.sourceMapInfo.name,
|
| + sourceFileManager, info.sourceLocationCollection));
|
| + }
|
| + return dartCodeBuffer.toString();
|
| +
|
| +}
|
| +
|
| +/// Computes the HTML representation for the Dart code snippets in [collection].
|
| +String computeDartHtmlPart(String name,
|
| + SourceFileManager sourceFileManager,
|
| + SourceLocationCollection collection,
|
| + {bool showAsBlock: false}) {
|
| + const int windowSize = 3;
|
| + StringBuffer dartCodeBuffer = new StringBuffer();
|
| + Map<Uri, Map<int, List<SourceLocation>>> sourceLocationMap = {};
|
| + collection.sourceLocations.forEach((SourceLocation sourceLocation) {
|
| + Map<int, List<SourceLocation>> uriMap =
|
| + sourceLocationMap.putIfAbsent(sourceLocation.sourceUri, () => {});
|
| + List<SourceLocation> lineList =
|
| + uriMap.putIfAbsent(sourceLocation.line, () => []);
|
| + lineList.add(sourceLocation);
|
| + });
|
| + sourceLocationMap.forEach((Uri uri, Map<int, List<SourceLocation>> uriMap) {
|
| + SourceFile sourceFile = sourceFileManager.getSourceFile(uri);
|
| + StringBuffer codeBuffer = new StringBuffer();
|
| +
|
| + int firstLineIndex;
|
| + int lastLineIndex;
|
| +
|
| + void flush() {
|
| + if (firstLineIndex != null && lastLineIndex != null) {
|
| + dartCodeBuffer.write(
|
| + '<h4>${uri.pathSegments.last}, '
|
| + '${firstLineIndex - windowSize + 1}-'
|
| + '${lastLineIndex + windowSize + 1}'
|
| + '</h4>\n');
|
| + dartCodeBuffer.write('<pre>\n');
|
| + for (int line = firstLineIndex - windowSize;
|
| + line < firstLineIndex;
|
| + line++) {
|
| + if (line >= 0) {
|
| + dartCodeBuffer.write(lineNumber(line));
|
| + dartCodeBuffer.write(sourceFile.getLineText(line));
|
| + }
|
| + }
|
| + dartCodeBuffer.write(codeBuffer);
|
| + for (int line = lastLineIndex + 1;
|
| + line <= lastLineIndex + windowSize;
|
| + line++) {
|
| + if (line < sourceFile.lines) {
|
| + dartCodeBuffer.write(lineNumber(line));
|
| + dartCodeBuffer.write(sourceFile.getLineText(line));
|
| + }
|
| + }
|
| + dartCodeBuffer.write('</pre>\n');
|
| + firstLineIndex = null;
|
| + lastLineIndex = null;
|
| + }
|
| + codeBuffer.clear();
|
| + }
|
| +
|
| + List<int> lineIndices = uriMap.keys.toList()..sort();
|
| + lineIndices.forEach((int lineIndex) {
|
| + List<SourceLocation> locations = uriMap[lineIndex];
|
| + if (lastLineIndex != null &&
|
| + lastLineIndex + windowSize * 4 < lineIndex) {
|
| + flush();
|
| + }
|
| + if (firstLineIndex == null) {
|
| + firstLineIndex = lineIndex;
|
| + } else {
|
| + for (int line = lastLineIndex + 1; line < lineIndex; line++) {
|
| + codeBuffer.write(lineNumber(line));
|
| + codeBuffer.write(sourceFile.getLineText(line));
|
| + }
|
| + }
|
| + String line = sourceFile.getLineText(lineIndex);
|
| + locations.sort((a, b) => a.offset.compareTo(b.offset));
|
| + for (int i = 0; i < locations.length; i++) {
|
| + SourceLocation sourceLocation = locations[i];
|
| + int index = collection.getIndex(sourceLocation);
|
| + int start = sourceLocation.column;
|
| + int end = line.length;
|
| + if (i + 1 < locations.length) {
|
| + end = locations[i + 1].column;
|
| + }
|
| + if (i == 0) {
|
| + codeBuffer.write(lineNumber(lineIndex));
|
| + codeBuffer.write(line.substring(0, start));
|
| + }
|
| + codeBuffer.write(
|
| + '<a name="${index}" style="background:#${toColor(index)};" '
|
| + 'title="[${lineIndex + 1},${start + 1}]" '
|
| + 'onmouseover="highlight(\'$index\');" '
|
| + 'onmouseout="highlight();">');
|
| + codeBuffer.write(line.substring(start, end));
|
| + codeBuffer.write('</a>');
|
| + }
|
| + lastLineIndex = lineIndex;
|
| + });
|
| +
|
| + flush();
|
| + });
|
| + String display = showAsBlock ? 'block' : 'none';
|
| + return '''
|
| +<div name="$name" class="dart-buffer" style="display:$display;">
|
| +<h3>Dart code for: ${escape(name)}</h3>
|
| +${dartCodeBuffer}
|
| +</div>''';
|
| +}
|
| +
|
| +/// Computes a HTML visualization of the [codePoints].
|
| +String computeJsTraceHtmlPart(List<CodePoint> codePoints,
|
| + SourceLocationCollection collection) {
|
| + StringBuffer buffer = new StringBuffer();
|
| + buffer.write('<table style="width:100%;">');
|
| + buffer.write(
|
| + '<tr><th>Node kind</th><th>JS code @ offset</th>'
|
| + '<th>Dart code @ mapped location</th><th>file:position:name</th></tr>');
|
| + codePoints.forEach((CodePoint codePoint) {
|
| + String jsCode = codePoint.jsCode;
|
| + if (jsCode.length > 40) {
|
| + jsCode = jsCode.substring(0, 40);
|
| + }
|
| + if (codePoint.sourceLocation != null) {
|
| + int index = collection.getIndex(codePoint.sourceLocation);
|
| + if (index != null) {
|
| +
|
| + buffer.write('<tr style="background:#${toColor(index)};" '
|
| + 'name="trace$index" '
|
| + 'onmouseover="highlight([${index}]);"'
|
| + 'onmouseout="highlight([]);">');
|
| + } else {
|
| + buffer.write('<tr>');
|
| + print('${codePoint.sourceLocation} not found in ');
|
| + collection.sourceLocationIndexMap.keys
|
| + .where((l) => l.sourceUri == codePoint.sourceLocation.sourceUri)
|
| + .forEach((l) => print(' $l'));
|
| + }
|
| + } else {
|
| + buffer.write('<tr>');
|
| + }
|
| + buffer.write('<td>${codePoint.kind}</td>');
|
| + buffer.write('<td class="code">${jsCode}</td>');
|
| + if (codePoint.sourceLocation == null) {
|
| + //buffer.write('<td></td>');
|
| + } else {
|
| + buffer.write('<td class="code">${codePoint.dartCode}</td>');
|
| + buffer.write('<td>${escape(codePoint.sourceLocation.shortText)}</td>');
|
| + }
|
| + buffer.write('</tr>');
|
| + });
|
| + buffer.write('</table>');
|
| +
|
| + return buffer.toString();
|
| +}
|
|
|