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

Side by Side 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, 5 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 /// Helper for creating HTML visualization of the source map information
6 /// generated by a [SourceMapProcessor].
7
8 library sourcemap.html.helper;
9
10 import 'dart:convert';
11
12 import 'package:compiler/src/io/source_file.dart';
13 import 'package:compiler/src/io/source_information.dart';
14 import 'package:compiler/src/js/js.dart' as js;
15
16 import 'colors.dart';
17 import 'sourcemap_helper.dart';
18 import 'sourcemap_html_templates.dart';
19
20 /// Returns the [index]th color for visualization.
21 String toColor(int index) {
22 int hueCount = 24;
23 double h = 360.0 * (index % hueCount) / hueCount;
24 double v = 1.0;
25 double s = 0.5;
26 HSV hsv = new HSV(h, s, v);
27 RGB rgb = HSV.toRGB(hsv);
28 return rgb.toHex;
29 }
30
31 /// Return the html for the [index] line number.
32 String lineNumber(int index) {
33 return '<span class="lineNumber">${index + 1} </span>';
34 }
35
36 /// Return the html escaped [text].
37 String escape(String text) {
38 return const HtmlEscape().convert(text);
39 }
40
41 /// Information needed to generate HTML for a single [SourceMapInfo].
42 class SourceMapHtmlInfo {
43 final SourceMapInfo sourceMapInfo;
44 final CodeProcessor codeProcessor;
45 final SourceLocationCollection sourceLocationCollection;
46
47 SourceMapHtmlInfo(this.sourceMapInfo,
48 this.codeProcessor,
49 this.sourceLocationCollection);
50 }
51
52 /// A collection of source locations.
53 ///
54 /// Used to index source locations for visualization and linking.
55 class SourceLocationCollection {
56 List<SourceLocation> sourceLocations = [];
57 Map<SourceLocation, int> sourceLocationIndexMap;
58
59 SourceLocationCollection([SourceLocationCollection parent])
60 : sourceLocationIndexMap =
61 parent == null ? {} : parent.sourceLocationIndexMap;
62
63 int registerSourceLocation(SourceLocation sourceLocation) {
64 return sourceLocationIndexMap.putIfAbsent(sourceLocation, () {
65 sourceLocations.add(sourceLocation);
66 return sourceLocationIndexMap.length;
67 });
68 }
69
70 int getIndex(SourceLocation sourceLocation) {
71 return sourceLocationIndexMap[sourceLocation];
72 }
73 }
74
75 /// Processor that computes the HTML representation of a block of JavaScript
76 /// code and collects the source locations mapped in the code.
77 class CodeProcessor {
78 int lineIndex = 0;
79 final String onclick;
80 int currentJsSourceOffset = 0;
81 final SourceLocationCollection collection;
82 final Map<int, List<SourceLocation>> codeLocations = {};
83
84 CodeProcessor(this.onclick, this.collection);
85
86 void addSourceLocation(int targetOffset, SourceLocation sourceLocation) {
87 codeLocations.putIfAbsent(targetOffset, () => []).add(sourceLocation);
88 collection.registerSourceLocation(sourceLocation);
89 }
90
91 String convertToHtml(String text) {
92 StringBuffer htmlBuffer = new StringBuffer();
93 int offset = 0;
94 int lineIndex = 0;
95 bool pendingSourceLocationsEnd = false;
96 htmlBuffer.write(lineNumber(lineIndex));
97 SourceLocation currentLocation;
98
99 void endCurrentLocation() {
100 if (currentLocation != null) {
101 htmlBuffer.write('</a>');
102 }
103 currentLocation = null;
104 }
105
106 void addSubstring(int until) {
107 if (until <= offset) return;
108
109 String substring = text.substring(offset, until);
110 offset = until;
111 bool first = true;
112 for (String line in substring.split('\n')) {
113 if (!first) {
114 endCurrentLocation();
115 htmlBuffer.write('\n');
116 lineIndex++;
117 htmlBuffer.write(lineNumber(lineIndex));
118 }
119 htmlBuffer.write(escape(line));
120 first = false;
121 }
122 }
123
124 void insertSourceLocations(List<SourceLocation> lastSourceLocations) {
125 endCurrentLocation();
126
127 String color;
128 int index;
129 String title;
130 if (lastSourceLocations.length == 1) {
131 SourceLocation sourceLocation = lastSourceLocations.single;
132 if (sourceLocation != null) {
133 index = collection.getIndex(sourceLocation);
134 color = "background:#${toColor(index)};";
135 title = sourceLocation.shortText;
136 currentLocation = sourceLocation;
137 }
138 } else {
139
140 index = collection.getIndex(lastSourceLocations.first);
141 StringBuffer sb = new StringBuffer();
142 double delta = 100.0 / (lastSourceLocations.length);
143 double position = 0.0;
144
145 void addColor(String color) {
146 sb.write(', ${color} ${position.toInt()}%');
147 position += delta;
148 sb.write(', ${color} ${position.toInt()}%');
149 }
150
151 for (SourceLocation sourceLocation in lastSourceLocations) {
152 if (sourceLocation == null) continue;
153 int colorIndex = collection.getIndex(sourceLocation);
154 addColor('#${toColor(colorIndex)}');
155 currentLocation = sourceLocation;
156 }
157 color = 'background: linear-gradient(to right${sb}); '
158 'background-size: 10px 10px;';
159 title = lastSourceLocations.map((l) => l.shortText).join(',');
160 }
161 if (index != null) {
162 Set<int> indices =
163 lastSourceLocations.map((l) => collection.getIndex(l)).toSet();
164 String onmouseover = indices.map((i) => '\'$i\'').join(',');
165 htmlBuffer.write(
166 '<a name="js$index" href="#${index}" style="$color" title="$title" '
167 'onclick="${onclick}" onmouseover="highlight([${onmouseover}]);"'
168 'onmouseout="highlight([]);">');
169 pendingSourceLocationsEnd = true;
170 }
171 if (lastSourceLocations.last == null) {
172 endCurrentLocation();
173 }
174 }
175
176 for (int targetOffset in codeLocations.keys.toList()..sort()) {
177 List<SourceLocation> sourceLocations = codeLocations[targetOffset];
178 addSubstring(targetOffset);
179 insertSourceLocations(sourceLocations);
180 }
181
182 addSubstring(text.length);
183 endCurrentLocation();
184 return htmlBuffer.toString();
185 }
186 }
187
188 /// Computes the HTML representation for a collection of JavaScript code blocks.
189 String computeJsHtml(Iterable<SourceMapHtmlInfo> infoList) {
190
191 StringBuffer jsCodeBuffer = new StringBuffer();
192 for (SourceMapHtmlInfo info in infoList) {
193 String name = info.sourceMapInfo.name;
194 String html = info.codeProcessor.convertToHtml(info.sourceMapInfo.code);
195 String onclick = 'show(\'$name\');';
196 jsCodeBuffer.write(
197 '<h3 onclick="$onclick">JS code for: ${escape(name)}</h3>\n');
198 jsCodeBuffer.write('''
199 <pre>
200 $html
201 </pre>
202 ''');
203 }
204 return jsCodeBuffer.toString();
205 }
206
207 /// Computes the HTML representation of the source mapping information for a
208 /// collection of JavaScript code blocks.
209 String computeJsTraceHtml(Iterable<SourceMapHtmlInfo> infoList) {
210 StringBuffer jsTraceBuffer = new StringBuffer();
211 for (SourceMapHtmlInfo info in infoList) {
212 String name = info.sourceMapInfo.name;
213 String jsTrace = computeJsTraceHtmlPart(
214 info.sourceMapInfo.codePoints, info.sourceLocationCollection);
215 jsTraceBuffer.write('''
216 <div name="$name" class="js-trace-buffer" style="display:none;">
217 <h3>Trace for: ${escape(name)}</h3>
218 $jsTrace
219 </div>
220 ''');
221 }
222 return jsTraceBuffer.toString();
223 }
224
225 /// Computes the HTML information for the [info].
226 SourceMapHtmlInfo createHtmlInfo(SourceLocationCollection collection,
227 SourceMapInfo info) {
228 js.Node node = info.node;
229 String code = info.code;
230 String name = info.name;
231 String onclick = 'show(\'$name\');';
232 SourceLocationCollection subcollection =
233 new SourceLocationCollection(collection);
234 CodeProcessor codeProcessor = new CodeProcessor(onclick, subcollection);
235 for (js.Node node in info.nodeMap.nodes) {
236 info.nodeMap[node].forEach(
237 (int targetOffset, List<SourceLocation> sourceLocations) {
238 for (SourceLocation sourceLocation in sourceLocations) {
239 codeProcessor.addSourceLocation(targetOffset, sourceLocation);
240 }
241 });
242 }
243 return new SourceMapHtmlInfo(info, codeProcessor, subcollection);
244 }
245
246 /// Outputs a HTML file in [jsMapHtmlUri] containing an interactive
247 /// visualization of the source mapping information in [infoList] computed
248 /// with the [sourceMapProcessor].
249 void createTraceSourceMapHtml(Uri jsMapHtmlUri,
250 SourceMapProcessor sourceMapProcessor,
251 Iterable<SourceMapInfo> infoList) {
252 SourceFileManager sourceFileManager = sourceMapProcessor.sourceFileManager;
253 SourceLocationCollection collection = new SourceLocationCollection();
254 List<SourceMapHtmlInfo> htmlInfoList = <SourceMapHtmlInfo>[];
255 for (SourceMapInfo info in infoList) {
256 htmlInfoList.add(createHtmlInfo(collection, info));
257 }
258
259 String jsCode = computeJsHtml(htmlInfoList);
260 String dartCode = computeDartHtml(sourceFileManager, htmlInfoList);
261
262 String jsTraceHtml = computeJsTraceHtml(htmlInfoList);
263 outputJsDartTrace(jsMapHtmlUri, jsCode, dartCode, jsTraceHtml);
264 print('Trace source map html generated: $jsMapHtmlUri');
265 }
266
267 /// Computes the HTML representation for the Dart code snippets referenced in
268 /// [infoList].
269 String computeDartHtml(
270 SourceFileManager sourceFileManager,
271 Iterable<SourceMapHtmlInfo> infoList) {
272
273 StringBuffer dartCodeBuffer = new StringBuffer();
274 for (SourceMapHtmlInfo info in infoList) {
275 dartCodeBuffer.write(computeDartHtmlPart(info.sourceMapInfo.name,
276 sourceFileManager, info.sourceLocationCollection));
277 }
278 return dartCodeBuffer.toString();
279
280 }
281
282 /// Computes the HTML representation for the Dart code snippets in [collection].
283 String computeDartHtmlPart(String name,
284 SourceFileManager sourceFileManager,
285 SourceLocationCollection collection,
286 {bool showAsBlock: false}) {
287 const int windowSize = 3;
288 StringBuffer dartCodeBuffer = new StringBuffer();
289 Map<Uri, Map<int, List<SourceLocation>>> sourceLocationMap = {};
290 collection.sourceLocations.forEach((SourceLocation sourceLocation) {
291 Map<int, List<SourceLocation>> uriMap =
292 sourceLocationMap.putIfAbsent(sourceLocation.sourceUri, () => {});
293 List<SourceLocation> lineList =
294 uriMap.putIfAbsent(sourceLocation.line, () => []);
295 lineList.add(sourceLocation);
296 });
297 sourceLocationMap.forEach((Uri uri, Map<int, List<SourceLocation>> uriMap) {
298 SourceFile sourceFile = sourceFileManager.getSourceFile(uri);
299 StringBuffer codeBuffer = new StringBuffer();
300
301 int firstLineIndex;
302 int lastLineIndex;
303
304 void flush() {
305 if (firstLineIndex != null && lastLineIndex != null) {
306 dartCodeBuffer.write(
307 '<h4>${uri.pathSegments.last}, '
308 '${firstLineIndex - windowSize + 1}-'
309 '${lastLineIndex + windowSize + 1}'
310 '</h4>\n');
311 dartCodeBuffer.write('<pre>\n');
312 for (int line = firstLineIndex - windowSize;
313 line < firstLineIndex;
314 line++) {
315 if (line >= 0) {
316 dartCodeBuffer.write(lineNumber(line));
317 dartCodeBuffer.write(sourceFile.getLineText(line));
318 }
319 }
320 dartCodeBuffer.write(codeBuffer);
321 for (int line = lastLineIndex + 1;
322 line <= lastLineIndex + windowSize;
323 line++) {
324 if (line < sourceFile.lines) {
325 dartCodeBuffer.write(lineNumber(line));
326 dartCodeBuffer.write(sourceFile.getLineText(line));
327 }
328 }
329 dartCodeBuffer.write('</pre>\n');
330 firstLineIndex = null;
331 lastLineIndex = null;
332 }
333 codeBuffer.clear();
334 }
335
336 List<int> lineIndices = uriMap.keys.toList()..sort();
337 lineIndices.forEach((int lineIndex) {
338 List<SourceLocation> locations = uriMap[lineIndex];
339 if (lastLineIndex != null &&
340 lastLineIndex + windowSize * 4 < lineIndex) {
341 flush();
342 }
343 if (firstLineIndex == null) {
344 firstLineIndex = lineIndex;
345 } else {
346 for (int line = lastLineIndex + 1; line < lineIndex; line++) {
347 codeBuffer.write(lineNumber(line));
348 codeBuffer.write(sourceFile.getLineText(line));
349 }
350 }
351 String line = sourceFile.getLineText(lineIndex);
352 locations.sort((a, b) => a.offset.compareTo(b.offset));
353 for (int i = 0; i < locations.length; i++) {
354 SourceLocation sourceLocation = locations[i];
355 int index = collection.getIndex(sourceLocation);
356 int start = sourceLocation.column;
357 int end = line.length;
358 if (i + 1 < locations.length) {
359 end = locations[i + 1].column;
360 }
361 if (i == 0) {
362 codeBuffer.write(lineNumber(lineIndex));
363 codeBuffer.write(line.substring(0, start));
364 }
365 codeBuffer.write(
366 '<a name="${index}" style="background:#${toColor(index)};" '
367 'title="[${lineIndex + 1},${start + 1}]" '
368 'onmouseover="highlight(\'$index\');" '
369 'onmouseout="highlight();">');
370 codeBuffer.write(line.substring(start, end));
371 codeBuffer.write('</a>');
372 }
373 lastLineIndex = lineIndex;
374 });
375
376 flush();
377 });
378 String display = showAsBlock ? 'block' : 'none';
379 return '''
380 <div name="$name" class="dart-buffer" style="display:$display;">
381 <h3>Dart code for: ${escape(name)}</h3>
382 ${dartCodeBuffer}
383 </div>''';
384 }
385
386 /// Computes a HTML visualization of the [codePoints].
387 String computeJsTraceHtmlPart(List<CodePoint> codePoints,
388 SourceLocationCollection collection) {
389 StringBuffer buffer = new StringBuffer();
390 buffer.write('<table style="width:100%;">');
391 buffer.write(
392 '<tr><th>Node kind</th><th>JS code @ offset</th>'
393 '<th>Dart code @ mapped location</th><th>file:position:name</th></tr>');
394 codePoints.forEach((CodePoint codePoint) {
395 String jsCode = codePoint.jsCode;
396 if (jsCode.length > 40) {
397 jsCode = jsCode.substring(0, 40);
398 }
399 if (codePoint.sourceLocation != null) {
400 int index = collection.getIndex(codePoint.sourceLocation);
401 if (index != null) {
402
403 buffer.write('<tr style="background:#${toColor(index)};" '
404 'name="trace$index" '
405 'onmouseover="highlight([${index}]);"'
406 'onmouseout="highlight([]);">');
407 } else {
408 buffer.write('<tr>');
409 print('${codePoint.sourceLocation} not found in ');
410 collection.sourceLocationIndexMap.keys
411 .where((l) => l.sourceUri == codePoint.sourceLocation.sourceUri)
412 .forEach((l) => print(' $l'));
413 }
414 } else {
415 buffer.write('<tr>');
416 }
417 buffer.write('<td>${codePoint.kind}</td>');
418 buffer.write('<td class="code">${jsCode}</td>');
419 if (codePoint.sourceLocation == null) {
420 //buffer.write('<td></td>');
421 } else {
422 buffer.write('<td class="code">${codePoint.dartCode}</td>');
423 buffer.write('<td>${escape(codePoint.sourceLocation.shortText)}</td>');
424 }
425 buffer.write('</tr>');
426 });
427 buffer.write('</table>');
428
429 return buffer.toString();
430 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698