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.originalDart}, |
| 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.originalDart} { |
| 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 = |
| 646 uriCodeSourceMap[codeSource.uri]; |
| 647 if (intervals == null) { |
| 648 intervals = <Interval, CodeSource>{}; |
| 649 uriCodeSourceMap[codeSource.uri] = intervals; |
| 650 } else { |
| 651 for (Interval existingInterval in intervals.keys.toList()) { |
| 652 if (existingInterval.contains(interval.from)) { |
| 653 CodeSource existingCodeSource = intervals[existingInterval]; |
| 654 intervals.remove(existingInterval); |
| 655 if (existingInterval.from < interval.from) { |
| 656 Interval preInterval = new Interval( |
| 657 existingInterval.from, interval.from); |
| 658 intervals[preInterval] = existingCodeSource; |
| 659 } |
| 660 if (interval.to < existingInterval.to) { |
| 661 Interval postInterval = new Interval( |
| 662 interval.to, existingInterval.to); |
| 663 intervals[postInterval] = existingCodeSource; |
| 664 } |
| 665 } |
| 666 } |
| 667 } |
| 668 intervals[interval] = codeSource; |
| 669 } |
| 670 if (element is ClassElement) { |
| 671 element.forEachLocalMember((Element member) { |
| 672 codeSource.members.add(computeCodeSource(member)); |
| 673 }); |
| 674 element.implementation.forEachLocalMember((Element member) { |
| 675 codeSource.members.add(computeCodeSource(member)); |
| 676 }); |
| 677 } else if (element is MemberElement) { |
| 678 element.nestedClosures.forEach((Element closure) { |
| 679 codeSource.members.add(computeCodeSource(closure)); |
| 680 }); |
| 681 } |
| 682 return codeSource; |
| 683 }); |
| 684 } |
| 685 |
| 686 for (LibraryElement library in |
| 687 sourceMaps.compiler.libraryLoader.libraries) { |
| 688 library.forEachLocalMember(computeCodeSource); |
| 689 library.implementation.forEachLocalMember(computeCodeSource); |
| 690 } |
| 691 |
| 692 uriCodeSourceMap.forEach((Uri uri, Map<Interval, CodeSource> intervals) { |
| 693 List<Interval> sortedKeys = intervals.keys.toList()..sort( |
| 694 (i1, i2) => i1.from.compareTo(i2.from)); |
| 695 Map<Interval, CodeSource> sortedintervals = <Interval, CodeSource>{}; |
| 696 sortedKeys.forEach((Interval interval) { |
| 697 sortedintervals[interval] = intervals[interval]; |
| 698 }); |
| 699 uriCodeSourceMap[uri] = sortedintervals; |
| 700 }); |
| 701 } |
| 702 |
| 703 CodeSource sourceLocationToCodeSource(SourceLocation sourceLocation) { |
| 704 Map<Interval, CodeSource> intervals = |
| 705 uriCodeSourceMap[sourceLocation.sourceUri]; |
| 706 if (intervals == null) { |
| 707 print('No code source for $sourceLocation(${sourceLocation.offset})'); |
| 708 print(' -- no intervals for ${sourceLocation.sourceUri}'); |
| 709 return null; |
| 710 } |
| 711 for (Interval interval in intervals.keys) { |
| 712 if (interval.contains(sourceLocation.offset)) { |
| 713 return intervals[interval]; |
| 714 } |
| 715 } |
| 716 print('No code source for $sourceLocation(${sourceLocation.offset})'); |
| 717 intervals.forEach((k, v) => print(' $k: ${v.name}')); |
| 718 return null; |
| 719 } |
402 } | 720 } |
403 | 721 |
404 /// Compute [CodeLine]s and [Coverage] for [filename] using the given [options]. | 722 /// Compute [CodeLine]s and [Coverage] for [filename] using the given [options]. |
405 Future<CodeLinesResult> computeCodeLines( | 723 Future<CodeLinesResult> computeCodeLines( |
406 List<String> options, | 724 List<String> options, |
407 String filename, | 725 String filename) async { |
408 {bool addAnnotations: true}) async { | |
409 SourceMapProcessor processor = new SourceMapProcessor(filename); | 726 SourceMapProcessor processor = new SourceMapProcessor(filename); |
410 SourceMaps sourceMaps = | 727 SourceMaps sourceMaps = |
411 await processor.process(options, perElement: true, forMain: true); | 728 await processor.process(options, perElement: true, forMain: true); |
412 | 729 |
413 const int WITH_SOURCE_INFO = 0; | 730 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 | 731 |
418 SourceMapInfo info = sourceMaps.mainSourceMapInfo; | 732 SourceMapInfo info = sourceMaps.mainSourceMapInfo; |
419 | 733 |
| 734 int nextAnnotationId = 0; |
420 List<CodeLine> codeLines; | 735 List<CodeLine> codeLines; |
421 Coverage coverage = new Coverage(); | 736 Coverage coverage = new Coverage(); |
422 List<Annotation> annotations = <Annotation>[]; | 737 Map<int, List<CodeLineAnnotation>> codeLineAnnotationMap = |
423 | 738 <int, List<CodeLineAnnotation>>{}; |
424 void addAnnotation(int id, int offset, String title) { | 739 |
425 annotations.add(new Annotation(id, offset, title)); | 740 /// Create a [CodeLineAnnotation] for [codeOffset]. |
| 741 void addCodeLineAnnotation( |
| 742 {AnnotationType annotationType, |
| 743 int codeOffset, |
| 744 List<SourceLocation> locations: const <SourceLocation>[], |
| 745 String stepInfo}) { |
| 746 if (annotationType == AnnotationType.WITHOUT_SOURCE_INFO || |
| 747 annotationType == AnnotationType.UNUSED_SOURCE_INFO) { |
| 748 locations = []; |
| 749 } |
| 750 List<CodeLocation> codeLocations = locations |
| 751 .map((l) => new CodeLocation(l.sourceUri, l.sourceName, l.offset)) |
| 752 .toList(); |
| 753 List<CodeSource> codeSourceList = locations |
| 754 .map(codeSources.sourceLocationToCodeSource) |
| 755 .where((c) => c != null) |
| 756 .toList(); |
| 757 CodeLineAnnotation data = new CodeLineAnnotation( |
| 758 annotationId: nextAnnotationId++, |
| 759 annotationType: annotationType, |
| 760 codeLocations: codeLocations, |
| 761 codeSources: codeSourceList, |
| 762 stepInfo: stepInfo); |
| 763 codeLineAnnotationMap.putIfAbsent( |
| 764 codeOffset, () => <CodeLineAnnotation>[]).add(data); |
426 } | 765 } |
427 | 766 |
428 String code = info.code; | 767 String code = info.code; |
429 TraceGraph graph = createTraceGraph(info, coverage); | 768 TraceGraph graph = createTraceGraph(info, coverage); |
430 if (addAnnotations) { | 769 |
431 Set<js.Node> mappedNodes = new Set<js.Node>(); | 770 Set<js.Node> mappedNodes = new Set<js.Node>(); |
432 | 771 |
433 void addSourceLocations( | 772 /// Add an annotation for [codeOffset] pointing to [locations]. |
434 int kind, int offset, List<SourceLocation> locations, String prefix) { | 773 void addSourceLocations( |
435 | 774 {AnnotationType annotationType, |
436 addAnnotation(kind, offset, | 775 int codeOffset, |
437 '${prefix}${locations | 776 List<SourceLocation> locations, |
438 .where((l) => l != null) | 777 String stepInfo}) { |
439 .map((l) => l.shortText) | 778 locations = locations.where((l) => l != null).toList(); |
440 .join('\n')}'); | 779 addCodeLineAnnotation( |
441 } | 780 annotationType: annotationType, |
442 | 781 codeOffset: codeOffset, |
443 bool addSourceLocationsForNode(int kind, js.Node node, String prefix) { | 782 stepInfo: stepInfo, |
444 Map<int, List<SourceLocation>> locations = info.nodeMap[node]; | 783 locations: locations); |
445 if (locations == null || locations.isEmpty) { | 784 } |
446 return false; | 785 |
447 } | 786 /// Add annotations for all mappings created for [node]. |
448 locations.forEach( | 787 bool addSourceLocationsForNode( |
449 (int offset, List<SourceLocation> locations) { | 788 {AnnotationType annotationType, |
450 addSourceLocations(kind, offset, locations, | 789 js.Node node, |
451 '${prefix}\n${truncate(nodeToString(node), 80)}\n'); | 790 String stepInfo}) { |
452 }); | 791 Map<int, List<SourceLocation>> locations = info.nodeMap[node]; |
453 mappedNodes.add(node); | 792 if (locations == null || locations.isEmpty) { |
454 return true; | 793 return false; |
455 } | 794 } |
456 | 795 locations.forEach((int offset, List<SourceLocation> locations) { |
457 | 796 addSourceLocations( |
458 for (TraceStep step in graph.steps) { | 797 annotationType: annotationType, |
459 String title = '${step.id}:${step.kind}:${step.offset}'; | 798 codeOffset: offset, |
460 if (!addSourceLocationsForNode(WITH_SOURCE_INFO, step.node, title)) { | 799 locations: locations, |
461 int offset; | 800 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 }); | 801 }); |
486 } | 802 mappedNodes.add(node); |
487 | 803 return true; |
| 804 } |
| 805 |
| 806 // Add annotations based on trace steps. |
| 807 for (TraceStep step in graph.steps) { |
| 808 String stepInfo = '${step.id}:${step.kind}:${step.offset}'; |
| 809 bool added = addSourceLocationsForNode( |
| 810 annotationType: AnnotationType.WITH_SOURCE_INFO, |
| 811 node: step.node, |
| 812 stepInfo: stepInfo); |
| 813 if (!added) { |
| 814 int offset; |
| 815 if (options.contains(USE_NEW_SOURCE_INFO)) { |
| 816 offset = step.offset.subexpressionOffset; |
| 817 } else { |
| 818 offset = info.jsCodePositions[step.node].startPosition; |
| 819 } |
| 820 if (offset != null) { |
| 821 addCodeLineAnnotation( |
| 822 annotationType: AnnotationType.WITHOUT_SOURCE_INFO, |
| 823 codeOffset: offset, |
| 824 stepInfo: stepInfo); |
| 825 } |
| 826 } |
| 827 } |
| 828 |
| 829 // Add additional annotations for mappings created for particular nodes. |
| 830 for (js.Node node in info.nodeMap.nodes) { |
| 831 if (!mappedNodes.contains(node)) { |
| 832 addSourceLocationsForNode( |
| 833 annotationType: AnnotationType.ADDITIONAL_SOURCE_INFO, |
| 834 node: node); |
| 835 } |
| 836 } |
| 837 |
| 838 // Add annotations for unused source information associated with nodes. |
| 839 SourceLocationCollector collector = new SourceLocationCollector(); |
| 840 info.node.accept(collector); |
| 841 collector.sourceLocations.forEach( |
| 842 (js.Node node, List<SourceLocation> locations) { |
| 843 if (!mappedNodes.contains(node)) { |
| 844 int offset = info.jsCodePositions[node].startPosition; |
| 845 addSourceLocations( |
| 846 annotationType: AnnotationType.UNUSED_SOURCE_INFO, |
| 847 codeOffset: offset, |
| 848 locations: locations); |
| 849 } |
| 850 }); |
| 851 |
| 852 // Assign consecutive ids to source mappings. |
| 853 int nextSourceMappedLocationIndex = 0; |
| 854 List<Annotation> annotations = <Annotation>[]; |
| 855 for (int codeOffset in codeLineAnnotationMap.keys.toList()..sort()) { |
| 856 bool hasSourceMappedLocation = false; |
| 857 for (CodeLineAnnotation data in codeLineAnnotationMap[codeOffset]) { |
| 858 if (data.annotationType.isSourceMapped) { |
| 859 data.sourceMappingIndex = nextSourceMappedLocationIndex; |
| 860 hasSourceMappedLocation = true; |
| 861 } |
| 862 annotations.add(new Annotation( |
| 863 data.annotationType.index, |
| 864 codeOffset, |
| 865 'id=${data.annotationId}', |
| 866 data: data)); |
| 867 } |
| 868 if (hasSourceMappedLocation) { |
| 869 nextSourceMappedLocationIndex++; |
| 870 } |
| 871 } |
| 872 |
| 873 // Associate JavaScript offsets with [Element]s. |
488 StringSourceFile sourceFile = new StringSourceFile.fromName(filename, code); | 874 StringSourceFile sourceFile = new StringSourceFile.fromName(filename, code); |
489 Map<int, Element> elementMap = <int, Element>{}; | 875 Map<int, Element> elementMap = <int, Element>{}; |
490 sourceMaps.elementSourceMapInfos.forEach( | 876 sourceMaps.elementSourceMapInfos.forEach( |
491 (Element element, SourceMapInfo info) { | 877 (Element element, SourceMapInfo info) { |
492 CodePosition position = info.jsCodePositions[info.node]; | 878 CodePosition position = info.jsCodePositions[info.node]; |
493 elementMap[sourceFile.getLine(position.startPosition)] = element; | 879 elementMap[sourceFile.getLine(position.startPosition)] = element; |
494 }); | 880 }); |
495 | 881 |
496 codeLines = convertAnnotatedCodeToCodeLines( | 882 codeLines = convertAnnotatedCodeToCodeLines(code, annotations); |
497 code, | 883 return new CodeLinesResult( |
498 annotations, | 884 codeLines, coverage, elementMap, |
499 colorScheme: new CustomColorScheme( | 885 sourceMaps.sourceFileManager, |
500 single: (int id) { | 886 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 } | 887 } |
524 | 888 |
525 /// Visitor that computes a map from [js.Node]s to all attached source | 889 /// Visitor that computes a map from [js.Node]s to all attached source |
526 /// locations. | 890 /// locations. |
527 class SourceLocationCollector extends js.BaseVisitor { | 891 class SourceLocationCollector extends js.BaseVisitor { |
528 Map<js.Node, List<SourceLocation>> sourceLocations = | 892 Map<js.Node, List<SourceLocation>> sourceLocations = |
529 <js.Node, List<SourceLocation>>{}; | 893 <js.Node, List<SourceLocation>>{}; |
530 | 894 |
531 @override | 895 @override |
532 visitNode(js.Node node) { | 896 visitNode(js.Node node) { |
(...skipping 25 matching lines...) Expand all Loading... |
558 AstElement astElement = element.implementation; | 922 AstElement astElement = element.implementation; |
559 kind = CodeKind.MEMBER; | 923 kind = CodeKind.MEMBER; |
560 uri = astElement.compilationUnit.script.resourceUri; | 924 uri = astElement.compilationUnit.script.resourceUri; |
561 name = computeElementNameForSourceMaps(astElement); | 925 name = computeElementNameForSourceMaps(astElement); |
562 if (astElement.hasNode) { | 926 if (astElement.hasNode) { |
563 begin = astElement.node.getBeginToken().charOffset; | 927 begin = astElement.node.getBeginToken().charOffset; |
564 end = astElement.node.getEndToken().charEnd; | 928 end = astElement.node.getEndToken().charEnd; |
565 } | 929 } |
566 } | 930 } |
567 return new CodeSource(kind, uri, name, begin, end); | 931 return new CodeSource(kind, uri, name, begin, end); |
568 } | 932 } |
| 933 |
| 934 /// Create [LineData] that colors line numbers according to the [CodeSource]s |
| 935 /// origin if available. |
| 936 LineData getLineData(CodeSource lineAnnotation) { |
| 937 if (lineAnnotation != null) { |
| 938 return new LineData( |
| 939 lineClass: ClassNames.line, |
| 940 lineNumberClass: |
| 941 '${ClassNames.lineNumber} ' |
| 942 '${ClassNames.colored(lineAnnotation.hashCode % 4)}'); |
| 943 } |
| 944 return new LineData( |
| 945 lineClass: ClassNames.line, |
| 946 lineNumberClass: ClassNames.lineNumber); |
| 947 } |
| 948 |
| 949 AnnotationData getAnnotationData(Iterable<Annotation> annotations, |
| 950 {bool forSpan}) { |
| 951 for (Annotation annotation in annotations) { |
| 952 CodeLineAnnotation data = annotation.data; |
| 953 if (data.annotationType.isSourceMapped) { |
| 954 if (forSpan) { |
| 955 int index = data.sourceMappingIndex; |
| 956 return new AnnotationData( |
| 957 tag: 'span', |
| 958 properties: { |
| 959 'class': |
| 960 '${ClassNames.sourceMapping} ' |
| 961 '${ClassNames.sourceMappingIndex(index % HUE_COUNT)}', |
| 962 'title': 'index=$index', |
| 963 }); |
| 964 } else { |
| 965 return new AnnotationData( |
| 966 tag: 'span', |
| 967 properties: { |
| 968 'title': annotation.title, |
| 969 'class': '${ClassNames.marker} ' |
| 970 '${data.annotationType.className}'}); |
| 971 } |
| 972 } |
| 973 } |
| 974 if (forSpan) return null; |
| 975 for (Annotation annotation in annotations) { |
| 976 CodeLineAnnotation data = annotation.data; |
| 977 if (data.annotationType == AnnotationType.UNUSED_SOURCE_INFO) { |
| 978 return new AnnotationData( |
| 979 tag: 'span', |
| 980 properties: { |
| 981 'title': annotation.title, |
| 982 'class': '${ClassNames.marker} ' |
| 983 '${data.annotationType.className}'}); |
| 984 } |
| 985 } |
| 986 for (Annotation annotation in annotations) { |
| 987 CodeLineAnnotation data = annotation.data; |
| 988 if (data.annotationType == AnnotationType.WITHOUT_SOURCE_INFO) { |
| 989 return new AnnotationData( |
| 990 tag: 'span', |
| 991 properties: { |
| 992 'title': annotation.title, |
| 993 'class': '${ClassNames.marker} ' |
| 994 '${data.annotationType.className}'}); |
| 995 } |
| 996 } |
| 997 return null; |
| 998 } |
OLD | NEW |