OLD | NEW |
---|---|
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 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 /// Source information system mapping that attempts a semantic mapping between | 5 /// Source information system mapping that attempts a semantic mapping between |
6 /// offsets of JavaScript code points to offsets of Dart code points. | 6 /// offsets of JavaScript code points to offsets of Dart code points. |
7 | 7 |
8 library dart2js.source_information.position; | 8 library dart2js.source_information.position; |
9 | 9 |
10 import '../common.dart'; | 10 import '../common.dart'; |
11 import '../elements/elements.dart' show | 11 import '../elements/elements.dart' show |
12 AstElement, | 12 AstElement, |
13 LocalElement; | 13 LocalElement; |
14 import '../js/js.dart' as js; | 14 import '../js/js.dart' as js; |
15 import '../js/js_source_mapping.dart'; | 15 import '../js/js_source_mapping.dart'; |
16 import '../js/js_debug.dart'; | 16 import '../js/js_debug.dart'; |
17 import '../tree/tree.dart' show | 17 import '../tree/tree.dart' show |
18 Node, | 18 Node, |
19 Send; | 19 Send; |
20 | 20 |
21 import 'code_output.dart' show | |
22 CodeBuffer; | |
21 import 'source_file.dart'; | 23 import 'source_file.dart'; |
22 import 'source_information.dart'; | 24 import 'source_information.dart'; |
23 | 25 |
24 /// [SourceInformation] that consists of an offset position into the source | 26 /// [SourceInformation] that consists of an offset position into the source |
25 /// code. | 27 /// code. |
26 class PositionSourceInformation extends SourceInformation { | 28 class PositionSourceInformation extends SourceInformation { |
27 @override | 29 @override |
28 final SourceLocation startPosition; | 30 final SourceLocation startPosition; |
29 | 31 |
30 @override | 32 @override |
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
95 if (startPosition != null) { | 97 if (startPosition != null) { |
96 return _computeText('${startPosition.sourceUri}'); | 98 return _computeText('${startPosition.sourceUri}'); |
97 } else { | 99 } else { |
98 return _computeText('${closingPosition.sourceUri}'); | 100 return _computeText('${closingPosition.sourceUri}'); |
99 } | 101 } |
100 } | 102 } |
101 } | 103 } |
102 | 104 |
103 class PositionSourceInformationStrategy | 105 class PositionSourceInformationStrategy |
104 implements JavaScriptSourceInformationStrategy { | 106 implements JavaScriptSourceInformationStrategy { |
105 const PositionSourceInformationStrategy(); | 107 Coverage coverage; |
Siggi Cherem (dart-lang)
2016/01/21 18:18:34
revert?
I couldn't find where you create a Positi
Johnni Winther
2016/01/22 15:12:38
Reverted
| |
108 | |
109 PositionSourceInformationStrategy([this.coverage]); | |
106 | 110 |
107 @override | 111 @override |
108 SourceInformationBuilder createBuilderForContext(AstElement element) { | 112 SourceInformationBuilder createBuilderForContext(AstElement element) { |
109 return new PositionSourceInformationBuilder(element); | 113 return new PositionSourceInformationBuilder(element); |
110 } | 114 } |
111 | 115 |
112 @override | 116 @override |
113 SourceInformationProcessor createProcessor(SourceMapper mapper) { | 117 SourceInformationProcessor createProcessor(SourceMapper mapper) { |
114 return new PositionSourceInformationProcessor(mapper); | 118 return new PositionSourceInformationProcessor(mapper, coverage); |
115 } | 119 } |
120 | |
121 void onComplete() { | |
122 if (coverage != null) { | |
123 coverage.collapse(); | |
124 } | |
125 } | |
126 | |
127 @override | |
128 SourceInformation buildPreambleMarker() { | |
129 return const PreambleMarker(); | |
130 } | |
131 } | |
132 | |
133 /// Marker used to tag the root nodes of non-preamble code. | |
sra1
2016/01/21 15:52:13
non-preamble -> preamble?
Johnni Winther
2016/01/22 15:12:38
Non-preamble! In output like
...
var dart = [["_
| |
134 /// | |
135 /// This is needed to be able to distinguish JavaScript nodes that shouldn't | |
136 /// have source locations (like the premable) from the nodes that should | |
137 /// (like functions compiled from Dart code). | |
138 class PreambleMarker extends SourceInformation { | |
139 const PreambleMarker(); | |
140 | |
141 @override | |
142 String get shortText => ''; | |
143 | |
144 @override | |
145 List<SourceLocation> get sourceLocations => const <SourceLocation>[]; | |
146 | |
147 @override | |
148 SourceSpan get sourceSpan => new SourceSpan(null, null, null); | |
116 } | 149 } |
117 | 150 |
118 /// [SourceInformationBuilder] that generates [PositionSourceInformation]. | 151 /// [SourceInformationBuilder] that generates [PositionSourceInformation]. |
119 class PositionSourceInformationBuilder implements SourceInformationBuilder { | 152 class PositionSourceInformationBuilder implements SourceInformationBuilder { |
120 final SourceFile sourceFile; | 153 final SourceFile sourceFile; |
121 final String name; | 154 final String name; |
122 | 155 |
123 PositionSourceInformationBuilder(AstElement element) | 156 PositionSourceInformationBuilder(AstElement element) |
124 : sourceFile = element.implementation.compilationUnit.script.file, | 157 : sourceFile = element.implementation.compilationUnit.script.file, |
125 name = computeElementNameForSourceMaps(element); | 158 name = computeElementNameForSourceMaps(element); |
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
218 } | 251 } |
219 } | 252 } |
220 | 253 |
221 /// The start, end and closing offsets for a [js.Node]. | 254 /// The start, end and closing offsets for a [js.Node]. |
222 class CodePosition { | 255 class CodePosition { |
223 final int startPosition; | 256 final int startPosition; |
224 final int endPosition; | 257 final int endPosition; |
225 final int closingPosition; | 258 final int closingPosition; |
226 | 259 |
227 CodePosition(this.startPosition, this.endPosition, this.closingPosition); | 260 CodePosition(this.startPosition, this.endPosition, this.closingPosition); |
261 | |
262 int getPosition(CodePositionKind kind) { | |
263 switch (kind) { | |
264 case CodePositionKind.START: | |
265 return startPosition; | |
266 case CodePositionKind.END: | |
267 return endPosition; | |
268 case CodePositionKind.CLOSING: | |
269 return closingPosition; | |
270 } | |
271 } | |
272 | |
273 String toString() { | |
274 return 'CodePosition(start=$startPosition,' | |
275 'end=$endPosition,closing=$closingPosition)'; | |
276 } | |
277 } | |
278 | |
279 /// A map from a [js.Node] to its [CodePosition]. | |
280 abstract class CodePositionMap { | |
281 CodePosition operator [](js.Node node); | |
228 } | 282 } |
229 | 283 |
230 /// Registry for mapping [js.Node]s to their [CodePosition]. | 284 /// Registry for mapping [js.Node]s to their [CodePosition]. |
231 class CodePositionRecorder { | 285 class CodePositionRecorder implements CodePositionMap { |
232 Map<js.Node, CodePosition> _codePositionMap = | 286 Map<js.Node, CodePosition> _codePositionMap = |
233 new Map<js.Node, CodePosition>.identity(); | 287 new Map<js.Node, CodePosition>.identity(); |
234 | 288 |
235 void registerPositions(js.Node node, | 289 void registerPositions(js.Node node, |
236 int startPosition, | 290 int startPosition, |
237 int endPosition, | 291 int endPosition, |
238 int closingPosition) { | 292 int closingPosition) { |
239 registerCodePosition(node, | 293 registerCodePosition(node, |
240 new CodePosition(startPosition, endPosition, closingPosition)); | 294 new CodePosition(startPosition, endPosition, closingPosition)); |
241 } | 295 } |
242 | 296 |
243 void registerCodePosition(js.Node node, CodePosition codePosition) { | 297 void registerCodePosition(js.Node node, CodePosition codePosition) { |
244 _codePositionMap[node] = codePosition; | 298 _codePositionMap[node] = codePosition; |
245 } | 299 } |
246 | 300 |
247 CodePosition operator [](js.Node node) => _codePositionMap[node]; | 301 CodePosition operator [](js.Node node) => _codePositionMap[node]; |
248 } | 302 } |
249 | 303 |
250 enum SourcePositionKind { | 304 enum SourcePositionKind { |
251 START, | 305 START, |
252 CLOSING, | 306 CLOSING, |
253 END, | 307 END, |
254 } | 308 } |
255 | 309 |
256 enum CodePositionKind { | 310 enum CodePositionKind { |
Siggi Cherem (dart-lang)
2016/01/21 18:18:34
nit: since this and SourcePositionKind are the sam
Johnni Winther
2016/01/22 15:12:38
They are semantically different and might diverge.
| |
257 START, | 311 START, |
258 CLOSING, | 312 CLOSING, |
Siggi Cherem (dart-lang)
2016/01/21 18:18:34
document what's the difference between closing and
Johnni Winther
2016/01/22 15:12:38
Add doc comments with examples.
| |
259 END, | 313 END, |
260 } | 314 } |
261 | 315 |
262 /// Processor that associates [SourceLocation]s from [SourceInformation] on | 316 /// Processor that associates [SourceLocation]s from [SourceInformation] on |
263 /// [js.Node]s with the target offsets in a [SourceMapper]. | 317 /// [js.Node]s with the target offsets in a [SourceMapper]. |
264 class PositionSourceInformationProcessor | 318 class PositionSourceInformationProcessor implements SourceInformationProcessor { |
265 extends js.BaseVisitor implements SourceInformationProcessor { | 319 final CodePositionRecorder codePositionRecorder = new CodePositionRecorder(); |
266 final CodePositionRecorder codePositions = new CodePositionRecorder(); | 320 CodePositionMap codePositionMap; |
267 final SourceMapper sourceMapper; | 321 List<TraceListener> traceListeners; |
268 | 322 |
269 PositionSourceInformationProcessor(this.sourceMapper); | 323 PositionSourceInformationProcessor( |
270 | 324 SourceMapper sourceMapper, |
271 void process(js.Node node) { | 325 [Coverage coverage]) { |
272 node.accept(this); | 326 codePositionMap = coverage != null |
273 } | 327 ? new CodePositionCoverage(codePositionRecorder, coverage) |
274 | 328 : codePositionRecorder; |
275 void visitChildren(js.Node node) { | 329 traceListeners = [new PositionTraceListener(sourceMapper)]; |
276 node.visitChildren(this); | 330 if (coverage != null) { |
277 } | 331 traceListeners.add(new CoverageListener(coverage)); |
278 | 332 } |
279 CodePosition getCodePosition(js.Node node) { | 333 } |
280 return codePositions[node]; | 334 |
281 } | 335 void process(js.Node node, CodeBuffer codeBuffer) { |
282 | 336 new JavaScriptTracer(codePositionMap, traceListeners).apply(node); |
283 /// Associates [sourceInformation] with the JavaScript [node]. | |
284 /// | |
285 /// The offset into the JavaScript code is computed by pulling the | |
286 /// [codePositionKind] from the code positions associated with | |
287 /// [codePositionNode]. | |
288 /// | |
289 /// The mapped Dart source location is computed by pulling the | |
290 /// [sourcePositionKind] source location from [sourceInformation]. | |
291 void apply(js.Node node, | |
292 js.Node codePositionNode, | |
293 CodePositionKind codePositionKind, | |
294 SourceInformation sourceInformation, | |
295 SourcePositionKind sourcePositionKind) { | |
296 if (sourceInformation != null) { | |
297 CodePosition codePosition = getCodePosition(codePositionNode); | |
298 // We should always have recorded the needed code positions. | |
299 assert(invariant( | |
300 NO_LOCATION_SPANNABLE, | |
301 codePosition != null, | |
302 message: | |
303 "Code position missing for " | |
304 "${nodeToString(codePositionNode)}:\n" | |
305 "${DebugPrinter.prettyPrint(node)}")); | |
306 if (codePosition == null) return; | |
307 int codeLocation; | |
308 SourceLocation sourceLocation; | |
309 switch (codePositionKind) { | |
310 case CodePositionKind.START: | |
311 codeLocation = codePosition.startPosition; | |
312 break; | |
313 case CodePositionKind.CLOSING: | |
314 codeLocation = codePosition.closingPosition; | |
315 break; | |
316 case CodePositionKind.END: | |
317 codeLocation = codePosition.endPosition; | |
318 break; | |
319 } | |
320 switch (sourcePositionKind) { | |
321 case SourcePositionKind.START: | |
322 sourceLocation = sourceInformation.startPosition; | |
323 break; | |
324 case SourcePositionKind.CLOSING: | |
325 sourceLocation = sourceInformation.closingPosition; | |
326 break; | |
327 case SourcePositionKind.END: | |
328 sourceLocation = sourceInformation.endPosition; | |
329 break; | |
330 } | |
331 if (codeLocation != null && sourceLocation != null) { | |
332 sourceMapper.register(node, codeLocation, sourceLocation); | |
333 } | |
334 } | |
335 } | |
336 | |
337 @override | |
338 visitNode(js.Node node) { | |
339 SourceInformation sourceInformation = node.sourceInformation; | |
340 if (sourceInformation != null) { | |
341 /// Associates the left-most position of the JS code with the left-most | |
342 /// position of the Dart code. | |
343 apply(node, | |
344 node, CodePositionKind.START, | |
345 sourceInformation, SourcePositionKind.START); | |
346 } | |
347 visitChildren(node); | |
348 } | |
349 | |
350 @override | |
351 visitFun(js.Fun node) { | |
352 SourceInformation sourceInformation = node.sourceInformation; | |
353 if (sourceInformation != null) { | |
354 /// Associates the end brace of the JavaScript function with the end brace | |
355 /// of the Dart function (or the `;` in case of arrow notation). | |
356 apply(node, | |
357 node, CodePositionKind.CLOSING, | |
358 sourceInformation, SourcePositionKind.CLOSING); | |
359 } | |
360 | |
361 visitChildren(node); | |
362 } | |
363 | |
364 @override | |
365 visitExpressionStatement(js.ExpressionStatement node) { | |
366 visitChildren(node); | |
367 } | |
368 | |
369 @override | |
370 visitBinary(js.Binary node) { | |
371 visitChildren(node); | |
372 } | |
373 | |
374 @override | |
375 visitAccess(js.PropertyAccess node) { | |
376 visitChildren(node); | |
377 } | |
378 | |
379 @override | |
380 visitCall(js.Call node) { | |
381 SourceInformation sourceInformation = node.sourceInformation; | |
382 if (sourceInformation != null) { | |
383 if (node.target is js.PropertyAccess) { | |
384 js.PropertyAccess access = node.target; | |
385 js.Node target = access; | |
386 bool pureAccess = false; | |
387 while (target is js.PropertyAccess) { | |
388 js.PropertyAccess targetAccess = target; | |
389 if (targetAccess.receiver is js.VariableUse || | |
390 targetAccess.receiver is js.This) { | |
391 pureAccess = true; | |
392 break; | |
393 } else { | |
394 target = targetAccess.receiver; | |
395 } | |
396 } | |
397 if (pureAccess) { | |
398 // a.m() this.m() a.b.c.d.m() | |
399 // ^ ^ ^ | |
400 apply( | |
401 node, | |
402 node, | |
403 CodePositionKind.START, | |
404 sourceInformation, | |
405 SourcePositionKind.START); | |
406 } else { | |
407 // *.m() *.a.b.c.d.m() | |
408 // ^ ^ | |
409 apply( | |
410 node, | |
411 access.selector, | |
412 CodePositionKind.START, | |
413 sourceInformation, | |
414 SourcePositionKind.CLOSING); | |
415 } | |
416 } else if (node.target is js.VariableUse) { | |
417 // m() | |
418 // ^ | |
419 apply( | |
420 node, | |
421 node, | |
422 CodePositionKind.START, | |
423 sourceInformation, | |
424 SourcePositionKind.START); | |
425 } else if (node.target is js.Fun || node.target is js.New) { | |
426 // function(){}() new Function("...")() | |
427 // ^ ^ | |
428 apply( | |
429 node, | |
430 node.target, | |
431 CodePositionKind.END, | |
432 sourceInformation, | |
433 SourcePositionKind.CLOSING); | |
434 } else { | |
435 assert(invariant(NO_LOCATION_SPANNABLE, false, | |
436 message: "Unexpected property access ${nodeToString(node)}:\n" | |
437 "${DebugPrinter.prettyPrint(node)}")); | |
438 // Don't know.... | |
439 apply( | |
440 node, | |
441 node, | |
442 CodePositionKind.START, | |
443 sourceInformation, | |
444 SourcePositionKind.START); | |
445 } | |
446 } | |
447 visitChildren(node); | |
448 } | 337 } |
449 | 338 |
450 @override | 339 @override |
451 void onPositions(js.Node node, | 340 void onPositions(js.Node node, |
452 int startPosition, | 341 int startPosition, |
453 int endPosition, | 342 int endPosition, |
454 int closingPosition) { | 343 int closingPosition) { |
455 codePositions.registerPositions( | 344 codePositionRecorder.registerPositions( |
456 node, startPosition, endPosition, closingPosition); | 345 node, startPosition, endPosition, closingPosition); |
457 } | 346 } |
458 } | 347 } |
348 | |
349 /// [TraceListener] that register [SourceLocation]s with a [SourceMapper]. | |
350 class PositionTraceListener extends TraceListener { | |
351 final SourceMapper sourceMapper; | |
352 | |
353 PositionTraceListener(this.sourceMapper); | |
354 | |
355 @override | |
356 void onStep(js.Node node, Offset offset, StepKind kind) { | |
357 SourceInformation sourceInformation = node.sourceInformation; | |
358 if (sourceInformation == null) return; | |
359 | |
360 SourcePositionKind sourcePositionKind = SourcePositionKind.START; | |
361 switch (kind) { | |
362 case StepKind.FUN: | |
363 sourcePositionKind = SourcePositionKind.CLOSING; | |
364 break; | |
365 case StepKind.CALL: | |
366 CallPosition callPosition = | |
367 CallPosition.getSemanticPositionForCall(node); | |
368 sourcePositionKind = callPosition.sourcePositionKind; | |
369 break; | |
370 case StepKind.NEW: | |
371 case StepKind.RETURN: | |
372 case StepKind.BREAK: | |
373 case StepKind.CONTINUE: | |
374 case StepKind.THROW: | |
375 case StepKind.EXPRESSION_STATEMENT: | |
376 case StepKind.IF_CONDITION: | |
377 case StepKind.FOR_INITIALIZER: | |
378 case StepKind.FOR_CONDITION: | |
379 case StepKind.FOR_UPDATE: | |
380 case StepKind.WHILE_CONDITION: | |
381 case StepKind.DO_CONDITION: | |
382 case StepKind.SWITCH_EXPRESSION: | |
Siggi Cherem (dart-lang)
2016/01/21 18:18:34
nit: replace the last 12 lines with 'default: ' ?
Johnni Winther
2016/01/22 15:12:38
I avoid using default to get the best case coverag
| |
383 break; | |
384 } | |
385 int codeLocation = offset.codeOffset; | |
386 SourceLocation sourceLocation; | |
Siggi Cherem (dart-lang)
2016/01/21 18:18:34
optional: make this a helper `getLocation` method
Johnni Winther
2016/01/22 15:12:38
Done.
| |
387 switch (sourcePositionKind) { | |
388 case SourcePositionKind.START: | |
389 sourceLocation = sourceInformation.startPosition; | |
390 break; | |
391 case SourcePositionKind.CLOSING: | |
392 sourceLocation = sourceInformation.closingPosition; | |
393 break; | |
394 case SourcePositionKind.END: | |
395 sourceLocation = sourceInformation.endPosition; | |
396 break; | |
397 } | |
398 if (codeLocation != null && sourceLocation != null) { | |
399 sourceMapper.register(node, codeLocation, sourceLocation); | |
400 } | |
401 } | |
402 } | |
403 | |
404 /// The position of a [js.Call] node. | |
405 class CallPosition { | |
406 final js.Node node; | |
407 final CodePositionKind codePositionKind; | |
408 final SourcePositionKind sourcePositionKind; | |
409 | |
410 CallPosition(this.node, this.codePositionKind, this.sourcePositionKind); | |
411 | |
412 /// Computes the [CallPosition] for [node]. | |
413 static CallPosition getSemanticPositionForCall(js.Call node) { | |
414 if (node.target is js.PropertyAccess) { | |
415 js.PropertyAccess access = node.target; | |
416 js.Node target = access; | |
417 bool pureAccess = false; | |
418 while (target is js.PropertyAccess) { | |
419 js.PropertyAccess targetAccess = target; | |
420 if (targetAccess.receiver is js.VariableUse || | |
421 targetAccess.receiver is js.This) { | |
422 pureAccess = true; | |
423 break; | |
424 } else { | |
425 target = targetAccess.receiver; | |
426 } | |
427 } | |
428 if (pureAccess) { | |
429 // a.m() this.m() a.b.c.d.m() | |
430 // ^ ^ ^ | |
431 return new CallPosition( | |
432 node, | |
433 CodePositionKind.START, | |
434 SourcePositionKind.START); | |
435 } else { | |
436 // *.m() *.a.b.c.d.m() | |
437 // ^ ^ | |
438 return new CallPosition( | |
439 access.selector, | |
440 CodePositionKind.START, | |
441 SourcePositionKind.CLOSING); | |
442 } | |
443 } else if (node.target is js.VariableUse) { | |
444 // m() | |
445 // ^ | |
446 return new CallPosition( | |
447 node, | |
448 CodePositionKind.START, | |
449 SourcePositionKind.START); | |
450 } else if (node.target is js.Fun || node.target is js.New) { | |
451 // function(){}() new Function("...")() | |
452 // ^ ^ | |
453 return new CallPosition( | |
454 node.target, | |
455 CodePositionKind.END, | |
456 SourcePositionKind.CLOSING); | |
457 } else { | |
458 assert(invariant(NO_LOCATION_SPANNABLE, false, | |
459 message: "Unexpected property access ${nodeToString(node)}:\n" | |
460 "${DebugPrinter.prettyPrint(node)}")); | |
461 // Don't know.... | |
462 return new CallPosition( | |
463 node, | |
464 CodePositionKind.START, | |
465 SourcePositionKind.START); | |
466 } | |
467 } | |
468 } | |
469 | |
470 class Offset { | |
471 /// The offset of the statement containing the step. | |
Siggi Cherem (dart-lang)
2016/01/21 18:18:34
the statement => the enclosing statement?
(I assu
Johnni Winther
2016/01/22 15:12:38
Yes. Comment updated.
| |
472 final int statementOffset; | |
473 | |
474 /// The left-to-right offset of the step. | |
Siggi Cherem (dart-lang)
2016/01/21 18:18:34
can you clarify? Not sure what left-to-right means
Johnni Winther
2016/01/22 15:12:38
Comments updated and examples added.
| |
475 final int leftToRightOffset; | |
476 | |
477 /// The subexpression offset of the step. | |
Siggi Cherem (dart-lang)
2016/01/21 18:18:34
is this relative to the beginning of the file or t
Johnni Winther
2016/01/22 15:12:38
Ditto.
| |
478 final int codeOffset; | |
479 | |
480 Offset(this.statementOffset, this.leftToRightOffset, this.codeOffset); | |
481 | |
482 String toString() { | |
483 return 'Offset[statementOffset=$statementOffset,' | |
484 'leftToRightOffset=$leftToRightOffset,' | |
485 'codeOffset=$codeOffset]'; | |
486 } | |
487 } | |
488 | |
489 enum BranchKind { | |
490 CONDITION, | |
491 LOOP, | |
492 CATCH, | |
493 FINALLY, | |
494 CASE, | |
495 } | |
496 | |
497 enum StepKind { | |
498 FUN, | |
499 CALL, | |
500 NEW, | |
501 RETURN, | |
502 BREAK, | |
503 CONTINUE, | |
504 THROW, | |
505 EXPRESSION_STATEMENT, | |
506 IF_CONDITION, | |
507 FOR_INITIALIZER, | |
508 FOR_CONDITION, | |
509 FOR_UPDATE, | |
510 WHILE_CONDITION, | |
511 DO_CONDITION, | |
512 SWITCH_EXPRESSION, | |
513 } | |
514 | |
515 /// Listener for the [JavaScriptTracer]. | |
516 abstract class TraceListener { | |
517 /// Called before [root] node is procesed by the [JavaScriptTracer]. | |
518 void onStart(js.Node root) {} | |
519 | |
520 /// Called after [root] node has been procesed by the [JavaScriptTracer]. | |
521 void onEnd(js.Node root) {} | |
522 | |
523 /// Called when a branch of the given [kind] is started. [value] is provided | |
524 /// to distinguish true/false branches of [BranchKind.CONDITION] and cases of | |
525 /// [Branch.CASE]. | |
526 void pushBranch(BranchKind kind, [value]) {} | |
527 | |
528 /// Called when the current branch ends. | |
529 void popBranch() {} | |
530 | |
531 /// Called when [node] defines a step of the given [kind] at the given | |
532 /// [offset] when the generated JavaScript code. | |
533 void onStep(js.Node node, Offset offset, StepKind kind) {} | |
534 } | |
535 | |
536 /// Visitor that computes the [js.Node]s the are part of the JavaScript | |
537 /// steppable execution and thus needs source mapping locations. | |
538 class JavaScriptTracer extends js.BaseVisitor { | |
539 final CodePositionMap codePositions; | |
540 final List<TraceListener> listeners; | |
541 | |
542 /// The steps added by subexpressions. | |
543 List steps = []; | |
544 | |
545 /// The offset of the current statement. | |
546 int statementOffset; | |
547 | |
548 /// The current offset in left-to-right progression. | |
549 int leftToRightOffset; | |
550 | |
551 /// The offset of the surrounding statement, used for the first subexpression. | |
552 int offsetPosition; | |
553 | |
554 bool active; | |
555 | |
556 JavaScriptTracer(this.codePositions, | |
557 this.listeners, | |
558 {this.active: false}); | |
559 | |
560 void notifyStart(js.Node node) { | |
561 listeners.forEach((listener) => listener.onStart(node)); | |
562 } | |
563 | |
564 void notifyEnd(js.Node node) { | |
565 listeners.forEach((listener) => listener.onEnd(node)); | |
566 } | |
567 | |
568 void notifyPushBranch(BranchKind kind, [value]) { | |
569 if (active) { | |
570 listeners.forEach((listener) => listener.pushBranch(kind, value)); | |
571 } | |
572 } | |
573 | |
574 void notifyPopBranch() { | |
575 if (active) { | |
576 listeners.forEach((listener) => listener.popBranch()); | |
577 } | |
578 } | |
579 | |
580 void notifyStep(js.Node node, Offset offset, StepKind kind) { | |
581 if (active) { | |
582 listeners.forEach((listener) => listener.onStep(node, offset, kind)); | |
583 } | |
584 } | |
585 | |
586 void apply(js.Node node) { | |
587 notifyStart(node); | |
588 node.accept(this); | |
589 notifyEnd(node); | |
590 } | |
591 | |
592 @override | |
593 visitNode(js.Node node) { | |
594 node.visitChildren(this); | |
595 } | |
596 | |
597 visit(js.Node node, [BranchKind branch, value]) { | |
598 if (node != null) { | |
599 if (branch != null) { | |
600 notifyPushBranch(branch, value); | |
601 node.accept(this); | |
602 notifyPopBranch(); | |
603 } else { | |
604 node.accept(this); | |
605 } | |
606 } | |
607 } | |
608 | |
609 visitList(List<js.Node> nodeList) { | |
610 if (nodeList != null) { | |
611 for (js.Node node in nodeList) { | |
612 visit(node); | |
613 } | |
614 } | |
615 } | |
616 | |
617 @override | |
618 visitFun(js.Fun node) { | |
619 bool activeBefore = active; | |
620 if (!active) { | |
621 active = node.sourceInformation != null; | |
622 } | |
623 visit(node.body); | |
624 leftToRightOffset = statementOffset = | |
625 getSyntaxOffset(node, kind: CodePositionKind.CLOSING); | |
626 Offset offset = getOffsetForNode(node, statementOffset); | |
627 notifyStep(node, offset, StepKind.FUN); | |
628 active = activeBefore; | |
629 } | |
630 | |
631 @override | |
632 visitBlock(js.Block node) { | |
633 for (js.Statement statement in node.statements) { | |
634 visit(statement); | |
635 } | |
636 } | |
637 | |
638 int getSyntaxOffset(js.Node node, | |
639 {CodePositionKind kind: CodePositionKind.START}) { | |
640 CodePosition codePosition = codePositions[node]; | |
641 if (codePosition != null) { | |
642 return codePosition.getPosition(kind); | |
643 } | |
644 return null; | |
645 } | |
646 | |
647 visitSubexpression(js.Node parent, | |
648 js.Expression child, | |
649 int codeOffset, | |
650 StepKind kind) { | |
651 var oldSteps = steps; | |
652 steps = []; | |
653 offsetPosition = codeOffset; | |
654 visit(child); | |
655 if (steps.isEmpty) { | |
656 notifyStep(parent, | |
657 getOffsetForNode(parent, offsetPosition), | |
658 kind); | |
659 } | |
660 steps = oldSteps; | |
661 } | |
662 | |
663 @override | |
664 visitExpressionStatement(js.ExpressionStatement node) { | |
665 statementOffset = getSyntaxOffset(node); | |
666 visitSubexpression( | |
667 node, node.expression, statementOffset, | |
668 StepKind.EXPRESSION_STATEMENT); | |
669 statementOffset = null; | |
670 leftToRightOffset = null; | |
671 } | |
672 | |
673 @override | |
674 visitEmptyStatement(js.EmptyStatement node) {} | |
675 | |
676 @override | |
677 visitCall(js.Call node) { | |
678 visit(node.target); | |
679 int oldPosition = offsetPosition; | |
680 offsetPosition = null; | |
681 visitList(node.arguments); | |
682 offsetPosition = oldPosition; | |
683 CallPosition callPosition = | |
684 CallPosition.getSemanticPositionForCall(node); | |
685 js.Node positionNode = callPosition.node; | |
686 int callOffset = getSyntaxOffset( | |
687 positionNode, kind: callPosition.codePositionKind); | |
688 if (offsetPosition == null) { | |
689 offsetPosition = callOffset; | |
690 } | |
691 Offset offset = getOffsetForNode(positionNode, offsetPosition); | |
692 notifyStep(node, offset, StepKind.CALL); | |
693 steps.add(node); | |
694 offsetPosition = null; | |
Siggi Cherem (dart-lang)
2016/01/21 18:18:34
I was expecting this to be restored to oldPosition
Johnni Winther
2016/01/22 15:12:38
The [offsetPosition] set be parent nodes should on
| |
695 } | |
696 | |
697 @override | |
698 visitNew(js.New node) { | |
699 visit(node.target); | |
700 visitList(node.arguments); | |
701 notifyStep( | |
702 node, getOffsetForNode(node, getSyntaxOffset(node)), StepKind.NEW); | |
Siggi Cherem (dart-lang)
2016/01/21 18:18:34
why for `new` don't you need the same logic as for
Johnni Winther
2016/01/22 15:12:38
Yes.
| |
703 steps.add(node); | |
704 offsetPosition = null; | |
705 } | |
706 | |
707 @override | |
708 visitAccess(js.PropertyAccess node) { | |
709 visit(node.receiver); | |
710 visit(node.selector); | |
711 } | |
712 | |
713 @override | |
714 visitVariableUse(js.VariableUse node) {} | |
715 | |
716 @override | |
717 visitLiteralBool(js.LiteralBool node) {} | |
718 | |
719 @override | |
720 visitLiteralString(js.LiteralString node) {} | |
721 | |
722 @override | |
723 visitLiteralNumber(js.LiteralNumber node) {} | |
724 | |
725 @override | |
726 visitLiteralNull(js.LiteralNull node) {} | |
727 | |
728 @override | |
729 visitName(js.Name node) {} | |
730 | |
731 @override | |
732 visitVariableDeclarationList(js.VariableDeclarationList node) { | |
733 visitList(node.declarations); | |
734 } | |
735 | |
736 @override | |
737 visitVariableDeclaration(js.VariableDeclaration node) {} | |
738 | |
739 @override | |
740 visitVariableInitialization(js.VariableInitialization node) { | |
741 visit(node.leftHandSide); | |
742 visit(node.value); | |
743 } | |
744 | |
745 @override | |
746 visitAssignment(js.Assignment node) { | |
747 visit(node.leftHandSide); | |
748 visit(node.value); | |
749 } | |
750 | |
751 @override | |
752 visitIf(js.If node) { | |
753 statementOffset = getSyntaxOffset(node); | |
754 visitSubexpression(node, node.condition, statementOffset, | |
755 StepKind.IF_CONDITION); | |
756 statementOffset = null; | |
757 visit(node.then, BranchKind.CONDITION, true); | |
758 visit(node.otherwise, BranchKind.CONDITION, false); | |
759 } | |
760 | |
761 @override | |
762 visitFor(js.For node) { | |
763 int offset = statementOffset = getSyntaxOffset(node); | |
764 statementOffset = offset; | |
765 leftToRightOffset = null; | |
766 if (node.init != null) { | |
767 visitSubexpression(node, node.init, getSyntaxOffset(node), | |
768 StepKind.FOR_INITIALIZER); | |
769 } | |
770 | |
771 if (node.condition != null) { | |
772 visitSubexpression(node, node.condition, getSyntaxOffset(node.condition), | |
773 StepKind.FOR_CONDITION); | |
774 } | |
775 | |
776 notifyPushBranch(BranchKind.LOOP); | |
777 visit(node.body); | |
778 | |
779 statementOffset = offset; | |
780 if (node.update != null) { | |
781 visitSubexpression(node, node.update, getSyntaxOffset(node.update), | |
782 StepKind.FOR_UPDATE); | |
783 } | |
784 | |
785 notifyPopBranch(); | |
786 } | |
787 | |
788 @override | |
789 visitWhile(js.While node) { | |
790 statementOffset = getSyntaxOffset(node); | |
791 if (node.condition != null) { | |
792 visitSubexpression(node, node.condition, getSyntaxOffset(node.condition), | |
793 StepKind.WHILE_CONDITION); | |
794 } | |
795 statementOffset = null; | |
796 leftToRightOffset = null; | |
797 | |
798 visit(node.body, BranchKind.LOOP); | |
799 } | |
800 | |
801 @override | |
802 visitDo(js.Do node) { | |
803 statementOffset = getSyntaxOffset(node); | |
804 visit(node.body); | |
805 if (node.condition != null) { | |
806 visitSubexpression(node, node.condition, getSyntaxOffset(node.condition), | |
807 StepKind.DO_CONDITION); | |
808 } | |
809 statementOffset = null; | |
810 leftToRightOffset = null; | |
811 } | |
812 | |
813 @override | |
814 visitBinary(js.Binary node) { | |
815 visit(node.left); | |
816 visit(node.right); | |
817 } | |
818 | |
819 @override | |
820 visitThis(js.This node) {} | |
821 | |
822 @override | |
823 visitReturn(js.Return node) { | |
824 statementOffset = getSyntaxOffset(node); | |
825 visit(node.value); | |
826 notifyStep( | |
827 node, getOffsetForNode(node, getSyntaxOffset(node)), StepKind.RETURN); | |
828 statementOffset = null; | |
829 leftToRightOffset = null; | |
830 } | |
831 | |
832 @override | |
833 visitThrow(js.Throw node) { | |
834 statementOffset = getSyntaxOffset(node); | |
835 visit(node.expression); | |
836 notifyStep( | |
837 node, getOffsetForNode(node, getSyntaxOffset(node)), StepKind.THROW); | |
838 statementOffset = null; | |
839 leftToRightOffset = null; | |
840 } | |
841 | |
842 @override | |
843 visitContinue(js.Continue node) { | |
844 statementOffset = getSyntaxOffset(node); | |
845 notifyStep( | |
846 node, getOffsetForNode(node, getSyntaxOffset(node)), StepKind.CONTINUE); | |
847 statementOffset = null; | |
848 leftToRightOffset = null; | |
849 } | |
850 | |
851 @override | |
852 visitBreak(js.Break node) { | |
853 statementOffset = getSyntaxOffset(node); | |
854 notifyStep( | |
855 node, getOffsetForNode(node, getSyntaxOffset(node)), StepKind.BREAK); | |
856 statementOffset = null; | |
857 leftToRightOffset = null; | |
858 } | |
859 | |
860 @override | |
861 visitTry(js.Try node) { | |
862 visit(node.body); | |
863 visit(node.catchPart, BranchKind.CATCH); | |
864 visit(node.finallyPart, BranchKind.FINALLY); | |
865 } | |
866 | |
867 @override | |
868 visitCatch(js.Catch node) { | |
869 visit(node.body); | |
870 } | |
871 | |
872 @override | |
873 visitConditional(js.Conditional node) { | |
874 visit(node.condition); | |
875 visit(node.then, BranchKind.CONDITION, true); | |
876 visit(node.otherwise, BranchKind.CONDITION, false); | |
877 } | |
878 | |
879 @override | |
880 visitPrefix(js.Prefix node) { | |
881 visit(node.argument); | |
882 } | |
883 | |
884 @override | |
885 visitPostfix(js.Postfix node) { | |
886 visit(node.argument); | |
887 } | |
888 | |
889 @override | |
890 visitObjectInitializer(js.ObjectInitializer node) { | |
891 visitList(node.properties); | |
892 } | |
893 | |
894 @override | |
895 visitProperty(js.Property node) { | |
896 visit(node.name); | |
897 visit(node.value); | |
898 } | |
899 | |
900 @override | |
901 visitRegExpLiteral(js.RegExpLiteral node) {} | |
902 | |
903 @override | |
904 visitSwitch(js.Switch node) { | |
905 statementOffset = getSyntaxOffset(node); | |
906 visitSubexpression(node, node.key, getSyntaxOffset(node), | |
907 StepKind.SWITCH_EXPRESSION); | |
908 statementOffset = null; | |
909 leftToRightOffset = null; | |
910 for (int i = 0; i < node.cases.length; i++) { | |
911 visit(node.cases[i], BranchKind.CASE, i); | |
912 } | |
913 } | |
914 | |
915 @override | |
916 visitCase(js.Case node) { | |
917 visit(node.expression); | |
918 visit(node.body); | |
919 } | |
920 | |
921 @override | |
922 visitDefault(js.Default node) { | |
923 visit(node.body); | |
924 } | |
925 | |
926 @override | |
927 visitArrayInitializer(js.ArrayInitializer node) { | |
928 visitList(node.elements); | |
929 } | |
930 | |
931 @override | |
932 visitArrayHole(js.ArrayHole node) {} | |
933 | |
934 @override | |
935 visitLabeledStatement(js.LabeledStatement node) { | |
936 statementOffset = getSyntaxOffset(node); | |
937 visit(node.body); | |
938 statementOffset = null; | |
939 } | |
940 | |
941 Offset getOffsetForNode(js.Node node, int codeOffset) { | |
942 if (codeOffset == null) { | |
943 CodePosition codePosition = codePositions[node]; | |
944 if (codePosition != null) { | |
945 codeOffset = codePosition.startPosition; | |
946 } | |
947 } | |
948 if (leftToRightOffset != null && leftToRightOffset < codeOffset) { | |
949 leftToRightOffset = codeOffset; | |
950 } | |
951 if (leftToRightOffset == null) { | |
952 leftToRightOffset = statementOffset; | |
953 } | |
954 return new Offset(statementOffset, leftToRightOffset, codeOffset); | |
955 } | |
956 } | |
957 | |
958 | |
959 class Coverage { | |
960 Set<js.Node> _nodesWithInfo = new Set<js.Node>(); | |
961 int _nodesWithInfoCount = 0; | |
962 Set<js.Node> _nodesWithoutInfo = new Set<js.Node>(); | |
963 int _nodesWithoutInfoCount = 0; | |
964 Map<Type, int> _nodesWithoutInfoCountByType = <Type, int>{}; | |
965 Set<js.Node> _nodesWithoutOffset = new Set<js.Node>(); | |
966 int _nodesWithoutOffsetCount = 0; | |
967 | |
968 void registerNodeWithInfo(js.Node node) { | |
969 _nodesWithInfo.add(node); | |
970 } | |
971 | |
972 void registerNodeWithoutInfo(js.Node node) { | |
973 _nodesWithoutInfo.add(node); | |
974 } | |
975 | |
976 void registerNodesWithoutOffset(js.Node node) { | |
977 _nodesWithoutOffset.add(node); | |
978 } | |
979 | |
980 void collapse() { | |
981 _nodesWithInfoCount += _nodesWithInfo.length; | |
982 _nodesWithInfo.clear(); | |
983 _nodesWithoutOffsetCount += _nodesWithoutOffset.length; | |
984 _nodesWithoutOffset.clear(); | |
985 | |
986 _nodesWithoutInfoCount += _nodesWithoutInfo.length; | |
987 for (js.Node node in _nodesWithoutInfo) { | |
988 if (node is js.ExpressionStatement) { | |
989 _nodesWithoutInfoCountByType.putIfAbsent( | |
990 node.expression.runtimeType, () => 0); | |
991 _nodesWithoutInfoCountByType[node.expression.runtimeType]++; | |
992 } else { | |
993 _nodesWithoutInfoCountByType.putIfAbsent( | |
994 node.runtimeType, () => 0); | |
995 _nodesWithoutInfoCountByType[node.runtimeType]++; | |
996 } | |
997 } | |
998 _nodesWithoutInfo.clear(); | |
999 } | |
1000 | |
1001 String getCoverageReport() { | |
1002 collapse(); | |
1003 StringBuffer sb = new StringBuffer(); | |
1004 int total = _nodesWithInfoCount + _nodesWithoutInfoCount; | |
1005 if (total > 0) { | |
1006 sb.write(_nodesWithoutInfoCount); | |
1007 sb.write('/'); | |
1008 sb.write(total); | |
1009 sb.write(' ('); | |
1010 sb.write((100.0 * _nodesWithInfoCount / total).toStringAsFixed(2)); | |
1011 sb.write('%) nodes with info.'); | |
1012 } else { | |
1013 sb.write('No nodes.'); | |
1014 } | |
1015 if (_nodesWithoutOffsetCount > 0) { | |
1016 sb.write(' '); | |
1017 sb.write(_nodesWithoutOffsetCount); | |
1018 sb.write(' node'); | |
1019 if (_nodesWithoutOffsetCount > 1) { | |
1020 sb.write('s'); | |
1021 } | |
1022 sb.write(' without offset.'); | |
1023 } | |
1024 if (_nodesWithoutInfoCount > 0) { | |
1025 sb.write('\nNodes without info ('); | |
1026 sb.write(_nodesWithoutInfoCount); | |
1027 sb.write(') by runtime type:'); | |
1028 _nodesWithoutInfoCountByType.forEach((Type type, int count) { | |
1029 sb.write('\n '); | |
1030 sb.write(count); | |
1031 sb.write(' '); | |
1032 sb.write(type); | |
1033 sb.write(' node'); | |
1034 if (count > 1) { | |
1035 sb.write('s'); | |
1036 } | |
1037 }); | |
1038 sb.write('\n'); | |
1039 } | |
1040 return sb.toString(); | |
1041 } | |
1042 | |
1043 String toString() => getCoverageReport(); | |
1044 } | |
1045 | |
1046 /// [TraceListener] that registers [onStep] callbacks with [coverage]. | |
1047 class CoverageListener extends TraceListener { | |
1048 final Coverage coverage; | |
1049 | |
1050 CoverageListener(this.coverage); | |
1051 | |
1052 @override | |
1053 void onStep(js.Node node, Offset offset, StepKind kind) { | |
1054 SourceInformation sourceInformation = node.sourceInformation; | |
1055 if (sourceInformation != null) { | |
1056 coverage.registerNodeWithInfo(node); | |
1057 } else { | |
1058 coverage.registerNodeWithoutInfo(node); | |
1059 } | |
1060 } | |
1061 | |
1062 @override | |
1063 void onEnd(js.Node node) { | |
1064 coverage.collapse(); | |
1065 } | |
1066 } | |
1067 | |
1068 /// [CodePositionMap] that registers calls with [Coverage]. | |
1069 class CodePositionCoverage implements CodePositionMap { | |
1070 final CodePositionMap codePositions; | |
1071 final Coverage coverage; | |
1072 | |
1073 CodePositionCoverage(this.codePositions, this.coverage); | |
1074 | |
1075 @override | |
1076 CodePosition operator [](js.Node node) { | |
1077 CodePosition codePosition = codePositions[node]; | |
1078 if (codePosition == null) { | |
1079 coverage.registerNodesWithoutOffset(node); | |
1080 } | |
1081 return codePosition; | |
1082 } | |
1083 } | |
OLD | NEW |