Chromium Code Reviews| 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 |