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