OLD | NEW |
---|---|
1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library sourcemap.diff_view; | 5 library sourcemap.diff_view; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 import 'dart:convert'; | 8 import 'dart:convert'; |
9 import 'dart:io'; | 9 import 'dart:io'; |
10 | 10 |
11 import 'package:compiler/src/common.dart'; | |
11 import 'package:compiler/src/commandline_options.dart'; | 12 import 'package:compiler/src/commandline_options.dart'; |
12 import 'package:compiler/src/diagnostics/invariant.dart'; | 13 import 'package:compiler/src/diagnostics/invariant.dart'; |
13 import 'package:compiler/src/elements/elements.dart'; | 14 import 'package:compiler/src/elements/elements.dart'; |
14 import 'package:compiler/src/io/position_information.dart'; | 15 import 'package:compiler/src/io/position_information.dart'; |
15 import 'package:compiler/src/io/source_information.dart'; | 16 import 'package:compiler/src/io/source_information.dart'; |
16 import 'package:compiler/src/io/source_file.dart'; | 17 import 'package:compiler/src/io/source_file.dart'; |
17 import 'package:compiler/src/js/js.dart' as js; | 18 import 'package:compiler/src/js/js.dart' as js; |
18 import 'package:compiler/src/js/js_debug.dart'; | 19 import 'package:compiler/src/js/js_debug.dart'; |
19 | 20 |
20 import 'diff.dart'; | 21 import 'diff.dart'; |
21 import 'html_parts.dart'; | 22 import 'html_parts.dart'; |
22 import 'js_tracer.dart'; | 23 import 'js_tracer.dart'; |
23 import 'output_structure.dart'; | 24 import 'output_structure.dart'; |
24 import 'sourcemap_helper.dart'; | 25 import 'sourcemap_helper.dart'; |
25 import 'sourcemap_html_helper.dart'; | 26 import 'sourcemap_html_helper.dart'; |
26 import 'trace_graph.dart'; | 27 import 'trace_graph.dart'; |
27 | 28 |
28 const String WITH_SOURCE_INFO_STYLE = 'border: solid 1px #FF8080;'; | |
29 const String WITHOUT_SOURCE_INFO_STYLE = 'background-color: #8080FF;'; | |
30 const String ADDITIONAL_SOURCE_INFO_STYLE = 'border: solid 1px #80FF80;'; | |
31 const String UNUSED_SOURCE_INFO_STYLE = 'border: solid 1px #8080FF;'; | |
32 | |
33 main(List<String> args) async { | 29 main(List<String> args) async { |
34 DEBUG_MODE = true; | 30 DEBUG_MODE = true; |
35 String out = 'out.js.diff_view.html'; | 31 String out = 'out.js.diff_view.html'; |
36 String filename; | 32 String filename; |
37 List<String> currentOptions = []; | 33 List<String> currentOptions = []; |
38 List<List<String>> optionSegments = [currentOptions]; | 34 List<List<String>> optionSegments = [currentOptions]; |
39 Map<int, String> loadFrom = {}; | 35 Map<int, String> loadFrom = {}; |
40 Map<int, String> saveTo = {}; | 36 Map<int, String> saveTo = {}; |
41 int argGroup = 0; | 37 int argGroup = 0; |
42 bool addAnnotations = true; | 38 bool showAnnotations = true; |
43 for (String arg in args) { | 39 for (String arg in args) { |
44 if (arg == '--') { | 40 if (arg == '--') { |
45 currentOptions = []; | 41 currentOptions = []; |
46 optionSegments.add(currentOptions); | 42 optionSegments.add(currentOptions); |
47 argGroup++; | 43 argGroup++; |
48 } else if (arg == '-h') { | 44 } else if (arg == '-h') { |
49 addAnnotations = false; | 45 showAnnotations = false; |
50 print('Hiding annotations'); | 46 print('Hiding annotations'); |
51 } else if (arg == '-l') { | 47 } else if (arg == '-l') { |
52 loadFrom[argGroup] = 'out.js.diff$argGroup.json'; | 48 loadFrom[argGroup] = 'out.js.diff$argGroup.json'; |
53 } else if (arg.startsWith('--load=')) { | 49 } else if (arg.startsWith('--load=')) { |
54 loadFrom[argGroup] = arg.substring('--load='.length); | 50 loadFrom[argGroup] = arg.substring('--load='.length); |
55 } else if (arg == '-s') { | 51 } else if (arg == '-s') { |
56 saveTo[argGroup] = 'out.js.diff$argGroup.json'; | 52 saveTo[argGroup] = 'out.js.diff$argGroup.json'; |
57 } else if (arg.startsWith('--save=')) { | 53 } else if (arg.startsWith('--save=')) { |
58 saveTo[argGroup] = arg.substring('--save='.length); | 54 saveTo[argGroup] = arg.substring('--save='.length); |
59 } else if (arg.startsWith('-o')) { | 55 } else if (arg.startsWith('-o')) { |
(...skipping 25 matching lines...) Expand all Loading... | |
85 | 81 |
86 SourceFileManager sourceFileManager = new IOSourceFileManager(Uri.base); | 82 SourceFileManager sourceFileManager = new IOSourceFileManager(Uri.base); |
87 List<AnnotatedOutput> outputs = <AnnotatedOutput>[]; | 83 List<AnnotatedOutput> outputs = <AnnotatedOutput>[]; |
88 for (int i = 0; i < 2; i++) { | 84 for (int i = 0; i < 2; i++) { |
89 AnnotatedOutput output; | 85 AnnotatedOutput output; |
90 if (loadFrom.containsKey(i)) { | 86 if (loadFrom.containsKey(i)) { |
91 output = AnnotatedOutput.loadOutput(loadFrom[i]); | 87 output = AnnotatedOutput.loadOutput(loadFrom[i]); |
92 } else { | 88 } else { |
93 print('Compiling ${options[i].join(' ')} $filename'); | 89 print('Compiling ${options[i].join(' ')} $filename'); |
94 CodeLinesResult result = await computeCodeLines( | 90 CodeLinesResult result = await computeCodeLines( |
95 options[i], filename, addAnnotations: addAnnotations); | 91 options[i], filename); |
96 OutputStructure structure = OutputStructure.parse(result.codeLines); | 92 OutputStructure structure = OutputStructure.parse(result.codeLines); |
97 computeEntityCodeSources(result, structure); | 93 computeEntityCodeSources(result, structure); |
98 output = new AnnotatedOutput( | 94 output = new AnnotatedOutput( |
99 filename, | 95 filename, |
100 options[i], | 96 options[i], |
101 structure, | 97 structure, |
102 result.coverage.getCoverageReport()); | 98 result.coverage.getCoverageReport()); |
103 } | 99 } |
104 if (saveTo.containsKey(i)) { | 100 if (saveTo.containsKey(i)) { |
105 AnnotatedOutput.saveOutput(output, saveTo[i]); | 101 AnnotatedOutput.saveOutput(output, saveTo[i]); |
106 } | 102 } |
107 outputs.add(output); | 103 outputs.add(output); |
108 } | 104 } |
109 | 105 |
110 List<DiffBlock> blocks = createDiffBlocks( | 106 List<DiffBlock> blocks = createDiffBlocks( |
111 outputs.map((o) => o.structure).toList(), | 107 outputs.map((o) => o.structure).toList(), |
112 sourceFileManager); | 108 sourceFileManager); |
113 | 109 |
114 outputDiffView( | 110 outputDiffView( |
115 out, outputs, blocks, addAnnotations: addAnnotations); | 111 out, outputs, blocks, |
112 showMarkers: showAnnotations, | |
113 showSourceMapped: showAnnotations); | |
116 } | 114 } |
117 | 115 |
118 /// Attaches [CodeSource]s to the entities in [structure] using the | 116 /// Attaches [CodeSource]s to the entities in [structure] using the |
119 /// element-to-offset in [result]. | 117 /// element-to-offset in [result]. |
120 void computeEntityCodeSources( | 118 void computeEntityCodeSources( |
121 CodeLinesResult result, OutputStructure structure) { | 119 CodeLinesResult result, OutputStructure structure) { |
122 result.elementMap.forEach((int line, Element element) { | 120 result.elementMap.forEach((int line, Element element) { |
123 OutputEntity entity = structure.getEntityForLine(line); | 121 OutputEntity entity = structure.getEntityForLine(line); |
124 if (entity != null) { | 122 if (entity != null) { |
125 entity.codeSource = codeSourceFromElement(element); | 123 entity.codeSource = codeSourceFromElement(element); |
126 } | 124 } |
127 }); | 125 }); |
128 } | 126 } |
129 | 127 |
128 class CodeLineAnnotationJsonStrategy implements JsonStrategy { | |
129 const CodeLineAnnotationJsonStrategy(); | |
130 | |
131 Map encodeAnnotation(Annotation annotation) { | |
132 CodeLineAnnotation data = annotation.data; | |
133 return { | |
134 'id': annotation.id, | |
135 'codeOffset': annotation.codeOffset, | |
136 'title': annotation.title, | |
137 'data': data.toJson(this), | |
138 }; | |
139 } | |
140 | |
141 Annotation decodeAnnotation(Map json) { | |
142 return new Annotation( | |
143 json['id'], | |
144 json['codeOffset'], | |
145 json['title'], | |
146 data: CodeLineAnnotation.fromJson(json['data'], this)); | |
147 } | |
148 | |
149 @override | |
150 decodeLineAnnotation(json) { | |
151 if (json != null) { | |
152 return CodeSource.fromJson(json); | |
153 } | |
154 return null; | |
155 } | |
156 | |
157 @override | |
158 encodeLineAnnotation(CodeSource lineAnnotation) { | |
159 if (lineAnnotation != null) { | |
160 return lineAnnotation.toJson(); | |
161 } | |
162 return null; | |
163 } | |
164 } | |
165 | |
130 /// The structured output of a compilation. | 166 /// The structured output of a compilation. |
131 class AnnotatedOutput { | 167 class AnnotatedOutput { |
132 final String filename; | 168 final String filename; |
133 final List<String> options; | 169 final List<String> options; |
134 final OutputStructure structure; | 170 final OutputStructure structure; |
135 final String coverage; | 171 final String coverage; |
136 | 172 |
137 AnnotatedOutput(this.filename, this.options, this.structure, this.coverage); | 173 AnnotatedOutput(this.filename, this.options, this.structure, this.coverage); |
138 | 174 |
139 List<CodeLine> get codeLines => structure.lines; | 175 List<CodeLine> get codeLines => structure.lines; |
140 | 176 |
141 Map toJson() { | 177 Map toJson() { |
142 return { | 178 return { |
143 'filename': filename, | 179 'filename': filename, |
144 'options': options, | 180 'options': options, |
145 'structure': structure.toJson(), | 181 'structure': structure.toJson(const CodeLineAnnotationJsonStrategy()), |
146 'coverage': coverage, | 182 'coverage': coverage, |
147 }; | 183 }; |
148 } | 184 } |
149 | 185 |
150 static AnnotatedOutput fromJson(Map json) { | 186 static AnnotatedOutput fromJson(Map json) { |
151 String filename = json['filename']; | 187 String filename = json['filename']; |
152 List<String> options = json['options']; | 188 List<String> options = json['options']; |
153 OutputStructure structure = OutputStructure.fromJson(json['structure']); | 189 OutputStructure structure = OutputStructure.fromJson( |
190 json['structure'], const CodeLineAnnotationJsonStrategy()); | |
154 String coverage = json['coverage']; | 191 String coverage = json['coverage']; |
155 return new AnnotatedOutput(filename, options, structure, coverage); | 192 return new AnnotatedOutput(filename, options, structure, coverage); |
156 } | 193 } |
157 | 194 |
158 static AnnotatedOutput loadOutput(filename) { | 195 static AnnotatedOutput loadOutput(filename) { |
159 AnnotatedOutput output = AnnotatedOutput.fromJson( | 196 AnnotatedOutput output = AnnotatedOutput.fromJson( |
160 JSON.decode(new File(filename).readAsStringSync())); | 197 JSON.decode(new File(filename).readAsStringSync())); |
161 print('Output loaded from $filename'); | 198 print('Output loaded from $filename'); |
162 return output; | 199 return output; |
163 } | 200 } |
164 | 201 |
165 static void saveOutput(AnnotatedOutput output, String filename) { | 202 static void saveOutput(AnnotatedOutput output, String filename) { |
166 if (filename != null) { | 203 if (filename != null) { |
167 new File(filename).writeAsStringSync( | 204 new File(filename).writeAsStringSync( |
168 const JsonEncoder.withIndent(' ').convert(output.toJson())); | 205 const JsonEncoder.withIndent(' ').convert(output.toJson())); |
169 print('Output saved in $filename'); | 206 print('Output saved in $filename'); |
170 } | 207 } |
171 } | 208 } |
172 } | 209 } |
173 | 210 |
174 void outputDiffView( | 211 void outputDiffView( |
175 String out, | 212 String out, |
176 List<AnnotatedOutput> outputs, | 213 List<AnnotatedOutput> outputs, |
177 List<DiffBlock> blocks, | 214 List<DiffBlock> blocks, |
178 {bool addAnnotations: true}) { | 215 {bool showMarkers: true, |
216 bool showSourceMapped: true}) { | |
179 assert(outputs[0].filename == outputs[1].filename); | 217 assert(outputs[0].filename == outputs[1].filename); |
180 bool usePre = true; | 218 bool usePre = true; |
181 | 219 |
182 StringBuffer sb = new StringBuffer(); | 220 StringBuffer sb = new StringBuffer(); |
183 sb.write(''' | 221 sb.write(''' |
184 <html> | 222 <html> |
185 <head> | 223 <head> |
186 <title>Diff for ${outputs[0].filename}</title> | 224 <title>Diff for ${outputs[0].filename}</title> |
187 <style> | 225 <style> |
188 .lineNumber { | 226 .${ClassNames.lineNumber} { |
189 font-size: smaller; | 227 font-size: smaller; |
190 color: #888; | 228 color: #888; |
191 } | 229 } |
192 .comment { | 230 .${ClassNames.comment} { |
193 font-size: smaller; | 231 font-size: smaller; |
194 color: #888; | 232 color: #888; |
195 font-family: initial; | 233 font-family: initial; |
196 } | 234 } |
197 .header { | 235 .${ClassNames.header} { |
198 position: fixed; | 236 position: fixed; |
199 width: 100%; | 237 width: 100%; |
200 background-color: #FFFFFF; | 238 background-color: #FFFFFF; |
201 left: 0px; | 239 left: 0px; |
202 top: 0px; | 240 top: 0px; |
203 height: 42px; | 241 height: 42px; |
204 z-index: 1000; | 242 z-index: 1000; |
205 } | 243 } |
206 .header-table { | 244 .${ClassNames.headerTable} { |
207 width: 100%; | 245 width: 100%; |
208 background-color: #400000; | 246 background-color: #400000; |
209 color: #FFFFFF; | 247 color: #FFFFFF; |
210 border-spacing: 0px; | 248 border-spacing: 0px; |
211 } | 249 } |
212 .header-column { | 250 .${ClassNames.headerColumn} { |
213 width: 34%; | |
214 } | 251 } |
215 .legend { | 252 .${ClassNames.legend} { |
216 padding: 2px; | 253 padding: 2px; |
217 } | 254 } |
218 .table { | 255 .${ClassNames.buttons} { |
256 position: fixed; | |
257 right: 0px; | |
258 top: 0px; | |
259 width: 220px; | |
260 background-color: #FFFFFF; | |
261 border: 1px solid #C0C0C0; | |
262 z-index: 2000; | |
263 } | |
264 .${ClassNames.table} { | |
219 position: absolute; | 265 position: absolute; |
220 left: 0px; | 266 left: 0px; |
221 top: 42px; | 267 top: 42px; |
222 width: 100%; | 268 width: 100%; |
223 border-spacing: 0px; | 269 border-spacing: 0px; |
224 } | 270 } |
225 .cell { | 271 .${ClassNames.cell}, |
226 max-width: 500px; | 272 .${ClassNames.innerCell}, |
273 .${ClassNames.mainDart}, | |
274 .${ClassNames.inlinedDart} { | |
227 overflow-y: hidden; | 275 overflow-y: hidden; |
228 vertical-align: top; | 276 vertical-align: top; |
229 border-top: 1px solid #F0F0F0; | |
230 border-left: 1px solid #F0F0F0; | |
231 '''); | 277 '''); |
232 if (usePre) { | 278 if (usePre) { |
233 sb.write(''' | 279 sb.write(''' |
234 overflow-x: hidden; | 280 overflow-x: hidden; |
235 white-space: pre-wrap; | 281 white-space: pre-wrap; |
236 '''); | 282 '''); |
237 } else { | 283 } else { |
238 sb.write(''' | 284 sb.write(''' |
239 overflow-x: hidden; | 285 overflow-x: hidden; |
240 padding-left: 100px; | 286 padding-left: 100px; |
241 text-indent: -100px; | 287 text-indent: -100px; |
242 '''); | 288 '''); |
243 } | 289 } |
244 sb.write(''' | 290 sb.write(''' |
245 font-family: monospace; | 291 font-family: monospace; |
246 padding: 0px; | 292 padding: 0px; |
247 } | 293 } |
248 .corresponding1 { | 294 .${ClassNames.cell} { |
295 border-top: 1px solid #F0F0F0; | |
296 border-left: 1px solid #C0C0C0; | |
297 } | |
298 .${ClassNames.innerCell} { | |
299 /*border-top: 1px solid #F8F8F8;*/ | |
300 width: 50%; | |
301 max-width: 250px; | |
302 } | |
303 .${ClassNames.corresponding(false)} { | |
249 background-color: #FFFFE0; | 304 background-color: #FFFFE0; |
250 } | 305 } |
251 .corresponding2 { | 306 .${ClassNames.corresponding(true)} { |
252 background-color: #EFEFD0; | 307 background-color: #EFEFD0; |
253 } | 308 } |
254 .identical1 { | 309 .${ClassNames.identical(false)} { |
255 background-color: #E0F0E0; | 310 background-color: #E0F0E0; |
256 } | 311 } |
257 .identical2 { | 312 .${ClassNames.identical(true)} { |
258 background-color: #C0E0C0; | 313 background-color: #C0E0C0; |
259 } | 314 } |
260 .line { | 315 .${ClassNames.line} { |
261 padding-left: 7em; | 316 padding-left: 7em; |
262 text-indent: -7em; | 317 text-indent: -7em; |
263 margin: 0px; | 318 margin: 0px; |
264 } | 319 } |
265 .column0 { | 320 .${ClassNames.column(column_js0)} { |
321 max-width: 500px; | |
322 width: 500px; | |
266 } | 323 } |
267 .column1 { | 324 .${ClassNames.column(column_js1)} { |
325 max-width: 500px; | |
326 width: 500px; | |
268 } | 327 } |
269 .column2 { | 328 .${ClassNames.column(column_dart)} { |
329 max-width: 300px; | |
330 width: 300px; | |
331 } | |
332 .${ClassNames.colored(0)} { | |
333 color: #FF0000; | |
334 } | |
335 .${ClassNames.colored(1)} { | |
336 color: #C0C000; | |
337 } | |
338 .${ClassNames.colored(2)} { | |
339 color: #008000; | |
340 } | |
341 .${ClassNames.colored(3)} { | |
342 color: #00C0C0; | |
343 } | |
344 .${ClassNames.withSourceInfo} { | |
345 border: solid 1px #FF8080; | |
346 } | |
347 .${ClassNames.withoutSourceInfo} { | |
348 background-color: #8080FF; | |
349 } | |
350 .${ClassNames.additionalSourceInfo} { | |
351 border: solid 1px #80FF80; | |
352 } | |
353 .${ClassNames.unusedSourceInfo} { | |
354 border: solid 1px #8080FF; | |
355 } | |
356 .${ClassNames.mainDart} { | |
357 } | |
358 .${ClassNames.inlinedDart} { | |
359 } | |
360 '''); | |
361 for (int i = 0; i < HUE_COUNT; i++) { | |
362 sb.write(''' | |
363 .${ClassNames.sourceMappingIndex(i)} { | |
364 background-color: ${toColorCss(i)}; | |
365 } | |
366 '''); | |
367 } | |
368 sb.write(''' | |
369 .${ClassNames.sourceMapped} { | |
370 ${showSourceMapped ? '' : 'display: none;'} | |
371 } | |
372 .${ClassNames.sourceMapping} { | |
373 ${showSourceMapped ? '' : 'border: 0px;'} | |
374 ${showSourceMapped ? '' : 'background-color: transparent;'} | |
375 } | |
376 .${ClassNames.markers} { | |
377 ${showMarkers ? '' : 'display: none;'} | |
378 } | |
379 .${ClassNames.marker} { | |
380 ${showMarkers ? '' : 'border: 0px;'} | |
381 ${showMarkers ? '' : 'background-color: transparent;'} | |
270 } | 382 } |
271 </style> | 383 </style> |
384 <script> | |
385 function isChecked(name) { | |
386 var box = document.getElementById('box-' + name); | |
387 return box.checked; | |
388 } | |
389 function toggleDisplay(name) { | |
390 var checked = isChecked(name); | |
391 var styleSheet = document.styleSheets[0]; | |
392 for (var index = 0; index < styleSheet.cssRules.length; index++) { | |
393 var cssRule = styleSheet.cssRules[index]; | |
394 if (cssRule.selectorText == '.' + name) { | |
395 if (checked) { | |
396 cssRule.style.removeProperty('display'); | |
397 } else { | |
398 cssRule.style.display = 'none'; | |
399 } | |
400 } | |
401 } | |
402 return checked; | |
403 } | |
404 function toggle${ClassNames.sourceMapped}() { | |
405 var checked = toggleDisplay('${ClassNames.sourceMapped}'); | |
406 toggleAnnotations(checked, '${ClassNames.sourceMapping}'); | |
407 } | |
408 function toggle${ClassNames.markers}() { | |
409 var checked = toggleDisplay('${ClassNames.markers}'); | |
410 toggleAnnotations(checked, '${ClassNames.marker}'); | |
411 } | |
412 function toggleAnnotations(show, name) { | |
413 var styleSheet = document.styleSheets[0]; | |
414 for (var index = 0; index < styleSheet.cssRules.length; index++) { | |
415 var cssRule = styleSheet.cssRules[index]; | |
416 if (cssRule.selectorText == '.' + name) { | |
417 if (show) { | |
418 cssRule.style.removeProperty('border'); | |
419 cssRule.style.removeProperty('background-color'); | |
420 } else { | |
421 cssRule.style.border = '0px'; | |
422 cssRule.style.backgroundColor = 'transparent'; | |
423 } | |
424 } | |
425 } | |
426 } | |
427 </script> | |
272 </head> | 428 </head> |
273 <body>'''); | 429 <body>'''); |
274 | 430 |
275 sb.write(''' | 431 sb.write(''' |
276 <div class="header"> | 432 <div class="${ClassNames.header}"> |
277 <table class="header-table"><tr> | 433 <div class="${ClassNames.legend}"> |
278 <td class="header-column">[${outputs[0].options.join(',')}]</td> | 434 <span class="${ClassNames.identical(false)}"> </span> |
279 <td class="header-column">[${outputs[1].options.join(',')}]</td> | 435 <span class="${ClassNames.identical(true)}"> </span> |
280 <td class="header-column">Dart code</td> | |
281 </tr></table> | |
282 <div class="legend"> | |
283 <span class="identical1"> </span> | |
284 <span class="identical2"> </span> | |
285 identical blocks | 436 identical blocks |
286 <span class="corresponding1"> </span> | 437 <span class="${ClassNames.corresponding(false)}"> </span> |
287 <span class="corresponding2"> </span> | 438 <span class="${ClassNames.corresponding(true)}"> </span> |
288 corresponding blocks | 439 corresponding blocks |
289 '''); | 440 '''); |
290 | 441 |
291 if (addAnnotations) { | 442 sb.write(''' |
292 sb.write(''' | 443 <span class="${ClassNames.markers}"> |
293 <span style="$WITH_SOURCE_INFO_STYLE"> </span> | 444 <span class="${ClassNames.withSourceInfo}"> </span> |
294 <span title="'offset with source information' means that source information | 445 <span title="'offset with source information' means that source information |
295 is available for an offset which is expected to have a source location | 446 is available for an offset which is expected to have a source location |
296 attached. This offset has source information as intended."> | 447 attached. This offset has source information as intended."> |
297 offset with source information</span> | 448 offset with source information</span> |
298 <span style="$WITHOUT_SOURCE_INFO_STYLE"> </span> | 449 <span class="${ClassNames.withoutSourceInfo}"> </span> |
299 <span title="'offset without source information' means that _no_ source | 450 <span title="'offset without source information' means that _no_ source |
300 information is available for an offset which was expected to have a source | 451 information is available for an offset which was expected to have a source |
301 location attached. Source information must be found for this offset."> | 452 location attached. Source information must be found for this offset."> |
302 offset without source information</span> | 453 offset without source information</span> |
303 <span style="$ADDITIONAL_SOURCE_INFO_STYLE"> </span> | 454 <span class="${ClassNames.additionalSourceInfo}"> </span> |
304 <span title="'offset with unneeded source information' means that a source | 455 <span title="'offset with unneeded source information' means that a source |
305 location was attached to an offset which was _not_ expected to have a source | 456 location was attached to an offset which was _not_ expected to have a source |
306 location attached. The source location should be removed from this offset."> | 457 location attached. The source location should be removed from this offset."> |
307 offset with unneeded source information</span> | 458 offset with unneeded source information</span> |
308 <span style="$UNUSED_SOURCE_INFO_STYLE"> </span> | 459 <span class="${ClassNames.unusedSourceInfo}"> </span> |
309 <span title="'offset with unused source information' means that source | 460 <span title="'offset with unused source information' means that source |
310 information is available for an offset which is _not_ expected to have a source | 461 information is available for an offset which is _not_ expected to have a source |
311 location attached. This source information _could_ be used by a parent AST node | 462 location attached. This source information _could_ be used by a parent AST node |
312 offset that is an 'offset without source information'."> | 463 offset that is an 'offset without source information'."> |
313 offset with unused source information</span> | 464 offset with unused source information</span> |
465 </span> | |
466 <span class="${ClassNames.sourceMapped}"> | |
314 '''); | 467 '''); |
468 for (int i = 0; i < HUE_COUNT; i++) { | |
469 sb.write(''' | |
470 <span class="${ClassNames.sourceMappingIndex(i)}"> </span>'''); | |
315 } | 471 } |
316 | |
317 sb.write(''' | 472 sb.write(''' |
318 </div></div> | 473 <span title="JavaScript offsets and their corresponding Dart Code offset |
319 <table class="table"> | 474 as mapped through source-maps."> |
475 mapped source locations</span> | |
476 </span> | |
320 '''); | 477 '''); |
321 | 478 |
322 void addCell(String content) { | |
323 sb.write(''' | |
324 <td class="cell"><pre> | |
325 '''); | |
326 sb.write(content); | |
327 sb.write(''' | |
328 </pre></td> | |
329 '''); | |
330 } | |
331 | 479 |
332 /// Marker to alternate output colors. | 480 /// Marker to alternate output colors. |
333 bool alternating = false; | 481 bool alternating = false; |
334 | 482 |
335 List<HtmlPrintContext> printContexts = <HtmlPrintContext>[]; | 483 List<HtmlPrintContext> printContexts = <HtmlPrintContext>[]; |
336 for (int i = 0; i < 2; i++) { | 484 for (int i = 0; i < 2; i++) { |
337 int lineNoWidth; | 485 int lineNoWidth; |
338 if (outputs[i].codeLines.isNotEmpty) { | 486 if (outputs[i].codeLines.isNotEmpty) { |
339 lineNoWidth = '${outputs[i].codeLines.last.lineNo + 1}'.length; | 487 lineNoWidth = '${outputs[i].codeLines.last.lineNo + 1}'.length; |
340 } | 488 } |
341 printContexts.add(new HtmlPrintContext(lineNoWidth: lineNoWidth)); | 489 printContexts.add(new HtmlPrintContext( |
490 lineNoWidth: lineNoWidth, | |
491 getAnnotationData: getAnnotationData, | |
492 getLineData: getLineData)); | |
342 } | 493 } |
343 | 494 |
495 Set<DiffColumn> allColumns = new Set<DiffColumn>(); | |
496 for (DiffBlock block in blocks) { | |
497 allColumns.addAll(block.columns); | |
498 } | |
499 | |
500 List<DiffColumn> columns = [column_js0, column_js1, column_dart] | |
501 .where((c) => allColumns.contains(c)).toList(); | |
502 | |
503 sb.write(''' | |
504 </div> | |
505 <table class="${ClassNames.headerTable}"><tr>'''); | |
506 for (DiffColumn column in columns) { | |
507 sb.write(''' | |
508 <td class="${ClassNames.headerColumn} ${ClassNames.column(column)}">'''); | |
509 if (column.type == 'js') { | |
510 sb.write('''[${outputs[column.index].options.join(',')}]'''); | |
511 } else { | |
512 sb.write('''Dart code'''); | |
513 } | |
514 sb.write('''</td>'''); | |
515 } | |
516 | |
517 sb.write(''' | |
518 </tr></table> | |
519 </div> | |
520 <table class="${ClassNames.table}"> | |
521 '''); | |
522 | |
344 for (DiffBlock block in blocks) { | 523 for (DiffBlock block in blocks) { |
345 String className; | 524 String className; |
346 switch (block.kind) { | 525 switch (block.kind) { |
347 case DiffKind.UNMATCHED: | 526 case DiffKind.UNMATCHED: |
348 className = 'cell'; | 527 className = '${ClassNames.cell}'; |
349 break; | 528 break; |
350 case DiffKind.MATCHING: | 529 case DiffKind.MATCHING: |
351 className = 'cell corresponding${alternating ? '1' : '2'}'; | 530 className = |
531 '${ClassNames.cell} ${ClassNames.corresponding(alternating)}'; | |
352 alternating = !alternating; | 532 alternating = !alternating; |
353 break; | 533 break; |
354 case DiffKind.IDENTICAL: | 534 case DiffKind.IDENTICAL: |
355 className = 'cell identical${alternating ? '1' : '2'}'; | 535 className = |
536 '${ClassNames.cell} ${ClassNames.identical(alternating)}'; | |
356 alternating = !alternating; | 537 alternating = !alternating; |
357 break; | 538 break; |
358 } | 539 } |
359 sb.write('<tr>'); | 540 sb.write('<tr>'); |
360 for (int index = 0; index < 3; index++) { | 541 for (DiffColumn column in columns) { |
361 sb.write('''<td class="$className column$index">'''); | 542 sb.write('''<td class="$className ${ClassNames.column(column)}">'''); |
362 List<HtmlPart> lines = block.getColumn(index); | 543 HtmlPrintContext context = new HtmlPrintContext( |
363 if (lines.isNotEmpty) { | 544 lineNoWidth: 4, |
364 for (HtmlPart line in lines) { | 545 includeAnnotation: (Annotation annotation) { |
365 sb.write('<p class="line">'); | 546 CodeLineAnnotation data = annotation.data; |
366 if (index < printContexts.length) { | 547 return data.annotationType == AnnotationType.WITH_SOURCE_INFO || |
367 line.printHtmlOn(sb, printContexts[index]); | 548 data.annotationType == AnnotationType.ADDITIONAL_SOURCE_INFO; |
368 } else { | 549 }, |
369 line.printHtmlOn(sb, new HtmlPrintContext()); | 550 getAnnotationData: getAnnotationData, |
370 } | 551 getLineData: getLineData); |
371 sb.write('</p>'); | 552 if (column.type == 'js') { |
372 } | 553 context = printContexts[column.index]; |
373 } | 554 } |
555 block.printHtmlOn(column, sb, context); | |
374 sb.write('''</td>'''); | 556 sb.write('''</td>'''); |
375 } | 557 } |
376 sb.write('</tr>'); | 558 sb.write('</tr>'); |
377 } | 559 } |
378 | 560 |
379 sb.write('''</tr><tr>'''); | 561 sb.write('''</tr><tr>'''); |
380 | 562 |
381 addCell(outputs[0].coverage); | 563 for (DiffColumn column in columns) { |
382 addCell(outputs[1].coverage); | 564 sb.write(''' |
565 <td class="${ClassNames.cell} ${ClassNames.column(column)}"><pre>'''); | |
566 if (column.type == 'js') { | |
567 sb.write(outputs[column.index].coverage); | |
568 } | |
569 sb.write('''</td>'''); | |
570 } | |
383 | 571 |
384 sb.write(''' | 572 sb.write(''' |
385 </table> | 573 </table> |
574 <div class="${ClassNames.buttons}"> | |
575 <input type="checkbox" id="box-${ClassNames.column(column_js0)}" | |
576 onclick="javascript:toggleDisplay('${ClassNames.column(column_js0)}')" | |
577 checked> | |
578 Left JavaScript code<br/> | |
579 | |
580 <input type="checkbox" id="box-${ClassNames.column(column_js1)}" | |
581 onclick="javascript:toggleDisplay('${ClassNames.column(column_js1)}')" | |
582 checked> | |
583 Right JavaScript code<br/> | |
584 | |
585 <input type="checkbox" id="box-${ClassNames.column(column_dart)}" | |
586 onclick="javascript:toggleDisplay('${ClassNames.column(column_dart)}')" | |
587 checked> | |
588 <span title="Show column with Dart code corresponding to the block."> | |
589 Dart code</span><br/> | |
590 | |
591 <input type="checkbox" id="box-${ClassNames.inlinedDart}" | |
592 onclick="javascript:toggleDisplay('${ClassNames.inlinedDart}')" checked> | |
593 <span title="Show Dart code inlined into the block."> | |
594 Inlined Dart code</span><br/> | |
595 | |
596 <input type="checkbox" id="box-${ClassNames.markers}" | |
597 onclick="javascript:toggle${ClassNames.markers}()" | |
598 ${showMarkers ? 'checked' : ''}> | |
599 <span title="Show markers for JavaScript offsets with source information."> | |
600 Source information markers</span><br/> | |
601 | |
602 <input type="checkbox" id="box-${ClassNames.sourceMapped}" | |
603 onclick="javascript:toggle${ClassNames.sourceMapped}()" | |
604 ${showSourceMapped ? 'checked' : ''}> | |
605 <span title="Show line-per-line mappings of JavaScript to Dart code."> | |
606 Source mapped Dart code</span><br/> | |
607 </div> | |
386 </body> | 608 </body> |
387 </html> | 609 </html> |
388 '''); | 610 '''); |
389 | 611 |
390 new File(out).writeAsStringSync(sb.toString()); | 612 new File(out).writeAsStringSync(sb.toString()); |
391 print('Diff generated in $out'); | 613 print('Diff generated in $out'); |
392 } | 614 } |
393 | 615 |
394 class CodeLinesResult { | 616 class CodeLinesResult { |
395 final List<CodeLine> codeLines; | 617 final List<CodeLine> codeLines; |
396 final Coverage coverage; | 618 final Coverage coverage; |
397 final Map<int, Element> elementMap; | 619 final Map<int, Element> elementMap; |
398 final SourceFileManager sourceFileManager; | 620 final SourceFileManager sourceFileManager; |
399 | 621 final CodeSources codeSources; |
400 CodeLinesResult(this.codeLines, this.coverage, | 622 |
401 this.elementMap, this.sourceFileManager); | 623 CodeLinesResult( |
624 this.codeLines, | |
625 this.coverage, | |
626 this.elementMap, | |
627 this.sourceFileManager, | |
628 this.codeSources); | |
629 } | |
630 | |
631 class CodeSources { | |
632 Map<Element, CodeSource> codeSourceMap = <Element, CodeSource>{}; | |
633 Map<Uri, Map<Interval, CodeSource>> uriCodeSourceMap = | |
634 <Uri, Map<Interval, CodeSource>>{}; | |
635 | |
636 CodeSources( | |
637 SourceMapProcessor processor, | |
638 SourceMaps sourceMaps) { | |
639 | |
640 CodeSource computeCodeSource(Element element) { | |
641 return codeSourceMap.putIfAbsent(element, () { | |
642 CodeSource codeSource = codeSourceFromElement(element); | |
643 if (codeSource.begin != null) { | |
644 Interval interval = new Interval(codeSource.begin, codeSource.end); | |
645 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.
| |
646 if (intervals == null) { | |
647 intervals = <Interval, CodeSource>{}; | |
648 uriCodeSourceMap[codeSource.uri] = intervals; | |
649 } else { | |
650 for (Interval existingInterval in intervals.keys.toList()) { | |
651 if (existingInterval.contains(interval.from)) { | |
652 CodeSource existingCodeSource = intervals[existingInterval]; | |
653 intervals.remove(existingInterval); | |
654 if (existingInterval.from < interval.from) { | |
655 Interval preInterval = new Interval( | |
656 existingInterval.from, interval.from); | |
657 intervals[preInterval] = existingCodeSource; | |
658 } | |
659 if (interval.to < existingInterval.to) { | |
660 Interval postInterval = new Interval( | |
661 interval.to, existingInterval.to); | |
662 intervals[postInterval] = existingCodeSource; | |
663 } | |
664 } | |
665 } | |
666 } | |
667 intervals[interval] = codeSource; | |
668 } | |
669 if (element is ClassElement) { | |
670 element.forEachLocalMember((Element member) { | |
671 codeSource.members.add(computeCodeSource(member)); | |
672 }); | |
673 element.implementation.forEachLocalMember((Element member) { | |
674 codeSource.members.add(computeCodeSource(member)); | |
675 }); | |
676 } else if (element is MemberElement) { | |
677 element.nestedClosures.forEach((Element closure) { | |
678 codeSource.members.add(computeCodeSource(closure)); | |
679 }); | |
680 } | |
681 return codeSource; | |
682 }); | |
683 } | |
684 | |
685 for (LibraryElement library in sourceMaps.compiler.libraryLoader.libraries) { | |
686 library.forEachLocalMember(computeCodeSource); | |
687 library.implementation.forEachLocalMember(computeCodeSource); | |
688 } | |
689 | |
690 uriCodeSourceMap.forEach((Uri uri, Map<Interval, CodeSource> intervals) { | |
691 List<Interval> sortedKeys = intervals.keys.toList()..sort( | |
692 (i1, i2) => i1.from.compareTo(i2.from)); | |
693 Map<Interval, CodeSource> sortedintervals = <Interval, CodeSource>{}; | |
694 sortedKeys.forEach((Interval interval) { | |
695 sortedintervals[interval] = intervals[interval]; | |
696 }); | |
697 uriCodeSourceMap[uri] = sortedintervals; | |
698 }); | |
699 } | |
700 | |
701 CodeSource sourceLocationToCodeSource(SourceLocation sourceLocation) { | |
702 Map<Interval, CodeSource> intervals = | |
703 uriCodeSourceMap[sourceLocation.sourceUri]; | |
704 if (intervals == null) { | |
705 print('No code source for $sourceLocation(${sourceLocation.offset})'); | |
706 print(' -- no intervals for ${sourceLocation.sourceUri}'); | |
707 return null; | |
708 } | |
709 for (Interval interval in intervals.keys) { | |
710 if (interval.contains(sourceLocation.offset)) { | |
711 return intervals[interval]; | |
712 } | |
713 } | |
714 print('No code source for $sourceLocation(${sourceLocation.offset})'); | |
715 intervals.forEach((k, v) => print(' $k: ${v.name}')); | |
716 return null; | |
717 } | |
402 } | 718 } |
403 | 719 |
404 /// Compute [CodeLine]s and [Coverage] for [filename] using the given [options]. | 720 /// Compute [CodeLine]s and [Coverage] for [filename] using the given [options]. |
405 Future<CodeLinesResult> computeCodeLines( | 721 Future<CodeLinesResult> computeCodeLines( |
406 List<String> options, | 722 List<String> options, |
407 String filename, | 723 String filename) async { |
408 {bool addAnnotations: true}) async { | |
409 SourceMapProcessor processor = new SourceMapProcessor(filename); | 724 SourceMapProcessor processor = new SourceMapProcessor(filename); |
410 SourceMaps sourceMaps = | 725 SourceMaps sourceMaps = |
411 await processor.process(options, perElement: true, forMain: true); | 726 await processor.process(options, perElement: true, forMain: true); |
412 | 727 |
413 const int WITH_SOURCE_INFO = 0; | 728 CodeSources codeSources = new CodeSources(processor, sourceMaps); |
414 const int WITHOUT_SOURCE_INFO = 1; | |
415 const int ADDITIONAL_SOURCE_INFO = 2; | |
416 const int UNUSED_SOURCE_INFO = 3; | |
417 | 729 |
418 SourceMapInfo info = sourceMaps.mainSourceMapInfo; | 730 SourceMapInfo info = sourceMaps.mainSourceMapInfo; |
419 | 731 |
732 int nextAnnotationId = 0; | |
420 List<CodeLine> codeLines; | 733 List<CodeLine> codeLines; |
421 Coverage coverage = new Coverage(); | 734 Coverage coverage = new Coverage(); |
422 List<Annotation> annotations = <Annotation>[]; | 735 Map<int, List<CodeLineAnnotation>> codeLineAnnotationMap = |
423 | 736 <int, List<CodeLineAnnotation>>{}; |
424 void addAnnotation(int id, int offset, String title) { | 737 |
425 annotations.add(new Annotation(id, offset, title)); | 738 /// Create a [CodeLineAnnotation] for [codeOffset]. |
739 void addCodeLineAnnotation( | |
740 {AnnotationType annotationType, | |
741 int codeOffset, | |
742 List<SourceLocation> locations: const <SourceLocation>[], | |
743 String stepInfo}) { | |
744 if (annotationType == AnnotationType.WITHOUT_SOURCE_INFO || | |
745 annotationType == AnnotationType.UNUSED_SOURCE_INFO) { | |
746 locations = []; | |
747 } | |
748 List<CodeLocation> codeLocations = locations | |
749 .map((l) => new CodeLocation(l.sourceUri, l.sourceName, l.offset)) | |
750 .toList(); | |
751 List<CodeSource> codeSourceList = locations | |
752 .map(codeSources.sourceLocationToCodeSource) | |
753 .where((c) => c != null) | |
754 .toList(); | |
755 CodeLineAnnotation data = new CodeLineAnnotation( | |
756 annotationId: nextAnnotationId++, | |
757 annotationType: annotationType, | |
758 codeLocations: codeLocations, | |
759 codeSources: codeSourceList, | |
760 stepInfo: stepInfo); | |
761 codeLineAnnotationMap.putIfAbsent( | |
762 codeOffset, () => <CodeLineAnnotation>[]).add(data); | |
426 } | 763 } |
427 | 764 |
428 String code = info.code; | 765 String code = info.code; |
429 TraceGraph graph = createTraceGraph(info, coverage); | 766 TraceGraph graph = createTraceGraph(info, coverage); |
430 if (addAnnotations) { | 767 |
431 Set<js.Node> mappedNodes = new Set<js.Node>(); | 768 Set<js.Node> mappedNodes = new Set<js.Node>(); |
432 | 769 |
433 void addSourceLocations( | 770 /// Add an annotation for [codeOffset] pointing to [locations]. |
434 int kind, int offset, List<SourceLocation> locations, String prefix) { | 771 void addSourceLocations( |
435 | 772 {AnnotationType annotationType, |
436 addAnnotation(kind, offset, | 773 int codeOffset, |
437 '${prefix}${locations | 774 List<SourceLocation> locations, |
438 .where((l) => l != null) | 775 String stepInfo}) { |
439 .map((l) => l.shortText) | 776 locations = locations.where((l) => l != null).toList(); |
440 .join('\n')}'); | 777 addCodeLineAnnotation( |
441 } | 778 annotationType: annotationType, |
442 | 779 codeOffset: codeOffset, |
443 bool addSourceLocationsForNode(int kind, js.Node node, String prefix) { | 780 stepInfo: stepInfo, |
444 Map<int, List<SourceLocation>> locations = info.nodeMap[node]; | 781 locations: locations); |
445 if (locations == null || locations.isEmpty) { | 782 } |
446 return false; | 783 |
447 } | 784 /// Add annotations for all mappings created for [node]. |
448 locations.forEach( | 785 bool addSourceLocationsForNode( |
449 (int offset, List<SourceLocation> locations) { | 786 {AnnotationType annotationType, |
450 addSourceLocations(kind, offset, locations, | 787 js.Node node, |
451 '${prefix}\n${truncate(nodeToString(node), 80)}\n'); | 788 String stepInfo}) { |
452 }); | 789 Map<int, List<SourceLocation>> locations = info.nodeMap[node]; |
453 mappedNodes.add(node); | 790 if (locations == null || locations.isEmpty) { |
454 return true; | 791 return false; |
455 } | 792 } |
456 | 793 locations.forEach((int offset, List<SourceLocation> locations) { |
457 | 794 addSourceLocations( |
458 for (TraceStep step in graph.steps) { | 795 annotationType: annotationType, |
459 String title = '${step.id}:${step.kind}:${step.offset}'; | 796 codeOffset: offset, |
460 if (!addSourceLocationsForNode(WITH_SOURCE_INFO, step.node, title)) { | 797 locations: locations, |
461 int offset; | 798 stepInfo: stepInfo); |
462 if (options.contains(USE_NEW_SOURCE_INFO)) { | |
463 offset = step.offset.subexpressionOffset; | |
464 } else { | |
465 offset = info.jsCodePositions[step.node].startPosition; | |
466 } | |
467 if (offset != null) { | |
468 addAnnotation(WITHOUT_SOURCE_INFO, offset, title); | |
469 } | |
470 } | |
471 } | |
472 for (js.Node node in info.nodeMap.nodes) { | |
473 if (!mappedNodes.contains(node)) { | |
474 addSourceLocationsForNode(ADDITIONAL_SOURCE_INFO, node, ''); | |
475 } | |
476 } | |
477 SourceLocationCollector collector = new SourceLocationCollector(); | |
478 info.node.accept(collector); | |
479 collector.sourceLocations.forEach( | |
480 (js.Node node, List<SourceLocation> locations) { | |
481 if (!mappedNodes.contains(node)) { | |
482 int offset = info.jsCodePositions[node].startPosition; | |
483 addSourceLocations(UNUSED_SOURCE_INFO, offset, locations, ''); | |
484 } | |
485 }); | 799 }); |
486 } | 800 mappedNodes.add(node); |
487 | 801 return true; |
802 } | |
803 | |
804 // Add annotations based on trace steps. | |
805 for (TraceStep step in graph.steps) { | |
806 String stepInfo = '${step.id}:${step.kind}:${step.offset}'; | |
807 bool added = addSourceLocationsForNode( | |
808 annotationType: AnnotationType.WITH_SOURCE_INFO, | |
809 node: step.node, | |
810 stepInfo: stepInfo); | |
811 if (!added) { | |
812 int offset; | |
813 if (options.contains(USE_NEW_SOURCE_INFO)) { | |
814 offset = step.offset.subexpressionOffset; | |
815 } else { | |
816 offset = info.jsCodePositions[step.node].startPosition; | |
817 } | |
818 if (offset != null) { | |
819 addCodeLineAnnotation( | |
820 annotationType: AnnotationType.WITHOUT_SOURCE_INFO, | |
821 codeOffset: offset, | |
822 stepInfo: stepInfo); | |
823 } | |
824 } | |
825 } | |
826 | |
827 // Add additional annotations for mappings created for particular nodes. | |
828 for (js.Node node in info.nodeMap.nodes) { | |
829 if (!mappedNodes.contains(node)) { | |
830 addSourceLocationsForNode( | |
831 annotationType: AnnotationType.ADDITIONAL_SOURCE_INFO, | |
832 node: node); | |
833 } | |
834 } | |
835 | |
836 // Add annotations for unused source information associated with nodes. | |
837 SourceLocationCollector collector = new SourceLocationCollector(); | |
838 info.node.accept(collector); | |
839 collector.sourceLocations.forEach( | |
840 (js.Node node, List<SourceLocation> locations) { | |
841 if (!mappedNodes.contains(node)) { | |
842 int offset = info.jsCodePositions[node].startPosition; | |
843 addSourceLocations( | |
844 annotationType: AnnotationType.UNUSED_SOURCE_INFO, | |
845 codeOffset: offset, | |
846 locations: locations); | |
847 } | |
848 }); | |
849 | |
850 // Assign consecutive ids to source mappings. | |
851 int nextSourceMappedLocationIndex = 0; | |
852 List<Annotation> annotations = <Annotation>[]; | |
853 for (int codeOffset in codeLineAnnotationMap.keys.toList()..sort()) { | |
854 bool hasSourceMappedLocation = false; | |
855 for (CodeLineAnnotation data in codeLineAnnotationMap[codeOffset]) { | |
856 if (data.annotationType.isSourceMapped) { | |
857 data.sourceMappingIndex = nextSourceMappedLocationIndex; | |
858 hasSourceMappedLocation = true; | |
859 } | |
860 annotations.add(new Annotation( | |
861 data.annotationType.index, | |
862 codeOffset, | |
863 'id=${data.annotationId}', | |
864 data: data)); | |
865 } | |
866 if (hasSourceMappedLocation) { | |
867 nextSourceMappedLocationIndex++; | |
868 } | |
869 } | |
870 | |
871 // Associate JavaScript offsets with [Element]s. | |
488 StringSourceFile sourceFile = new StringSourceFile.fromName(filename, code); | 872 StringSourceFile sourceFile = new StringSourceFile.fromName(filename, code); |
489 Map<int, Element> elementMap = <int, Element>{}; | 873 Map<int, Element> elementMap = <int, Element>{}; |
490 sourceMaps.elementSourceMapInfos.forEach( | 874 sourceMaps.elementSourceMapInfos.forEach( |
491 (Element element, SourceMapInfo info) { | 875 (Element element, SourceMapInfo info) { |
492 CodePosition position = info.jsCodePositions[info.node]; | 876 CodePosition position = info.jsCodePositions[info.node]; |
493 elementMap[sourceFile.getLine(position.startPosition)] = element; | 877 elementMap[sourceFile.getLine(position.startPosition)] = element; |
494 }); | 878 }); |
495 | 879 |
496 codeLines = convertAnnotatedCodeToCodeLines( | 880 codeLines = convertAnnotatedCodeToCodeLines(code, annotations); |
497 code, | 881 return new CodeLinesResult( |
498 annotations, | 882 codeLines, coverage, elementMap, |
499 colorScheme: new CustomColorScheme( | 883 sourceMaps.sourceFileManager, |
500 single: (int id) { | 884 codeSources); |
501 if (id == WITH_SOURCE_INFO) { | |
502 return WITH_SOURCE_INFO_STYLE; | |
503 } else if (id == ADDITIONAL_SOURCE_INFO) { | |
504 return ADDITIONAL_SOURCE_INFO_STYLE; | |
505 } else if (id == UNUSED_SOURCE_INFO) { | |
506 return UNUSED_SOURCE_INFO_STYLE; | |
507 } | |
508 return WITHOUT_SOURCE_INFO_STYLE; | |
509 }, | |
510 multi: (List ids) { | |
511 if (ids.contains(WITH_SOURCE_INFO)) { | |
512 return WITH_SOURCE_INFO_STYLE; | |
513 } else if (ids.contains(ADDITIONAL_SOURCE_INFO)) { | |
514 return ADDITIONAL_SOURCE_INFO_STYLE; | |
515 } else if (ids.contains(UNUSED_SOURCE_INFO)) { | |
516 return UNUSED_SOURCE_INFO_STYLE; | |
517 } | |
518 return WITHOUT_SOURCE_INFO_STYLE; | |
519 } | |
520 )); | |
521 return new CodeLinesResult(codeLines, coverage, elementMap, | |
522 sourceMaps.sourceFileManager); | |
523 } | 885 } |
524 | 886 |
525 /// Visitor that computes a map from [js.Node]s to all attached source | 887 /// Visitor that computes a map from [js.Node]s to all attached source |
526 /// locations. | 888 /// locations. |
527 class SourceLocationCollector extends js.BaseVisitor { | 889 class SourceLocationCollector extends js.BaseVisitor { |
528 Map<js.Node, List<SourceLocation>> sourceLocations = | 890 Map<js.Node, List<SourceLocation>> sourceLocations = |
529 <js.Node, List<SourceLocation>>{}; | 891 <js.Node, List<SourceLocation>>{}; |
530 | 892 |
531 @override | 893 @override |
532 visitNode(js.Node node) { | 894 visitNode(js.Node node) { |
(...skipping 25 matching lines...) Expand all Loading... | |
558 AstElement astElement = element.implementation; | 920 AstElement astElement = element.implementation; |
559 kind = CodeKind.MEMBER; | 921 kind = CodeKind.MEMBER; |
560 uri = astElement.compilationUnit.script.resourceUri; | 922 uri = astElement.compilationUnit.script.resourceUri; |
561 name = computeElementNameForSourceMaps(astElement); | 923 name = computeElementNameForSourceMaps(astElement); |
562 if (astElement.hasNode) { | 924 if (astElement.hasNode) { |
563 begin = astElement.node.getBeginToken().charOffset; | 925 begin = astElement.node.getBeginToken().charOffset; |
564 end = astElement.node.getEndToken().charEnd; | 926 end = astElement.node.getEndToken().charEnd; |
565 } | 927 } |
566 } | 928 } |
567 return new CodeSource(kind, uri, name, begin, end); | 929 return new CodeSource(kind, uri, name, begin, end); |
568 } | 930 } |
931 | |
932 /// Create [LineData] that colors line numbers according to the [CodeSource]s | |
933 /// origin if available. | |
934 LineData getLineData(CodeSource lineAnnotation) { | |
935 if (lineAnnotation != null) { | |
936 return new LineData( | |
937 lineClass: ClassNames.line, | |
938 lineNumberClass: | |
939 '${ClassNames.lineNumber} ' | |
940 '${ClassNames.colored(lineAnnotation.hashCode % 4)}'); | |
941 } | |
942 return new LineData( | |
943 lineClass: ClassNames.line, | |
944 lineNumberClass: ClassNames.lineNumber); | |
945 } | |
946 | |
947 AnnotationData getAnnotationData(Iterable<Annotation> annotations, | |
948 {bool forSpan}) { | |
949 for (Annotation annotation in annotations) { | |
950 CodeLineAnnotation data = annotation.data; | |
951 if (data.annotationType.isSourceMapped) { | |
952 if (forSpan) { | |
953 int index = data.sourceMappingIndex; | |
954 return new AnnotationData( | |
955 tag: 'span', | |
956 properties: { | |
957 'class': | |
958 '${ClassNames.sourceMapping} ' | |
959 '${ClassNames.sourceMappingIndex(index % HUE_COUNT)}', | |
960 'title': 'index=$index', | |
961 }); | |
962 } else { | |
963 return new AnnotationData( | |
964 tag: 'span', | |
965 properties: { | |
966 'title': annotation.title, | |
967 'class': '${ClassNames.marker} ' | |
968 '${data.annotationType.className}'}); | |
969 } | |
970 } | |
971 } | |
972 if (forSpan) return null; | |
973 for (Annotation annotation in annotations) { | |
974 CodeLineAnnotation data = annotation.data; | |
975 if (data.annotationType == AnnotationType.UNUSED_SOURCE_INFO) { | |
976 return new AnnotationData( | |
977 tag: 'span', | |
978 properties: { | |
979 'title': annotation.title, | |
980 'class': '${ClassNames.marker} ' | |
981 '${data.annotationType.className}'}); | |
982 } | |
983 } | |
984 for (Annotation annotation in annotations) { | |
985 CodeLineAnnotation data = annotation.data; | |
986 if (data.annotationType == AnnotationType.WITHOUT_SOURCE_INFO) { | |
987 return new AnnotationData( | |
988 tag: 'span', | |
989 properties: { | |
990 'title': annotation.title, | |
991 'class': '${ClassNames.marker} ' | |
992 '${data.annotationType.className}'}); | |
993 } | |
994 } | |
995 return null; | |
996 } | |
OLD | NEW |