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 offset with source information |
128 <span style="$WITHOUT_SOURCE_INFO_STYLE"> </span> | 295 <span style="$WITHOUT_SOURCE_INFO_STYLE"> </span> |
129 offset without source information | 296 offset without source information |
130 <span style="$ADDITIONAL_SOURCE_INFO_STYLE"> </span> | 297 <span style="$ADDITIONAL_SOURCE_INFO_STYLE"> </span> |
131 offset with unneeded source information | 298 offset with unneeded source information |
299 <span style="$UNUSED_SOURCE_INFO_STYLE"> </span> | |
300 offset with unused source information | |
Siggi Cherem (dart-lang)
2016/02/08 22:31:53
not sure what's the distinction here between addit
Johnni Winther
2016/02/10 09:19:27
Large description added in hovertext.
| |
132 '''); | 301 '''); |
133 } | 302 } |
303 | |
134 sb.write(''' | 304 sb.write(''' |
135 </div> | 305 </div></div> |
136 <table style="position:absolute;left:0px;top:40px;width:100%;"><tr> | 306 <table class="table"> |
137 '''); | 307 '''); |
138 | 308 |
139 void addCell(String content) { | 309 void addCell(String content) { |
140 sb.write(''' | 310 sb.write(''' |
141 <td class="cell"><pre> | 311 <td class="cell"><pre> |
142 '''); | 312 '''); |
143 sb.write(content); | 313 sb.write(content); |
144 sb.write(''' | 314 sb.write(''' |
145 </pre></td> | 315 </pre></td> |
146 '''); | 316 '''); |
147 } | 317 } |
148 | 318 |
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. | 319 /// Marker to alternate output colors. |
156 bool alternating = false; | 320 bool alternating = false; |
157 | 321 |
158 /// Enable 'corresponding' background colors for [f]. | 322 List<HtmlPrintContext> printContexts = <HtmlPrintContext>[]; |
159 void withMatching(f()) { | 323 for (int i = 0; i < 2; i++) { |
160 HtmlPart start = new ConstHtmlPart( | 324 int lineNoWidth; |
161 '<div class="corresponding${alternating ? '1' : '2'}">'); | 325 if (outputs[i].codeLines.isNotEmpty) { |
162 HtmlPart end = new ConstHtmlPart('</div>'); | 326 lineNoWidth = '${outputs[i].codeLines.last.lineNo + 1}'.length; |
163 alternating = !alternating; | 327 } |
164 outputLines[0].add(start); | 328 printContexts.add(new HtmlPrintContext(lineNoWidth: lineNoWidth)); |
165 outputLines[1].add(start); | |
166 f(); | |
167 outputLines[0].add(end); | |
168 outputLines[1].add(end); | |
169 } | 329 } |
170 | 330 |
171 /// Enable 'identical' background colors for [f]. | 331 for (DiffBlock block in blocks) { |
172 void withIdentical(f()) { | 332 String className; |
173 HtmlPart start = new ConstHtmlPart( | 333 switch (block.kind) { |
174 '<div class="identical${alternating ? '1' : '2'}">'); | 334 case DiffKind.UNMATCHED: |
175 HtmlPart end = new ConstHtmlPart('</div>'); | 335 className = 'cell'; |
176 alternating = !alternating; | 336 break; |
177 outputLines[0].add(start); | 337 case DiffKind.MATCHING: |
178 outputLines[1].add(start); | 338 className = 'cell corresponding${alternating ? '1' : '2'}'; |
179 f(); | 339 alternating = !alternating; |
180 outputLines[0].add(end); | 340 break; |
181 outputLines[1].add(end); | 341 case DiffKind.IDENTICAL: |
342 className = 'cell identical${alternating ? '1' : '2'}'; | |
343 alternating = !alternating; | |
344 break; | |
345 } | |
346 sb.write('<tr>'); | |
347 for (int index = 0; index < 3; index++) { | |
348 sb.write('''<td class="$className column$index">'''); | |
349 List<HtmlPart> lines = block.getColumn(index); | |
350 if (lines.isNotEmpty) { | |
351 for (HtmlPart line in lines) { | |
352 sb.write('<p class="line">'); | |
353 if (index < printContexts.length) { | |
354 line.printHtmlOn(sb, printContexts[index]); | |
355 } else { | |
356 line.printHtmlOn(sb, new HtmlPrintContext()); | |
357 } | |
358 sb.write('</p>'); | |
359 } | |
360 } | |
361 sb.write('''</td>'''); | |
362 } | |
363 sb.write('</tr>'); | |
182 } | 364 } |
183 | 365 |
184 /// Output code lines in [range] from input number [index], padding the other | 366 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 | 367 |
195 /// Output code lines of the [indices] from the corresponding inputs. | 368 addCell(outputs[0].coverage); |
196 void addBoth(List<int> indices) { | 369 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 | 370 |
366 sb.write(''' | 371 sb.write(''' |
367 </tr></table> | 372 </table> |
368 </body> | 373 </body> |
369 </html> | 374 </html> |
370 '''); | 375 '''); |
371 | 376 |
372 new File(out).writeAsStringSync(sb.toString()); | 377 new File(out).writeAsStringSync(sb.toString()); |
373 print('Diff generated in $out'); | 378 print('Diff generated in $out'); |
374 } | 379 } |
375 | 380 |
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 { | 381 class CodeLinesResult { |
810 final List<CodeLine> codeLines; | 382 final List<CodeLine> codeLines; |
811 final Coverage coverage; | 383 final Coverage coverage; |
384 final Map<int, Element> elementMap; | |
385 final SourceFileManager sourceFileManager; | |
812 | 386 |
813 CodeLinesResult(this.codeLines, this.coverage); | 387 CodeLinesResult(this.codeLines, this.coverage, |
388 this.elementMap, this.sourceFileManager); | |
814 } | 389 } |
815 | 390 |
816 /// Compute [CodeLine]s and [Coverage] for [filename] using the given [options]. | 391 /// Compute [CodeLine]s and [Coverage] for [filename] using the given [options]. |
817 Future<CodeLinesResult> computeCodeLines( | 392 Future<CodeLinesResult> computeCodeLines( |
818 List<String> options, | 393 List<String> options, |
819 String filename, | 394 String filename, |
820 {bool addAnnotations: true}) async { | 395 {bool addAnnotations: true}) async { |
821 SourceMapProcessor processor = new SourceMapProcessor(filename); | 396 SourceMapProcessor processor = new SourceMapProcessor(filename); |
822 List<SourceMapInfo> sourceMapInfoList = | 397 SourceMaps sourceMaps = |
823 await processor.process(options, perElement: false); | 398 await processor.process(options, perElement: true, forMain: true); |
824 | 399 |
825 const int WITH_SOURCE_INFO = 0; | 400 const int WITH_SOURCE_INFO = 0; |
826 const int WITHOUT_SOURCE_INFO = 1; | 401 const int WITHOUT_SOURCE_INFO = 1; |
827 const int ADDITIONAL_SOURCE_INFO = 2; | 402 const int ADDITIONAL_SOURCE_INFO = 2; |
403 const int UNUSED_SOURCE_INFO = 3; | |
828 | 404 |
829 for (SourceMapInfo info in sourceMapInfoList) { | 405 SourceMapInfo info = sourceMaps.mainSourceMapInfo; |
830 if (info.element != null) continue; | |
831 | 406 |
832 List<CodeLine> codeLines; | 407 List<CodeLine> codeLines; |
833 Coverage coverage = new Coverage(); | 408 Coverage coverage = new Coverage(); |
834 List<Annotation> annotations = <Annotation>[]; | 409 List<Annotation> annotations = <Annotation>[]; |
835 | 410 |
836 String code = info.code; | 411 void addAnnotation(int id, int offset, String title) { |
837 TraceGraph graph = createTraceGraph(info, coverage); | 412 annotations.add(new Annotation(id, offset, title)); |
838 if (addAnnotations) { | 413 } |
839 Set<js.Node> mappedNodes = new Set<js.Node>(); | 414 |
840 for (TraceStep step in graph.steps) { | 415 String code = info.code; |
416 TraceGraph graph = createTraceGraph(info, coverage); | |
417 if (addAnnotations) { | |
418 Set<js.Node> mappedNodes = new Set<js.Node>(); | |
419 | |
420 void addSourceLocations( | |
421 int kind, int offset, List<SourceLocation> locations, String prefix) { | |
422 | |
423 addAnnotation(kind, offset, | |
424 '${prefix}${locations | |
425 .where((l) => l != null) | |
426 .map((l) => l.shortText) | |
427 .join('\n')}'); | |
428 } | |
429 | |
430 bool addSourceLocationsForNode(int kind, js.Node node, String prefix) { | |
431 Map<int, List<SourceLocation>> locations = info.nodeMap[node]; | |
432 if (locations == null || locations.isEmpty) { | |
433 return false; | |
434 } | |
435 locations.forEach( | |
436 (int offset, List<SourceLocation> locations) { | |
437 addSourceLocations(kind, offset, locations, | |
438 '${prefix}\n${truncate(nodeToString(node), 80)}\n'); | |
439 }); | |
440 mappedNodes.add(node); | |
441 return true; | |
442 } | |
443 | |
444 | |
445 for (TraceStep step in graph.steps) { | |
446 String title = '${step.id}:${step.kind}:${step.offset}'; | |
447 if (!addSourceLocationsForNode(WITH_SOURCE_INFO, step.node, title)) { | |
841 int offset; | 448 int offset; |
842 if (options.contains(USE_NEW_SOURCE_INFO)) { | 449 if (options.contains(USE_NEW_SOURCE_INFO)) { |
843 offset = step.offset.subexpressionOffset; | 450 offset = step.offset.subexpressionOffset; |
844 } else { | 451 } else { |
845 offset = info.jsCodePositions[step.node].startPosition; | 452 offset = info.jsCodePositions[step.node].startPosition; |
846 } | 453 } |
847 if (offset != null) { | 454 if (offset != null) { |
848 int id = step.sourceLocation != null | 455 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 } | 456 } |
862 } | 457 } |
863 } | 458 } |
864 codeLines = convertAnnotatedCodeToCodeLines( | 459 for (js.Node node in info.nodeMap.nodes) { |
865 code, | 460 if (!mappedNodes.contains(node)) { |
866 annotations, | 461 addSourceLocationsForNode(ADDITIONAL_SOURCE_INFO, node, ''); |
867 colorScheme: new CustomColorScheme( | 462 } |
868 single: (int id) { | 463 } |
869 if (id == WITH_SOURCE_INFO) { | 464 SourceLocationCollector collector = new SourceLocationCollector(); |
870 return WITH_SOURCE_INFO_STYLE; | 465 info.node.accept(collector); |
871 } else if (id == ADDITIONAL_SOURCE_INFO) { | 466 collector.sourceLocations.forEach( |
872 return ADDITIONAL_SOURCE_INFO_STYLE; | 467 (js.Node node, List<SourceLocation> locations) { |
873 } | 468 if (!mappedNodes.contains(node)) { |
874 return WITHOUT_SOURCE_INFO_STYLE; | 469 int offset = info.jsCodePositions[node].startPosition; |
875 }, | 470 addSourceLocations(UNUSED_SOURCE_INFO, offset, locations, ''); |
876 multi: (List ids) { | 471 } |
877 if (ids.contains(WITH_SOURCE_INFO)) { | 472 }); |
878 return WITH_SOURCE_INFO_STYLE; | 473 } |
879 } else if (ids.contains(ADDITIONAL_SOURCE_INFO)) { | 474 |
880 return ADDITIONAL_SOURCE_INFO_STYLE; | 475 StringSourceFile sourceFile = new StringSourceFile.fromName(filename, code); |
881 } | 476 Map<int, Element> elementMap = <int, Element>{}; |
882 return WITHOUT_SOURCE_INFO_STYLE; | 477 sourceMaps.elementSourceMapInfos.forEach( |
478 (Element element, SourceMapInfo info) { | |
479 CodePosition position = info.jsCodePositions[info.node]; | |
480 elementMap[sourceFile.getLine(position.startPosition)] = element; | |
481 }); | |
482 | |
483 codeLines = convertAnnotatedCodeToCodeLines( | |
484 code, | |
485 annotations, | |
486 colorScheme: new CustomColorScheme( | |
487 single: (int id) { | |
488 if (id == WITH_SOURCE_INFO) { | |
489 return WITH_SOURCE_INFO_STYLE; | |
490 } else if (id == ADDITIONAL_SOURCE_INFO) { | |
491 return ADDITIONAL_SOURCE_INFO_STYLE; | |
492 } else if (id == UNUSED_SOURCE_INFO) { | |
493 return UNUSED_SOURCE_INFO_STYLE; | |
883 } | 494 } |
884 )); | 495 return WITHOUT_SOURCE_INFO_STYLE; |
885 return new CodeLinesResult(codeLines, coverage); | 496 }, |
497 multi: (List ids) { | |
498 if (ids.contains(WITH_SOURCE_INFO)) { | |
499 return WITH_SOURCE_INFO_STYLE; | |
500 } else if (ids.contains(ADDITIONAL_SOURCE_INFO)) { | |
501 return ADDITIONAL_SOURCE_INFO_STYLE; | |
502 } else if (ids.contains(UNUSED_SOURCE_INFO)) { | |
503 return UNUSED_SOURCE_INFO_STYLE; | |
504 } | |
505 return WITHOUT_SOURCE_INFO_STYLE; | |
506 } | |
507 )); | |
508 return new CodeLinesResult(codeLines, coverage, elementMap, | |
509 sourceMaps.sourceFileManager); | |
510 } | |
511 | |
512 /// Visitor that computes a map from [js.Node]s to all attached source | |
513 /// locations. | |
514 class SourceLocationCollector extends js.BaseVisitor { | |
515 Map<js.Node, List<SourceLocation>> sourceLocations = | |
516 <js.Node, List<SourceLocation>>{}; | |
517 | |
518 @override | |
519 visitNode(js.Node node) { | |
520 SourceInformation sourceInformation = node.sourceInformation; | |
521 if (sourceInformation != null) { | |
522 sourceLocations[node] = sourceInformation.sourceLocations; | |
523 } | |
524 node.visitChildren(this); | |
886 } | 525 } |
887 } | 526 } |
527 | |
528 /// Compute a [CodeSource] for source span of [element]. | |
529 CodeSource codeSourceFromElement(Element element) { | |
530 CodeKind kind; | |
531 Uri uri; | |
532 String name; | |
533 int begin; | |
534 int end; | |
535 if (element.isLibrary) { | |
536 LibraryElement library = element; | |
537 kind = CodeKind.LIBRARY; | |
538 name = library.libraryOrScriptName; | |
539 uri = library.entryCompilationUnit.script.resourceUri; | |
540 } else if (element.isClass) { | |
541 kind = CodeKind.CLASS; | |
542 name = element.name; | |
543 uri = element.compilationUnit.script.resourceUri; | |
544 } else { | |
545 AstElement astElement = element.implementation; | |
546 kind = CodeKind.MEMBER; | |
547 uri = astElement.compilationUnit.script.resourceUri; | |
548 name = computeElementNameForSourceMaps(astElement); | |
549 if (astElement.hasNode) { | |
550 begin = astElement.node.getBeginToken().charOffset; | |
551 end = astElement.node.getEndToken().charEnd; | |
552 } | |
553 } | |
554 return new CodeSource(kind, uri, name, begin, end); | |
555 } | |
OLD | NEW |