OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library sourcemap.diff_view; | |
Siggi Cherem (dart-lang)
2016/01/21 18:18:34
I only did a cursory review of this file, let me k
| |
6 | |
7 import 'dart:async'; | |
8 import 'dart:io'; | |
9 import 'package:compiler/src/commandline_options.dart'; | |
10 import 'package:compiler/src/diagnostics/invariant.dart'; | |
11 import 'package:compiler/src/io/position_information.dart'; | |
12 import 'package:compiler/src/js/js.dart' as js; | |
13 import 'sourcemap_helper.dart'; | |
14 import 'sourcemap_html_helper.dart'; | |
15 import 'trace_graph.dart'; | |
16 import 'js_tracer.dart'; | |
17 | |
18 const String WITH_SOURCE_INFO_STYLE = 'background-color:#FF8080;'; | |
19 const String WITHOUT_SOURCE_INFO_STYLE = 'border: solid 1px #FF8080;'; | |
20 const String ADDITIONAL_SOURCE_INFO_STYLE = 'border: solid 1px #8080FF;'; | |
21 | |
22 main(List<String> args) async { | |
23 DEBUG_MODE = true; | |
24 String out = 'out.js.diff_view.html'; | |
25 String filename; | |
26 List<String> currentOptions = []; | |
27 List<List<String>> options = [currentOptions]; | |
28 int argGroup = 0; | |
29 for (String arg in args) { | |
30 if (arg == '--') { | |
31 currentOptions = []; | |
32 options.add(currentOptions); | |
33 argGroup++; | |
34 } else if (arg.startsWith('-o')) { | |
35 out = arg.substring('-o'.length); | |
36 } else if (arg.startsWith('--out=')) { | |
37 out = arg.substring('--out='.length); | |
38 } else if (arg.startsWith('-')) { | |
39 currentOptions.add(arg); | |
40 } else { | |
41 filename = arg; | |
42 } | |
43 } | |
44 List<String> commonArguments = options[0]; | |
45 List<String> options1; | |
46 List<String> options2; | |
47 if (options.length == 1) { | |
48 // Use default options; comparing SSA and CPS output using the new | |
49 // source information strategy. | |
Siggi Cherem (dart-lang)
2016/01/21 18:18:34
is a goal also to compare the new and old source-i
Johnni Winther
2016/01/22 15:12:38
It is support as well.
| |
50 options1 = [USE_NEW_SOURCE_INFO]..addAll(commonArguments); | |
51 options2 = [USE_NEW_SOURCE_INFO, Flags.useCpsIr]..addAll(commonArguments); | |
52 } else if (options.length == 2) { | |
53 // Use alternative options for the second output column. | |
54 options1 = commonArguments; | |
55 options2 = options[1]..addAll(commonArguments); | |
56 } else { | |
57 // Use specific options for both output columns. | |
58 options1 = options[1]..addAll(commonArguments); | |
59 options2 = options[2]..addAll(commonArguments); | |
60 } | |
61 | |
62 print('Compiling ${options1.join(' ')} $filename'); | |
63 CodeLinesResult result1 = await computeCodeLines(options1, filename); | |
64 print('Compiling ${options2.join(' ')} $filename'); | |
65 CodeLinesResult result2 = await computeCodeLines(options2, filename); | |
66 | |
67 StringBuffer sb = new StringBuffer(); | |
68 sb.write(''' | |
69 <html> | |
70 <head> | |
71 <title>Diff for $filename</title> | |
72 <style> | |
73 .lineNumber { | |
74 font-size: smaller; | |
75 color: #888; | |
76 } | |
77 .header { | |
78 position: fixed; | |
79 width: 50%; | |
80 background-color: #400000; | |
81 color: #FFFFFF; | |
82 height: 20px; | |
83 top: 0px; | |
84 z-index: 1000; | |
85 } | |
86 .cell { | |
87 max-width:500px; | |
88 overflow-x:auto; | |
89 vertical-align:top; | |
90 } | |
91 .corresponding1 { | |
92 background-color: #FFFFE0; | |
93 } | |
94 .corresponding2 { | |
95 background-color: #EFEFD0; | |
96 } | |
97 .identical1 { | |
98 background-color: #E0F0E0; | |
99 } | |
100 .identical2 { | |
101 background-color: #C0E0C0; | |
102 } | |
103 </style> | |
104 </head> | |
105 <body>'''); | |
106 | |
107 sb.write(''' | |
108 <div class="header" style="left: 0px;">[${options1.join(',')}]</div> | |
109 <div class="header" style="right: 0px;">[${options2.join(',')}]</div> | |
110 <div style="position:absolute;top:22px;width:100%;height:18px;"> | |
111 <span class="identical1"> </span> | |
112 <span class="identical2"> </span> | |
113 identical blocks | |
114 <span class="corresponding1"> </span> | |
115 <span class="corresponding2"> </span> | |
116 corresponding blocks | |
117 <span style="$WITH_SOURCE_INFO_STYLE"> </span> | |
118 offset with source information | |
119 <span style="$WITHOUT_SOURCE_INFO_STYLE"> </span> | |
120 offset without source information | |
121 <span style="$ADDITIONAL_SOURCE_INFO_STYLE"> </span> | |
122 offset with unneeded source information | |
123 </div> | |
124 <table style="position:absolute;top:40px;width:100%;"><tr> | |
125 '''); | |
126 | |
127 void addCell(String content) { | |
128 sb.write(''' | |
129 <td class="cell"><pre> | |
130 '''); | |
131 sb.write(content); | |
132 sb.write(''' | |
133 </pre></td> | |
134 '''); | |
135 } | |
136 | |
137 List<OutputStructure> structures = [ | |
138 OutputStructure.parse(result1.codeLines), | |
139 OutputStructure.parse(result2.codeLines)]; | |
140 List<List<CodeLine>> inputLines = [result1.codeLines, result2.codeLines]; | |
141 List<List<HtmlPart>> outputLines = [<HtmlPart>[], <HtmlPart>[]]; | |
142 | |
143 /// Marker to alternate output colors. | |
144 bool alternating = false; | |
145 | |
146 /// Enable 'corresponding' background colors for [f]. | |
147 void withMatching(f()) { | |
148 HtmlPart start = new ConstHtmlPart( | |
149 '<div class="corresponding${alternating ? '1' : '2'}">'); | |
150 HtmlPart end = new ConstHtmlPart('</div>'); | |
151 alternating = !alternating; | |
152 outputLines[0].add(start); | |
153 outputLines[1].add(start); | |
154 f(); | |
155 outputLines[0].add(end); | |
156 outputLines[1].add(end); | |
157 } | |
158 | |
159 /// Enable 'identical' background colors for [f]. | |
160 void withIdentical(f()) { | |
161 HtmlPart start = new ConstHtmlPart( | |
162 '<div class="identical${alternating ? '1' : '2'}">'); | |
163 HtmlPart end = new ConstHtmlPart('</div>'); | |
164 alternating = !alternating; | |
165 outputLines[0].add(start); | |
166 outputLines[1].add(start); | |
167 f(); | |
168 outputLines[0].add(end); | |
169 outputLines[1].add(end); | |
170 } | |
171 | |
172 /// Output code lines in [range] from input number [index], padding the other | |
173 /// column with empty lines. | |
174 void handleSkew(int index, Interval range) { | |
175 int from = range.from; | |
176 while (from < range.to) { | |
177 outputLines[1 - index].add(const ConstHtmlPart('\n')); | |
178 outputLines[index].add( | |
179 new CodeLineHtmlPart(inputLines[index][from++])); | |
180 } | |
181 } | |
182 | |
183 /// Output code lines of the [indices] from the corresponding inputs. | |
184 void addBoth(List<int> indices) { | |
185 outputLines[0].add(new CodeLineHtmlPart(inputLines[0][indices[0]])); | |
186 outputLines[1].add(new CodeLineHtmlPart(inputLines[1][indices[1]])); | |
187 } | |
188 | |
189 /// Output code lines of the [ranges] from the corresponding inputs. | |
190 void addBothLines(List<Interval> ranges) { | |
191 Interval range1 = ranges[0]; | |
192 Interval range2 = ranges[1]; | |
193 int offset = 0; | |
194 while (range1.from + offset < range1.to && | |
195 range2.from + offset < range2.to) { | |
196 addBoth([range1.from + offset, range2.from + offset]); | |
197 offset++; | |
198 } | |
199 if (range1.from + offset < range1.to) { | |
200 handleSkew(0, new Interval(range1.from + offset, range1.to)); | |
201 } | |
202 if (range2.from + offset < range2.to) { | |
203 handleSkew(1, new Interval(range2.from + offset, range2.to)); | |
204 } | |
205 } | |
206 | |
207 /// Merge the code lines in [range1] and [range2] of the corresponding input. | |
208 void addRaw(Interval range1, Interval range2) { | |
209 match(a, b) => a.code == b.code; | |
210 | |
211 List<Interval> currentMatchedIntervals; | |
212 | |
213 void flushMatching() { | |
214 if (currentMatchedIntervals != null) { | |
215 withIdentical(() { | |
216 addBothLines(currentMatchedIntervals); | |
217 }); | |
218 } | |
219 currentMatchedIntervals = null; | |
220 } | |
221 | |
222 align( | |
223 inputLines[0], | |
224 inputLines[1], | |
225 range1: range1, | |
226 range2: range2, | |
227 match: match, | |
228 handleSkew: (int listIndex, Interval range) { | |
229 flushMatching(); | |
230 handleSkew(listIndex, range); | |
231 }, | |
232 handleMatched: (List<int> indices) { | |
233 if (currentMatchedIntervals == null) { | |
234 currentMatchedIntervals = [ | |
235 new Interval(indices[0], indices[0] + 1), | |
236 new Interval(indices[1], indices[1] + 1)]; | |
237 } else { | |
238 currentMatchedIntervals[0] = | |
239 new Interval(currentMatchedIntervals[0].from, indices[0] + 1); | |
240 currentMatchedIntervals[1] = | |
241 new Interval(currentMatchedIntervals[1].from, indices[1] + 1); | |
242 } | |
243 }, | |
244 handleUnmatched: (List<int> indices) { | |
245 flushMatching(); | |
246 addBoth(indices); | |
247 }); | |
248 | |
249 flushMatching(); | |
250 } | |
251 | |
252 /// Output the lines of the library blocks in [childRange] in | |
253 /// `structures[index]`, padding the other column with empty lines. | |
254 void addBlock(int index, Interval childRange) { | |
255 handleSkew(index, structures[index].getChildInterval(childRange)); | |
256 } | |
257 | |
258 /// Output the members of the [classes] aligned. | |
259 void addMatchingClasses(List<LibraryClass> classes) { | |
260 withMatching(() { | |
261 addBothLines(classes.map((c) => c.header).toList()); | |
262 }); | |
263 align(classes[0].children, classes[1].children, | |
264 match: (a, b) => a.name == b.name, | |
265 handleSkew: (int listIndex, Interval childRange) { | |
266 handleSkew(listIndex, | |
267 classes[listIndex].getChildInterval(childRange)); | |
268 }, | |
269 handleMatched: (List<int> indices) { | |
270 List<Interval> intervals = [ | |
271 classes[0].getChild(indices[0]).interval, | |
272 classes[1].getChild(indices[1]).interval]; | |
273 withMatching(() { | |
274 addBothLines(intervals); | |
275 }); | |
276 }, | |
277 handleUnmatched: (List<int> indices) { | |
278 List<Interval> intervals = [ | |
279 classes[0].getChild(indices[0]).interval, | |
280 classes[1].getChild(indices[1]).interval]; | |
281 addBothLines(intervals); | |
282 }); | |
283 withMatching(() { | |
284 addBothLines(classes.map((c) => c.footer).toList()); | |
285 }); | |
286 } | |
287 | |
288 /// Output the library blocks in [indices] from the corresponding | |
289 /// [OutputStructure]s, aligning their content. | |
290 void addMatchingBlocks(List<int> indices) { | |
291 List<LibraryBlock> blocks = [ | |
292 structures[0].getChild(indices[0]), | |
293 structures[1].getChild(indices[1])]; | |
294 | |
295 withMatching(() { | |
296 addBothLines(blocks.map((b) => b.header).toList()); | |
297 }); | |
298 align(blocks[0].children, blocks[1].children, | |
299 match: (a, b) => a.name == b.name, | |
300 handleSkew: (int listIndex, Interval childRange) { | |
301 handleSkew(listIndex, blocks[listIndex].getChildInterval(childRange)); | |
302 }, | |
303 handleMatched: (List<int> indices) { | |
304 List<BasicEntity> entities = [ | |
305 blocks[0].getChild(indices[0]), | |
306 blocks[1].getChild(indices[1])]; | |
307 if (entities.every((e) => e is LibraryClass)) { | |
308 addMatchingClasses(entities); | |
309 } else { | |
310 withMatching(() { | |
311 addBothLines(entities.map((e) => e.interval).toList()); | |
312 }); | |
313 } | |
314 }, | |
315 handleUnmatched: (List<int> indices) { | |
316 List<Interval> intervals = [ | |
317 blocks[0].getChild(indices[0]).interval, | |
318 blocks[1].getChild(indices[1]).interval]; | |
319 addBothLines(intervals); | |
320 }); | |
321 withMatching(() { | |
322 addBothLines(blocks.map((b) => b.footer).toList()); | |
323 }); | |
324 } | |
325 | |
326 /// Output the lines of the blocks in [indices] from the corresponding | |
327 /// [OutputStructure]s. | |
328 void addUnmatchedBlocks(List<int> indices) { | |
329 List<LibraryBlock> blocks = [ | |
330 structures[0].getChild(indices[0]), | |
331 structures[1].getChild(indices[1])]; | |
332 addBothLines([blocks[0].interval, blocks[1].interval]); | |
333 } | |
334 | |
335 | |
336 addRaw(structures[0].header, structures[1].header); | |
337 | |
338 align(structures[0].children, | |
339 structures[1].children, | |
340 match: (a, b) => a.name == b.name, | |
341 handleSkew: addBlock, | |
342 handleMatched: addMatchingBlocks, | |
343 handleUnmatched: addUnmatchedBlocks); | |
344 | |
345 addRaw(structures[0].footer, structures[1].footer); | |
346 | |
347 addCell(htmlPartsToString(outputLines[0], inputLines[0])); | |
348 addCell(htmlPartsToString(outputLines[1], inputLines[1])); | |
349 | |
350 sb.write('''</tr><tr>'''); | |
351 addCell(result1.coverage.getCoverageReport()); | |
352 addCell(result2.coverage.getCoverageReport()); | |
353 | |
354 sb.write(''' | |
355 </tr></table> | |
356 </body> | |
357 </html> | |
358 '''); | |
359 | |
360 new File(out).writeAsStringSync(sb.toString()); | |
361 print('Diff generated in $out'); | |
362 } | |
363 | |
364 /// Align the content of [list1] and [list2]. | |
365 /// | |
366 /// If provided, [range1] and [range2] aligned the subranges of [list1] and | |
367 /// [list2], otherwise the whole lists are aligned. | |
368 /// | |
369 /// If provided, [match] determines the equality between members of [list1] and | |
370 /// [list2], otherwise `==` is used. | |
371 /// | |
372 /// [handleSkew] is called when a subrange of one list is not found in the | |
373 /// other. | |
374 /// | |
375 /// [handleMatched] is called when two indices match up. | |
376 /// | |
377 /// [handleUnmatched] is called when two indices don't match up (none are found | |
378 /// in the other list). | |
379 void align(List list1, | |
380 List list2, | |
381 {Interval range1, | |
382 Interval range2, | |
383 bool match(a, b), | |
384 void handleSkew(int listIndex, Interval range), | |
385 void handleMatched(List<int> indices), | |
386 void handleUnmatched(List<int> indices)}) { | |
387 if (match == null) { | |
388 match = (a, b) => a == b; | |
389 } | |
390 | |
391 if (range1 == null) { | |
392 range1 = new Interval(0, list1.length); | |
393 } | |
394 if (range2 == null) { | |
395 range2 = new Interval(0, list2.length); | |
396 } | |
397 | |
398 Interval findInOther( | |
399 List thisLines, Interval thisRange, | |
400 List otherLines, Interval otherRange) { | |
401 for (int index = otherRange.from; index < otherRange.to; index++) { | |
402 if (match(thisLines[thisRange.from], otherLines[index])) { | |
403 int offset = 1; | |
404 while (thisRange.from + offset < thisRange.to && | |
405 otherRange.from + offset < otherRange.to && | |
406 match(thisLines[thisRange.from + offset], | |
407 otherLines[otherRange.from + offset])) { | |
408 offset++; | |
409 } | |
410 return new Interval(index, index + offset); | |
411 } | |
412 } | |
413 return null; | |
414 } | |
415 | |
416 int start1 = range1.from; | |
417 int end1 = range1.to; | |
418 int start2 = range2.from; | |
419 int end2 = range2.to; | |
420 | |
421 const int ALIGN1 = -1; | |
422 const int UNMATCHED = 0; | |
423 const int ALIGN2 = 1; | |
424 | |
425 while (start1 < end1 && start2 < end2) { | |
426 if (match(list1[start1], list2[start2])) { | |
427 handleMatched([start1++, start2++]); | |
428 } else { | |
429 Interval subrange1 = new Interval(start1, end1); | |
430 Interval subrange2 = new Interval(start2, end2); | |
431 Interval element2inList1 = | |
432 findInOther(list1, subrange1, list2, subrange2); | |
433 Interval element1inList2 = | |
434 findInOther(list2, subrange2, list1, subrange1); | |
435 int choice = 0; | |
436 if (element2inList1 != null) { | |
437 if (element1inList2 != null) { | |
438 if (element1inList2.length > 1 && element2inList1.length > 1) { | |
439 choice = | |
440 element2inList1.from < element1inList2.from ? ALIGN2 : ALIGN1; | |
441 } else if (element2inList1.length > 1) { | |
442 choice = ALIGN2; | |
443 } else if (element1inList2.length > 1) { | |
444 choice = ALIGN1; | |
445 } else { | |
446 choice = | |
447 element2inList1.from < element1inList2.from ? ALIGN2 : ALIGN1; | |
448 } | |
449 } else { | |
450 choice = ALIGN2; | |
451 } | |
452 } else if (element1inList2 != null) { | |
453 choice = ALIGN1; | |
454 } | |
455 switch (choice) { | |
456 case ALIGN1: | |
457 handleSkew(0, new Interval(start1, element1inList2.from)); | |
458 start1 = element1inList2.from; | |
459 break; | |
460 case ALIGN2: | |
461 handleSkew(1, new Interval(start2, element2inList1.from)); | |
462 start2 = element2inList1.from; | |
463 break; | |
464 case UNMATCHED: | |
465 handleUnmatched([start1++, start2++]); | |
466 break; | |
467 } | |
468 } | |
469 } | |
470 if (start1 < end1) { | |
471 handleSkew(0, new Interval(start1, end1)); | |
472 } | |
473 if (start2 < end2) { | |
474 handleSkew(1, new Interval(start2, end2)); | |
475 } | |
476 } | |
477 | |
478 // Constants used to identify the subsection of the JavaScript output. These | |
479 // are specifically for the unminified full_emitter output. | |
480 const String HEAD = ' var dart = ['; | |
481 const String TAIL = ' }], '; | |
482 const String END = ' setupProgram(dart'; | |
483 | |
484 final RegExp TOP_LEVEL_VALUE = new RegExp(r'^ (".+?"):'); | |
485 final RegExp TOP_LEVEL_FUNCTION = | |
486 new RegExp(r'^ ([a-zA-Z0-9_$]+): function'); | |
487 final RegExp TOP_LEVEL_CLASS = new RegExp(r'^ ([a-zA-Z0-9_$]+): \{'); | |
488 | |
489 final RegExp MEMBER_VALUE = new RegExp(r'^ (".+?"):'); | |
490 final RegExp MEMBER_FUNCTION = new RegExp(r'^ ([a-zA-Z0-9_$]+): function'); | |
491 final RegExp MEMBER_OBJECT = new RegExp(r'^ ([a-zA-Z0-9_$]+): \{'); | |
492 | |
493 /// Subrange of the JavaScript output. | |
494 abstract class OutputEntity { | |
495 Interval get interval; | |
496 Interval get header; | |
497 Interval get footer; | |
498 | |
499 List<OutputEntity> get children; | |
500 | |
501 Interval getChildInterval(Interval childIndex) { | |
502 return new Interval( | |
503 children[childIndex.from].interval.from, | |
504 children[childIndex.to - 1].interval.to); | |
505 | |
506 } | |
507 | |
508 OutputEntity getChild(int index) { | |
509 return children[index]; | |
510 } | |
511 } | |
512 | |
513 /// The whole JavaScript output. | |
514 class OutputStructure extends OutputEntity { | |
515 final List<CodeLine> lines; | |
516 final int headerEnd; | |
517 final int footerStart; | |
518 final List<LibraryBlock> children; | |
519 | |
520 OutputStructure( | |
521 this.lines, | |
522 this.headerEnd, | |
523 this.footerStart, | |
524 this.children); | |
525 | |
526 Interval get interval => new Interval(0, lines.length); | |
527 | |
528 Interval get header => new Interval(0, headerEnd); | |
529 | |
530 Interval get footer => new Interval(footerStart, lines.length); | |
531 | |
532 /// Compute the structure of the JavaScript [lines]. | |
533 static OutputStructure parse(List<CodeLine> lines) { | |
534 | |
535 int findHeaderStart(List<CodeLine> lines) { | |
536 int index = 0; | |
537 for (CodeLine line in lines) { | |
538 if (line.code.startsWith(HEAD)) { | |
539 return index; | |
540 } | |
541 index++; | |
542 } | |
543 return lines.length; | |
544 } | |
545 | |
546 int findHeaderEnd(int start, List<CodeLine> lines) { | |
547 int index = start; | |
548 for (CodeLine line in lines.skip(start)) { | |
549 if (line.code.startsWith(END)) { | |
550 return index; | |
551 } | |
552 index++; | |
553 } | |
554 return lines.length; | |
555 } | |
556 | |
557 String readHeader(CodeLine line) { | |
558 String code = line.code; | |
559 String ssaLineHeader; | |
560 if (code.startsWith(HEAD)) { | |
561 return code.substring(HEAD.length); | |
562 } else if (code.startsWith(TAIL)) { | |
563 return code.substring(TAIL.length); | |
564 } | |
565 return null; | |
566 } | |
567 | |
568 List<LibraryBlock> computeHeaderMap( | |
569 List<CodeLine> lines, int start, int end) { | |
570 List<LibraryBlock> libraryBlocks = <LibraryBlock>[]; | |
571 LibraryBlock current; | |
572 for (int index = start; index < end; index++) { | |
573 String header = readHeader(lines[index]); | |
574 if (header != null) { | |
575 if (current != null) { | |
576 current.to = index; | |
577 } | |
578 libraryBlocks.add(current = new LibraryBlock(header, index)); | |
579 } | |
580 } | |
581 if (current != null) { | |
582 current.to = end; | |
583 } | |
584 return libraryBlocks; | |
585 } | |
586 | |
587 int headerEnd = findHeaderStart(lines); | |
588 int footerStart = findHeaderEnd(headerEnd, lines); | |
589 List<LibraryBlock> libraryBlocks = | |
590 computeHeaderMap(lines, headerEnd, footerStart); | |
591 for (LibraryBlock block in libraryBlocks) { | |
592 block.preprocess(lines); | |
593 } | |
594 | |
595 return new OutputStructure( | |
596 lines, headerEnd, footerStart, libraryBlocks); | |
597 } | |
598 } | |
599 | |
600 abstract class AbstractEntity extends OutputEntity { | |
601 final String name; | |
602 final int from; | |
603 int to; | |
604 | |
605 AbstractEntity(this.name, this.from); | |
606 | |
607 Interval get interval => new Interval(from, to); | |
608 } | |
609 | |
610 /// A block defining the content of a Dart library. | |
611 class LibraryBlock extends AbstractEntity { | |
612 List<BasicEntity> children = <BasicEntity>[]; | |
613 int get headerEnd => from + 2; | |
614 int get footerStart => to - 1; | |
615 | |
616 LibraryBlock(String name, int from) : super(name, from); | |
617 | |
618 Interval get header => new Interval(from, headerEnd); | |
619 | |
620 Interval get footer => new Interval(footerStart, to); | |
621 | |
622 void preprocess(List<CodeLine> lines) { | |
623 int index = headerEnd; | |
624 BasicEntity current; | |
625 while (index < footerStart) { | |
626 String line = lines[index].code; | |
627 BasicEntity next; | |
628 Match matchFunction = TOP_LEVEL_FUNCTION.firstMatch(line); | |
629 if (matchFunction != null) { | |
630 next = new BasicEntity(matchFunction.group(1), index); | |
631 } else { | |
632 Match matchClass = TOP_LEVEL_CLASS.firstMatch(line); | |
633 if (matchClass != null) { | |
634 next = new LibraryClass(matchClass.group(1), index); | |
635 } else { | |
636 Match matchValue = TOP_LEVEL_VALUE.firstMatch(line); | |
637 if (matchValue != null) { | |
638 next = new BasicEntity(matchValue.group(1), index); | |
639 } | |
640 } | |
641 } | |
642 if (next != null) { | |
643 if (current != null) { | |
644 current.to = index; | |
645 } | |
646 children.add(current = next); | |
647 } else if (index == headerEnd) { | |
648 throw 'Failed to match first library block line:\n$line'; | |
649 } | |
650 | |
651 index++; | |
652 } | |
653 if (current != null) { | |
654 current.to = footerStart; | |
655 } | |
656 | |
657 for (BasicEntity entity in children) { | |
658 entity.preprocess(lines); | |
659 } | |
660 } | |
661 } | |
662 | |
663 /// A simple member of a library or class. | |
664 class BasicEntity extends AbstractEntity { | |
665 BasicEntity(String name, int from) : super(name, from); | |
666 | |
667 Interval get header => new Interval(from, to); | |
668 | |
669 Interval get footer => new Interval(to, to); | |
670 | |
671 List<OutputEntity> get children => const <OutputEntity>[]; | |
672 | |
673 void preprocess(List<CodeLine> lines) {} | |
674 } | |
675 | |
676 /// A block defining a Dart class. | |
677 class LibraryClass extends BasicEntity { | |
678 List<BasicEntity> children = <BasicEntity>[]; | |
679 int get headerEnd => from + 1; | |
680 int get footerStart => to - 1; | |
681 | |
682 LibraryClass(String name, int from) : super(name, from); | |
683 | |
684 Interval get header => new Interval(from, headerEnd); | |
685 | |
686 Interval get footer => new Interval(footerStart, to); | |
687 | |
688 void preprocess(List<CodeLine> lines) { | |
689 int index = headerEnd; | |
690 BasicEntity current; | |
691 while (index < footerStart) { | |
692 String line = lines[index].code; | |
693 BasicEntity next; | |
694 Match matchFunction = MEMBER_FUNCTION.firstMatch(line); | |
695 if (matchFunction != null) { | |
696 next = new BasicEntity(matchFunction.group(1), index); | |
697 } else { | |
698 Match matchClass = MEMBER_OBJECT.firstMatch(line); | |
699 if (matchClass != null) { | |
700 next = new BasicEntity(matchClass.group(1), index); | |
701 } else { | |
702 Match matchValue = MEMBER_VALUE.firstMatch(line); | |
703 if (matchValue != null) { | |
704 next = new BasicEntity(matchValue.group(1), index); | |
705 } | |
706 } | |
707 } | |
708 if (next != null) { | |
709 if (current != null) { | |
710 current.to = index; | |
711 } | |
712 children.add(current = next); | |
713 } else if (index == headerEnd) { | |
714 throw 'Failed to match first library block line:\n$line'; | |
715 } | |
716 | |
717 index++; | |
718 } | |
719 if (current != null) { | |
720 current.to = footerStart; | |
721 } | |
722 } | |
723 } | |
724 | |
725 class Interval { | |
726 final int from; | |
727 final int to; | |
728 | |
729 const Interval(this.from, this.to); | |
730 | |
731 int get length => to - from; | |
732 } | |
733 | |
734 class HtmlPart { | |
735 void printHtmlOn(StringBuffer buffer) {} | |
736 } | |
737 | |
738 class ConstHtmlPart implements HtmlPart { | |
739 final String html; | |
740 | |
741 const ConstHtmlPart(this.html); | |
742 | |
743 @override | |
744 void printHtmlOn(StringBuffer buffer) { | |
745 buffer.write(html); | |
746 } | |
747 } | |
748 | |
749 class CodeLineHtmlPart implements HtmlPart { | |
750 final CodeLine line; | |
751 | |
752 CodeLineHtmlPart(this.line); | |
753 | |
754 @override | |
755 void printHtmlOn(StringBuffer buffer, [int lineNoWidth]) { | |
756 line.printHtmlOn(buffer, lineNoWidth); | |
757 } | |
758 } | |
759 | |
760 /// Convert [parts] to an HTML string while checking invariants for [lines]. | |
761 String htmlPartsToString(List<HtmlPart> parts, List<CodeLine> lines) { | |
762 int lineNoWidth; | |
763 if (lines.isNotEmpty) { | |
764 lineNoWidth = '${lines.last.lineNo + 1}'.length; | |
765 } | |
766 StringBuffer buffer = new StringBuffer(); | |
767 int expectedLineNo = 0; | |
768 for (HtmlPart part in parts) { | |
769 if (part is CodeLineHtmlPart) { | |
770 if (part.line.lineNo != expectedLineNo) { | |
771 print('Expected line no $expectedLineNo, found ${part.line.lineNo}'); | |
772 if (part.line.lineNo < expectedLineNo) { | |
773 print('Duplicate lines:'); | |
774 int index = part.line.lineNo; | |
775 while (index <= expectedLineNo) { | |
776 print(lines[index++].code); | |
777 } | |
778 } else { | |
779 print('Missing lines:'); | |
780 int index = expectedLineNo; | |
781 while (index <= part.line.lineNo) { | |
782 print(lines[index++].code); | |
783 } | |
784 } | |
785 expectedLineNo = part.line.lineNo; | |
786 } | |
787 expectedLineNo++; | |
788 part.printHtmlOn(buffer, lineNoWidth); | |
789 } else { | |
790 part.printHtmlOn(buffer); | |
791 } | |
792 } | |
793 return buffer.toString(); | |
794 } | |
795 | |
796 class CodeLinesResult { | |
797 final List<CodeLine> codeLines; | |
798 final Coverage coverage; | |
799 | |
800 CodeLinesResult(this.codeLines, this.coverage); | |
801 } | |
802 | |
803 /// Compute [CodeLine]s and [Coverage] for [filename] using the given [options]. | |
804 Future<CodeLinesResult> computeCodeLines( | |
805 List<String> options, | |
806 String filename) async { | |
807 SourceMapProcessor processor = new SourceMapProcessor(filename); | |
808 List<SourceMapInfo> sourceMapInfoList = | |
809 await processor.process(options, perElement: false); | |
810 | |
811 const int WITH_SOURCE_INFO = 0; | |
812 const int WITHOUT_SOURCE_INFO = 1; | |
813 const int ADDITIONAL_SOURCE_INFO = 2; | |
814 | |
815 for (SourceMapInfo info in sourceMapInfoList) { | |
816 if (info.element != null) continue; | |
817 | |
818 List<CodeLine> codeLines; | |
819 Coverage coverage = new Coverage(); | |
820 List<Annotation> annotations = <Annotation>[]; | |
821 String code = info.code; | |
822 TraceGraph graph = createTraceGraph(info, coverage); | |
823 Set<js.Node> mappedNodes = new Set<js.Node>(); | |
824 for (TraceStep step in graph.steps) { | |
825 int offset; | |
826 if (options.contains(USE_NEW_SOURCE_INFO)) { | |
827 offset = step.offset.codeOffset; | |
828 } else { | |
829 offset = info.jsCodePositions[step.node].startPosition; | |
830 } | |
831 if (offset != null) { | |
832 int id = step.sourceLocation != null | |
833 ? WITH_SOURCE_INFO : WITHOUT_SOURCE_INFO; | |
834 annotations.add( | |
835 new Annotation(id, offset, null)); | |
836 } | |
837 } | |
838 if (!options.contains(USE_NEW_SOURCE_INFO)) { | |
839 for (js.Node node in info.nodeMap.nodes) { | |
840 if (!mappedNodes.contains(node)) { | |
841 int offset = info.jsCodePositions[node].startPosition; | |
842 annotations.add( | |
843 new Annotation(ADDITIONAL_SOURCE_INFO, offset, null)); | |
844 } | |
845 } | |
846 } | |
847 codeLines = convertAnnotatedCodeToCodeLines( | |
848 code, | |
849 annotations, | |
850 colorScheme: new CustomColorScheme( | |
851 single: (int id) { | |
852 if (id == WITH_SOURCE_INFO) { | |
853 return WITH_SOURCE_INFO_STYLE; | |
854 } else if (id == ADDITIONAL_SOURCE_INFO) { | |
855 return ADDITIONAL_SOURCE_INFO_STYLE; | |
856 } | |
857 return WITHOUT_SOURCE_INFO_STYLE; | |
858 }, | |
859 multi: (List ids) { | |
860 if (ids.contains(WITH_SOURCE_INFO)) { | |
861 return WITH_SOURCE_INFO_STYLE; | |
862 } else if (ids.contains(ADDITIONAL_SOURCE_INFO)) { | |
863 return ADDITIONAL_SOURCE_INFO_STYLE; | |
864 } | |
865 return WITHOUT_SOURCE_INFO_STYLE; | |
866 } | |
867 )); | |
868 return new CodeLinesResult(codeLines, coverage); | |
869 } | |
870 } | |
OLD | NEW |