Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(120)

Unified Diff: tests/compiler/dart2js/sourcemaps/sourcemap_html_helper.dart

Issue 1196433002: Create and test source mapping for invocations. (Closed) Base URL: https://github.com/dart-lang/sdk.git@master
Patch Set: Rebased Created 5 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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();
+}

Powered by Google App Engine
This is Rietveld 408576698