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 AstElement, FieldElement, LocalElement; |
12 AstElement, | |
13 FieldElement, | |
14 LocalElement; | |
15 import '../js/js.dart' as js; | 12 import '../js/js.dart' as js; |
16 import '../js/js_source_mapping.dart'; | 13 import '../js/js_source_mapping.dart'; |
17 import '../js/js_debug.dart'; | 14 import '../js/js_debug.dart'; |
18 import '../tree/tree.dart' show | 15 import '../tree/tree.dart' show FunctionExpression, Node, Send; |
19 FunctionExpression, | |
20 Node, | |
21 Send; | |
22 | 16 |
23 import 'code_output.dart' show | 17 import 'code_output.dart' show CodeBuffer; |
24 CodeBuffer; | |
25 import 'source_file.dart'; | 18 import 'source_file.dart'; |
26 import 'source_information.dart'; | 19 import 'source_information.dart'; |
27 | 20 |
28 /// [SourceInformation] that consists of an offset position into the source | 21 /// [SourceInformation] that consists of an offset position into the source |
29 /// code. | 22 /// code. |
30 class PositionSourceInformation extends SourceInformation { | 23 class PositionSourceInformation extends SourceInformation { |
31 @override | 24 @override |
32 final SourceLocation startPosition; | 25 final SourceLocation startPosition; |
33 | 26 |
34 @override | 27 @override |
35 final SourceLocation closingPosition; | 28 final SourceLocation closingPosition; |
36 | 29 |
37 PositionSourceInformation(this.startPosition, | 30 PositionSourceInformation(this.startPosition, [this.closingPosition]); |
38 [this.closingPosition]); | |
39 | 31 |
40 @override | 32 @override |
41 List<SourceLocation> get sourceLocations { | 33 List<SourceLocation> get sourceLocations { |
42 List<SourceLocation> list = <SourceLocation>[]; | 34 List<SourceLocation> list = <SourceLocation>[]; |
43 if (startPosition != null) { | 35 if (startPosition != null) { |
44 list.add(startPosition); | 36 list.add(startPosition); |
45 } | 37 } |
46 if (closingPosition != null) { | 38 if (closingPosition != null) { |
47 list.add(closingPosition); | 39 list.add(closingPosition); |
48 } | 40 } |
49 return list; | 41 return list; |
50 } | 42 } |
51 | 43 |
52 @override | 44 @override |
53 SourceSpan get sourceSpan { | 45 SourceSpan get sourceSpan { |
54 SourceLocation location = | 46 SourceLocation location = |
55 startPosition != null ? startPosition : closingPosition; | 47 startPosition != null ? startPosition : closingPosition; |
56 Uri uri = location.sourceUri; | 48 Uri uri = location.sourceUri; |
57 int offset = location.offset; | 49 int offset = location.offset; |
58 return new SourceSpan(uri, offset, offset); | 50 return new SourceSpan(uri, offset, offset); |
59 } | 51 } |
60 | 52 |
61 int get hashCode { | 53 int get hashCode { |
62 return 0x7FFFFFFF & | 54 return 0x7FFFFFFF & |
63 (startPosition.hashCode * 17 + closingPosition.hashCode * 19); | 55 (startPosition.hashCode * 17 + closingPosition.hashCode * 19); |
64 } | 56 } |
65 | 57 |
66 bool operator ==(other) { | 58 bool operator ==(other) { |
67 if (identical(this, other)) return true; | 59 if (identical(this, other)) return true; |
68 if (other is! PositionSourceInformation) return false; | 60 if (other is! PositionSourceInformation) return false; |
69 return startPosition == other.startPosition && | 61 return startPosition == other.startPosition && |
70 closingPosition == other.closingPosition; | 62 closingPosition == other.closingPosition; |
71 } | 63 } |
72 | 64 |
73 /// Create a textual representation of the source information using [uriText] | 65 /// Create a textual representation of the source information using [uriText] |
74 /// as the Uri representation. | 66 /// as the Uri representation. |
75 String _computeText(String uriText) { | 67 String _computeText(String uriText) { |
76 StringBuffer sb = new StringBuffer(); | 68 StringBuffer sb = new StringBuffer(); |
77 sb.write('$uriText:'); | 69 sb.write('$uriText:'); |
78 // Use 1-based line/column info to match usual dart tool output. | 70 // Use 1-based line/column info to match usual dart tool output. |
79 if (startPosition != null) { | 71 if (startPosition != null) { |
80 sb.write('[${startPosition.line + 1},' | 72 sb.write('[${startPosition.line + 1},' |
81 '${startPosition.column + 1}]'); | 73 '${startPosition.column + 1}]'); |
82 } | 74 } |
83 if (closingPosition != null) { | 75 if (closingPosition != null) { |
84 sb.write('-[${closingPosition.line + 1},' | 76 sb.write('-[${closingPosition.line + 1},' |
85 '${closingPosition.column + 1}]'); | 77 '${closingPosition.column + 1}]'); |
86 } | 78 } |
87 return sb.toString(); | 79 return sb.toString(); |
88 } | 80 } |
89 | 81 |
90 String get shortText { | 82 String get shortText { |
91 if (startPosition != null) { | 83 if (startPosition != null) { |
92 return _computeText(startPosition.sourceUri.pathSegments.last); | 84 return _computeText(startPosition.sourceUri.pathSegments.last); |
93 } else { | 85 } else { |
94 return _computeText(closingPosition.sourceUri.pathSegments.last); | 86 return _computeText(closingPosition.sourceUri.pathSegments.last); |
95 } | 87 } |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
151 final String name; | 143 final String name; |
152 final AstElement element; | 144 final AstElement element; |
153 | 145 |
154 PositionSourceInformationBuilder(AstElement element) | 146 PositionSourceInformationBuilder(AstElement element) |
155 : this.element = element, | 147 : this.element = element, |
156 sourceFile = element.implementation.compilationUnit.script.file, | 148 sourceFile = element.implementation.compilationUnit.script.file, |
157 name = computeElementNameForSourceMaps(element); | 149 name = computeElementNameForSourceMaps(element); |
158 | 150 |
159 SourceInformation buildDeclaration(AstElement element) { | 151 SourceInformation buildDeclaration(AstElement element) { |
160 if (element.isSynthesized) { | 152 if (element.isSynthesized) { |
161 return new PositionSourceInformation( | 153 return new PositionSourceInformation(new OffsetSourceLocation( |
162 new OffsetSourceLocation( | 154 sourceFile, element.position.charOffset, name)); |
163 sourceFile, element.position.charOffset, name)); | |
164 } else { | 155 } else { |
165 return new PositionSourceInformation( | 156 return new PositionSourceInformation( |
166 new OffsetSourceLocation(sourceFile, | 157 new OffsetSourceLocation(sourceFile, |
167 element.resolvedAst.node.getBeginToken().charOffset, name), | 158 element.resolvedAst.node.getBeginToken().charOffset, name), |
168 new OffsetSourceLocation(sourceFile, | 159 new OffsetSourceLocation(sourceFile, |
169 element.resolvedAst.node.getEndToken().charOffset, name)); | 160 element.resolvedAst.node.getEndToken().charOffset, name)); |
170 } | 161 } |
171 } | 162 } |
172 | 163 |
173 /// Builds a source information object pointing the start position of [node]. | 164 /// Builds a source information object pointing the start position of [node]. |
174 SourceInformation buildBegin(Node node) { | 165 SourceInformation buildBegin(Node node) { |
175 return new PositionSourceInformation(new OffsetSourceLocation( | 166 return new PositionSourceInformation(new OffsetSourceLocation( |
176 sourceFile, node.getBeginToken().charOffset, name)); | 167 sourceFile, node.getBeginToken().charOffset, name)); |
177 } | 168 } |
178 | 169 |
179 @override | 170 @override |
180 SourceInformation buildGeneric(Node node) => buildBegin(node); | 171 SourceInformation buildGeneric(Node node) => buildBegin(node); |
181 | 172 |
182 @override | 173 @override |
183 SourceInformation buildCreate(Node node) => buildBegin(node); | 174 SourceInformation buildCreate(Node node) => buildBegin(node); |
184 | 175 |
185 @override | 176 @override |
186 SourceInformation buildReturn(Node node) => buildBegin(node); | 177 SourceInformation buildReturn(Node node) => buildBegin(node); |
187 | 178 |
188 @override | 179 @override |
189 SourceInformation buildImplicitReturn(AstElement element) { | 180 SourceInformation buildImplicitReturn(AstElement element) { |
190 if (element.isSynthesized) { | 181 if (element.isSynthesized) { |
191 return new PositionSourceInformation( | 182 return new PositionSourceInformation(new OffsetSourceLocation( |
192 new OffsetSourceLocation( | 183 sourceFile, element.position.charOffset, name)); |
193 sourceFile, element.position.charOffset, name)); | |
194 } else { | 184 } else { |
195 return new PositionSourceInformation( | 185 return new PositionSourceInformation(new OffsetSourceLocation( |
196 new OffsetSourceLocation(sourceFile, | 186 sourceFile, element.resolvedAst.node.getEndToken().charOffset, name)); |
197 element.resolvedAst.node.getEndToken().charOffset, name)); | |
198 } | 187 } |
199 } | 188 } |
200 | |
201 | 189 |
202 @override | 190 @override |
203 SourceInformation buildLoop(Node node) => buildBegin(node); | 191 SourceInformation buildLoop(Node node) => buildBegin(node); |
204 | 192 |
205 @override | 193 @override |
206 SourceInformation buildGet(Node node) { | 194 SourceInformation buildGet(Node node) { |
207 Node left = node; | 195 Node left = node; |
208 Node right = node; | 196 Node right = node; |
209 Send send = node.asSend(); | 197 Send send = node.asSend(); |
210 if (send != null) { | 198 if (send != null) { |
(...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
326 return startPosition; | 314 return startPosition; |
327 case CodePositionKind.END: | 315 case CodePositionKind.END: |
328 return endPosition; | 316 return endPosition; |
329 case CodePositionKind.CLOSING: | 317 case CodePositionKind.CLOSING: |
330 return closingPosition; | 318 return closingPosition; |
331 } | 319 } |
332 } | 320 } |
333 | 321 |
334 String toString() { | 322 String toString() { |
335 return 'CodePosition(start=$startPosition,' | 323 return 'CodePosition(start=$startPosition,' |
336 'end=$endPosition,closing=$closingPosition)'; | 324 'end=$endPosition,closing=$closingPosition)'; |
337 } | 325 } |
338 } | 326 } |
339 | 327 |
340 /// A map from a [js.Node] to its [CodePosition]. | 328 /// A map from a [js.Node] to its [CodePosition]. |
341 abstract class CodePositionMap { | 329 abstract class CodePositionMap { |
342 CodePosition operator [](js.Node node); | 330 CodePosition operator [](js.Node node); |
343 } | 331 } |
344 | 332 |
345 /// Registry for mapping [js.Node]s to their [CodePosition]. | 333 /// Registry for mapping [js.Node]s to their [CodePosition]. |
346 class CodePositionRecorder implements CodePositionMap { | 334 class CodePositionRecorder implements CodePositionMap { |
347 Map<js.Node, CodePosition> _codePositionMap = | 335 Map<js.Node, CodePosition> _codePositionMap = |
348 new Map<js.Node, CodePosition>.identity(); | 336 new Map<js.Node, CodePosition>.identity(); |
349 | 337 |
350 void registerPositions(js.Node node, | 338 void registerPositions( |
351 int startPosition, | 339 js.Node node, int startPosition, int endPosition, int closingPosition) { |
352 int endPosition, | 340 registerCodePosition( |
353 int closingPosition) { | 341 node, new CodePosition(startPosition, endPosition, closingPosition)); |
354 registerCodePosition(node, | |
355 new CodePosition(startPosition, endPosition, closingPosition)); | |
356 } | 342 } |
357 | 343 |
358 void registerCodePosition(js.Node node, CodePosition codePosition) { | 344 void registerCodePosition(js.Node node, CodePosition codePosition) { |
359 _codePositionMap[node] = codePosition; | 345 _codePositionMap[node] = codePosition; |
360 } | 346 } |
361 | 347 |
362 CodePosition operator [](js.Node node) => _codePositionMap[node]; | 348 CodePosition operator [](js.Node node) => _codePositionMap[node]; |
363 } | 349 } |
364 | 350 |
365 /// Enum values for the part of a Dart node used for the source location offset. | 351 /// Enum values for the part of a Dart node used for the source location offset. |
(...skipping 23 matching lines...) Expand all Loading... |
389 /// For function expressions the inner position is the closing brace or the | 375 /// For function expressions the inner position is the closing brace or the |
390 /// arrow: | 376 /// arrow: |
391 /// | 377 /// |
392 /// foo() => () {} | 378 /// foo() => () {} |
393 /// ^ // the inner position of the 'foo' function | 379 /// ^ // the inner position of the 'foo' function |
394 /// ^ // the inner position of the closure | 380 /// ^ // the inner position of the closure |
395 /// | 381 /// |
396 INNER, | 382 INNER, |
397 } | 383 } |
398 | 384 |
399 SourceLocation getSourceLocation( | 385 SourceLocation getSourceLocation(SourceInformation sourceInformation, |
400 SourceInformation sourceInformation, | |
401 [SourcePositionKind sourcePositionKind = SourcePositionKind.START]) { | 386 [SourcePositionKind sourcePositionKind = SourcePositionKind.START]) { |
402 if (sourceInformation == null) return null; | 387 if (sourceInformation == null) return null; |
403 switch (sourcePositionKind) { | 388 switch (sourcePositionKind) { |
404 case SourcePositionKind.START: | 389 case SourcePositionKind.START: |
405 return sourceInformation.startPosition; | 390 return sourceInformation.startPosition; |
406 case SourcePositionKind.INNER: | 391 case SourcePositionKind.INNER: |
407 return sourceInformation.closingPosition; | 392 return sourceInformation.closingPosition; |
408 } | 393 } |
409 } | 394 } |
410 | 395 |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
442 END, | 427 END, |
443 } | 428 } |
444 | 429 |
445 /// Processor that associates [SourceLocation]s from [SourceInformation] on | 430 /// Processor that associates [SourceLocation]s from [SourceInformation] on |
446 /// [js.Node]s with the target offsets in a [SourceMapper]. | 431 /// [js.Node]s with the target offsets in a [SourceMapper]. |
447 class PositionSourceInformationProcessor implements SourceInformationProcessor { | 432 class PositionSourceInformationProcessor implements SourceInformationProcessor { |
448 final CodePositionRecorder codePositionRecorder = new CodePositionRecorder(); | 433 final CodePositionRecorder codePositionRecorder = new CodePositionRecorder(); |
449 CodePositionMap codePositionMap; | 434 CodePositionMap codePositionMap; |
450 List<TraceListener> traceListeners; | 435 List<TraceListener> traceListeners; |
451 | 436 |
452 PositionSourceInformationProcessor( | 437 PositionSourceInformationProcessor(SourceMapper sourceMapper, |
453 SourceMapper sourceMapper, | |
454 [Coverage coverage]) { | 438 [Coverage coverage]) { |
455 codePositionMap = coverage != null | 439 codePositionMap = coverage != null |
456 ? new CodePositionCoverage(codePositionRecorder, coverage) | 440 ? new CodePositionCoverage(codePositionRecorder, coverage) |
457 : codePositionRecorder; | 441 : codePositionRecorder; |
458 traceListeners = [new PositionTraceListener(sourceMapper)]; | 442 traceListeners = [new PositionTraceListener(sourceMapper)]; |
459 if (coverage != null) { | 443 if (coverage != null) { |
460 traceListeners.add(new CoverageListener(coverage)); | 444 traceListeners.add(new CoverageListener(coverage)); |
461 } | 445 } |
462 } | 446 } |
463 | 447 |
464 void process(js.Node node, CodeBuffer codeBuffer) { | 448 void process(js.Node node, CodeBuffer codeBuffer) { |
465 new JavaScriptTracer(codePositionMap, traceListeners).apply(node); | 449 new JavaScriptTracer(codePositionMap, traceListeners).apply(node); |
466 } | 450 } |
467 | 451 |
468 @override | 452 @override |
469 void onPositions(js.Node node, | 453 void onPositions( |
470 int startPosition, | 454 js.Node node, int startPosition, int endPosition, int closingPosition) { |
471 int endPosition, | |
472 int closingPosition) { | |
473 codePositionRecorder.registerPositions( | 455 codePositionRecorder.registerPositions( |
474 node, startPosition, endPosition, closingPosition); | 456 node, startPosition, endPosition, closingPosition); |
475 } | 457 } |
476 } | 458 } |
477 | 459 |
478 /// Visitor that computes [SourceInformation] for a [js.Node] using information | 460 /// Visitor that computes [SourceInformation] for a [js.Node] using information |
479 /// attached to the node itself or alternatively from child nodes. | 461 /// attached to the node itself or alternatively from child nodes. |
480 class NodeSourceInformation extends js.BaseVisitor<SourceInformation> { | 462 class NodeSourceInformation extends js.BaseVisitor<SourceInformation> { |
481 const NodeSourceInformation(); | 463 const NodeSourceInformation(); |
482 | 464 |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
517 return visit(node.value); | 499 return visit(node.value); |
518 } | 500 } |
519 | 501 |
520 @override | 502 @override |
521 SourceInformation visitAssignment(js.Assignment node) { | 503 SourceInformation visitAssignment(js.Assignment node) { |
522 if (node.sourceInformation != null) { | 504 if (node.sourceInformation != null) { |
523 return node.sourceInformation; | 505 return node.sourceInformation; |
524 } | 506 } |
525 return visit(node.value); | 507 return visit(node.value); |
526 } | 508 } |
527 | |
528 } | 509 } |
529 | 510 |
530 /// Mixin that add support for computing [SourceInformation] for a [js.Node]. | 511 /// Mixin that add support for computing [SourceInformation] for a [js.Node]. |
531 class NodeToSourceInformationMixin { | 512 class NodeToSourceInformationMixin { |
532 SourceInformation computeSourceInformation(js.Node node) { | 513 SourceInformation computeSourceInformation(js.Node node) { |
533 return const NodeSourceInformation().visit(node); | 514 return const NodeSourceInformation().visit(node); |
534 } | 515 } |
535 } | 516 } |
536 | 517 |
537 /// [TraceListener] that register [SourceLocation]s with a [SourceMapper]. | 518 /// [TraceListener] that register [SourceLocation]s with a [SourceMapper]. |
538 class PositionTraceListener extends TraceListener with | 519 class PositionTraceListener extends TraceListener |
539 NodeToSourceInformationMixin { | 520 with NodeToSourceInformationMixin { |
540 final SourceMapper sourceMapper; | 521 final SourceMapper sourceMapper; |
541 | 522 |
542 PositionTraceListener(this.sourceMapper); | 523 PositionTraceListener(this.sourceMapper); |
543 | 524 |
544 @override | 525 @override |
545 void onStep(js.Node node, Offset offset, StepKind kind) { | 526 void onStep(js.Node node, Offset offset, StepKind kind) { |
546 SourceInformation sourceInformation = computeSourceInformation(node); | 527 SourceInformation sourceInformation = computeSourceInformation(node); |
547 if (sourceInformation == null) return; | 528 if (sourceInformation == null) return; |
548 int codeLocation = offset.subexpressionOffset; | 529 int codeLocation = offset.subexpressionOffset; |
549 if (codeLocation == null) return; | 530 if (codeLocation == null) return; |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
610 pureAccess = true; | 591 pureAccess = true; |
611 break; | 592 break; |
612 } else { | 593 } else { |
613 target = targetAccess.receiver; | 594 target = targetAccess.receiver; |
614 } | 595 } |
615 } | 596 } |
616 if (pureAccess) { | 597 if (pureAccess) { |
617 // a.m() this.m() a.b.c.d.m() | 598 // a.m() this.m() a.b.c.d.m() |
618 // ^ ^ ^ | 599 // ^ ^ ^ |
619 return new CallPosition( | 600 return new CallPosition( |
620 node, | 601 node, CodePositionKind.START, SourcePositionKind.START); |
621 CodePositionKind.START, | |
622 SourcePositionKind.START); | |
623 } else { | 602 } else { |
624 // *.m() *.a.b.c.d.m() | 603 // *.m() *.a.b.c.d.m() |
625 // ^ ^ | 604 // ^ ^ |
626 return new CallPosition( | 605 return new CallPosition( |
627 access.selector, | 606 access.selector, CodePositionKind.START, SourcePositionKind.INNER); |
628 CodePositionKind.START, | |
629 SourcePositionKind.INNER); | |
630 } | 607 } |
631 } else if (node.target is js.VariableUse) { | 608 } else if (node.target is js.VariableUse) { |
632 // m() | 609 // m() |
633 // ^ | 610 // ^ |
634 return new CallPosition( | 611 return new CallPosition( |
635 node, | 612 node, CodePositionKind.START, SourcePositionKind.START); |
636 CodePositionKind.START, | |
637 SourcePositionKind.START); | |
638 } else if (node.target is js.Fun || node.target is js.New) { | 613 } else if (node.target is js.Fun || node.target is js.New) { |
639 // function(){}() new Function("...")() | 614 // function(){}() new Function("...")() |
640 // ^ ^ | 615 // ^ ^ |
641 return new CallPosition( | 616 return new CallPosition( |
642 node.target, | 617 node.target, CodePositionKind.END, SourcePositionKind.INNER); |
643 CodePositionKind.END, | |
644 SourcePositionKind.INNER); | |
645 } else if (node.target is js.Binary || node.target is js.Call) { | 618 } else if (node.target is js.Binary || node.target is js.Call) { |
646 // (0,a)() m()() | 619 // (0,a)() m()() |
647 // ^ ^ | 620 // ^ ^ |
648 return new CallPosition( | 621 return new CallPosition( |
649 node.target, | 622 node.target, CodePositionKind.END, SourcePositionKind.INNER); |
650 CodePositionKind.END, | |
651 SourcePositionKind.INNER); | |
652 } else { | 623 } else { |
653 assert(invariant(NO_LOCATION_SPANNABLE, false, | 624 assert(invariant(NO_LOCATION_SPANNABLE, false, |
654 message: "Unexpected property access ${nodeToString(node)}:\n" | 625 message: "Unexpected property access ${nodeToString(node)}:\n" |
655 "${DebugPrinter.prettyPrint(node)}")); | 626 "${DebugPrinter.prettyPrint(node)}")); |
656 // Don't know.... | 627 // Don't know.... |
657 return new CallPosition( | 628 return new CallPosition( |
658 node, | 629 node, CodePositionKind.START, SourcePositionKind.START); |
659 CodePositionKind.START, | |
660 SourcePositionKind.START); | |
661 } | 630 } |
662 } | 631 } |
663 } | 632 } |
664 | 633 |
665 class Offset { | 634 class Offset { |
666 /// The offset of the enclosing statement relative to the beginning of the | 635 /// The offset of the enclosing statement relative to the beginning of the |
667 /// file. | 636 /// file. |
668 /// | 637 /// |
669 /// For instance: | 638 /// For instance: |
670 /// | 639 /// |
(...skipping 30 matching lines...) Expand all Loading... |
701 /// foo().bar(baz()); | 670 /// foo().bar(baz()); |
702 /// ^ // the left-to-right offset of the `foo()` call | 671 /// ^ // the left-to-right offset of the `foo()` call |
703 /// ^ // the left-to-right offset of the `*.bar()` call | 672 /// ^ // the left-to-right offset of the `*.bar()` call |
704 /// ^ // the left-to-right offset of the `baz()` call | 673 /// ^ // the left-to-right offset of the `baz()` call |
705 /// | 674 /// |
706 /// Here, `baz()` is executed before `foo()` so we need to use 'f' as its best | 675 /// Here, `baz()` is executed before `foo()` so we need to use 'f' as its best |
707 /// position under the restriction. | 676 /// position under the restriction. |
708 /// | 677 /// |
709 final int leftToRightOffset; | 678 final int leftToRightOffset; |
710 | 679 |
711 Offset(this.statementOffset, this.leftToRightOffset, this.subexpressionOffset)
; | 680 Offset( |
| 681 this.statementOffset, this.leftToRightOffset, this.subexpressionOffset); |
712 | 682 |
713 String toString() { | 683 String toString() { |
714 return 'Offset[statementOffset=$statementOffset,' | 684 return 'Offset[statementOffset=$statementOffset,' |
715 'leftToRightOffset=$leftToRightOffset,' | 685 'leftToRightOffset=$leftToRightOffset,' |
716 'subexpressionOffset=$subexpressionOffset]'; | 686 'subexpressionOffset=$subexpressionOffset]'; |
717 } | 687 } |
718 } | 688 } |
719 | 689 |
720 enum BranchKind { | 690 enum BranchKind { CONDITION, LOOP, CATCH, FINALLY, CASE, } |
721 CONDITION, | |
722 LOOP, | |
723 CATCH, | |
724 FINALLY, | |
725 CASE, | |
726 } | |
727 | 691 |
728 enum StepKind { | 692 enum StepKind { |
729 FUN_ENTRY, | 693 FUN_ENTRY, |
730 FUN_EXIT, | 694 FUN_EXIT, |
731 CALL, | 695 CALL, |
732 NEW, | 696 NEW, |
733 RETURN, | 697 RETURN, |
734 BREAK, | 698 BREAK, |
735 CONTINUE, | 699 CONTINUE, |
736 THROW, | 700 THROW, |
(...skipping 23 matching lines...) Expand all Loading... |
760 /// Called when the current branch ends. | 724 /// Called when the current branch ends. |
761 void popBranch() {} | 725 void popBranch() {} |
762 | 726 |
763 /// Called when [node] defines a step of the given [kind] at the given | 727 /// Called when [node] defines a step of the given [kind] at the given |
764 /// [offset] when the generated JavaScript code. | 728 /// [offset] when the generated JavaScript code. |
765 void onStep(js.Node node, Offset offset, StepKind kind) {} | 729 void onStep(js.Node node, Offset offset, StepKind kind) {} |
766 } | 730 } |
767 | 731 |
768 /// Visitor that computes the [js.Node]s the are part of the JavaScript | 732 /// Visitor that computes the [js.Node]s the are part of the JavaScript |
769 /// steppable execution and thus needs source mapping locations. | 733 /// steppable execution and thus needs source mapping locations. |
770 class JavaScriptTracer extends js.BaseVisitor { | 734 class JavaScriptTracer extends js.BaseVisitor { |
771 final CodePositionMap codePositions; | 735 final CodePositionMap codePositions; |
772 final List<TraceListener> listeners; | 736 final List<TraceListener> listeners; |
773 | 737 |
774 /// The steps added by subexpressions. | 738 /// The steps added by subexpressions. |
775 List steps = []; | 739 List steps = []; |
776 | 740 |
777 /// The offset of the current statement. | 741 /// The offset of the current statement. |
778 int statementOffset; | 742 int statementOffset; |
779 | 743 |
780 /// The current offset in left-to-right progression. | 744 /// The current offset in left-to-right progression. |
781 int leftToRightOffset; | 745 int leftToRightOffset; |
782 | 746 |
783 /// The offset of the surrounding statement, used for the first subexpression. | 747 /// The offset of the surrounding statement, used for the first subexpression. |
784 int offsetPosition; | 748 int offsetPosition; |
785 | 749 |
786 bool active; | 750 bool active; |
787 | 751 |
788 JavaScriptTracer(this.codePositions, | 752 JavaScriptTracer(this.codePositions, this.listeners, {this.active: false}); |
789 this.listeners, | |
790 {this.active: false}); | |
791 | 753 |
792 void notifyStart(js.Node node) { | 754 void notifyStart(js.Node node) { |
793 listeners.forEach((listener) => listener.onStart(node)); | 755 listeners.forEach((listener) => listener.onStart(node)); |
794 } | 756 } |
795 | 757 |
796 void notifyEnd(js.Node node) { | 758 void notifyEnd(js.Node node) { |
797 listeners.forEach((listener) => listener.onEnd(node)); | 759 listeners.forEach((listener) => listener.onEnd(node)); |
798 } | 760 } |
799 | 761 |
800 void notifyPushBranch(BranchKind kind, [value]) { | 762 void notifyPushBranch(BranchKind kind, [value]) { |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
845 } | 807 } |
846 } | 808 } |
847 } | 809 } |
848 | 810 |
849 @override | 811 @override |
850 visitFun(js.Fun node) { | 812 visitFun(js.Fun node) { |
851 bool activeBefore = active; | 813 bool activeBefore = active; |
852 if (!active) { | 814 if (!active) { |
853 active = node.sourceInformation != null; | 815 active = node.sourceInformation != null; |
854 } | 816 } |
855 leftToRightOffset = statementOffset = | 817 leftToRightOffset = |
856 getSyntaxOffset(node, kind: CodePositionKind.START); | 818 statementOffset = getSyntaxOffset(node, kind: CodePositionKind.START); |
857 Offset entryOffset = getOffsetForNode(node, statementOffset); | 819 Offset entryOffset = getOffsetForNode(node, statementOffset); |
858 notifyStep(node, entryOffset, StepKind.FUN_ENTRY); | 820 notifyStep(node, entryOffset, StepKind.FUN_ENTRY); |
859 | 821 |
860 visit(node.body); | 822 visit(node.body); |
861 | 823 |
862 leftToRightOffset = statementOffset = | 824 leftToRightOffset = |
863 getSyntaxOffset(node, kind: CodePositionKind.CLOSING); | 825 statementOffset = getSyntaxOffset(node, kind: CodePositionKind.CLOSING); |
864 Offset exitOffset = getOffsetForNode(node, statementOffset); | 826 Offset exitOffset = getOffsetForNode(node, statementOffset); |
865 notifyStep(node, exitOffset, StepKind.FUN_EXIT); | 827 notifyStep(node, exitOffset, StepKind.FUN_EXIT); |
866 active = activeBefore; | 828 active = activeBefore; |
867 } | 829 } |
868 | 830 |
869 @override | 831 @override |
870 visitBlock(js.Block node) { | 832 visitBlock(js.Block node) { |
871 for (js.Statement statement in node.statements) { | 833 for (js.Statement statement in node.statements) { |
872 visit(statement); | 834 visit(statement); |
873 } | 835 } |
874 } | 836 } |
875 | 837 |
876 int getSyntaxOffset(js.Node node, | 838 int getSyntaxOffset(js.Node node, |
877 {CodePositionKind kind: CodePositionKind.START}) { | 839 {CodePositionKind kind: CodePositionKind.START}) { |
878 CodePosition codePosition = codePositions[node]; | 840 CodePosition codePosition = codePositions[node]; |
879 if (codePosition != null) { | 841 if (codePosition != null) { |
880 return codePosition.getPosition(kind); | 842 return codePosition.getPosition(kind); |
881 } | 843 } |
882 return null; | 844 return null; |
883 } | 845 } |
884 | 846 |
885 visitSubexpression(js.Node parent, | 847 visitSubexpression( |
886 js.Expression child, | 848 js.Node parent, js.Expression child, int codeOffset, StepKind kind) { |
887 int codeOffset, | |
888 StepKind kind) { | |
889 var oldSteps = steps; | 849 var oldSteps = steps; |
890 steps = []; | 850 steps = []; |
891 offsetPosition = codeOffset; | 851 offsetPosition = codeOffset; |
892 visit(child); | 852 visit(child); |
893 if (steps.isEmpty) { | 853 if (steps.isEmpty) { |
894 notifyStep(parent, | 854 notifyStep(parent, getOffsetForNode(parent, offsetPosition), kind); |
895 getOffsetForNode(parent, offsetPosition), | |
896 kind); | |
897 // The [offsetPosition] should only be used by the first subexpression. | 855 // The [offsetPosition] should only be used by the first subexpression. |
898 offsetPosition = null; | 856 offsetPosition = null; |
899 } | 857 } |
900 steps = oldSteps; | 858 steps = oldSteps; |
901 } | 859 } |
902 | 860 |
903 @override | 861 @override |
904 visitExpressionStatement(js.ExpressionStatement node) { | 862 visitExpressionStatement(js.ExpressionStatement node) { |
905 statementOffset = getSyntaxOffset(node); | 863 statementOffset = getSyntaxOffset(node); |
906 visitSubexpression( | 864 visitSubexpression( |
907 node, node.expression, statementOffset, | 865 node, node.expression, statementOffset, StepKind.EXPRESSION_STATEMENT); |
908 StepKind.EXPRESSION_STATEMENT); | |
909 statementOffset = null; | 866 statementOffset = null; |
910 leftToRightOffset = null; | 867 leftToRightOffset = null; |
911 } | 868 } |
912 | 869 |
913 @override | 870 @override |
914 visitEmptyStatement(js.EmptyStatement node) {} | 871 visitEmptyStatement(js.EmptyStatement node) {} |
915 | 872 |
916 @override | 873 @override |
917 visitCall(js.Call node) { | 874 visitCall(js.Call node) { |
918 visit(node.target); | 875 visit(node.target); |
919 int oldPosition = offsetPosition; | 876 int oldPosition = offsetPosition; |
920 offsetPosition = null; | 877 offsetPosition = null; |
921 visitList(node.arguments); | 878 visitList(node.arguments); |
922 offsetPosition = oldPosition; | 879 offsetPosition = oldPosition; |
923 CallPosition callPosition = | 880 CallPosition callPosition = CallPosition.getSemanticPositionForCall(node); |
924 CallPosition.getSemanticPositionForCall(node); | |
925 js.Node positionNode = callPosition.node; | 881 js.Node positionNode = callPosition.node; |
926 int callOffset = getSyntaxOffset( | 882 int callOffset = |
927 positionNode, kind: callPosition.codePositionKind); | 883 getSyntaxOffset(positionNode, kind: callPosition.codePositionKind); |
928 if (offsetPosition == null) { | 884 if (offsetPosition == null) { |
929 // Use the call offset if this is not the first subexpression. | 885 // Use the call offset if this is not the first subexpression. |
930 offsetPosition = callOffset; | 886 offsetPosition = callOffset; |
931 } | 887 } |
932 Offset offset = getOffsetForNode(positionNode, offsetPosition); | 888 Offset offset = getOffsetForNode(positionNode, offsetPosition); |
933 notifyStep(node, offset, StepKind.CALL); | 889 notifyStep(node, offset, StepKind.CALL); |
934 steps.add(node); | 890 steps.add(node); |
935 offsetPosition = null; | 891 offsetPosition = null; |
936 } | 892 } |
937 | 893 |
938 @override | 894 @override |
939 visitNew(js.New node) { | 895 visitNew(js.New node) { |
940 visit(node.target); | 896 visit(node.target); |
941 visitList(node.arguments); | 897 visitList(node.arguments); |
942 if (offsetPosition == null) { | 898 if (offsetPosition == null) { |
943 // Use the syntax offset if this is not the first subexpression. | 899 // Use the syntax offset if this is not the first subexpression. |
944 offsetPosition = getSyntaxOffset(node); | 900 offsetPosition = getSyntaxOffset(node); |
945 } | 901 } |
946 notifyStep( | 902 notifyStep(node, getOffsetForNode(node, offsetPosition), StepKind.NEW); |
947 node, getOffsetForNode(node, offsetPosition), StepKind.NEW); | |
948 steps.add(node); | 903 steps.add(node); |
949 offsetPosition = null; | 904 offsetPosition = null; |
950 } | 905 } |
951 | 906 |
952 @override | 907 @override |
953 visitAccess(js.PropertyAccess node) { | 908 visitAccess(js.PropertyAccess node) { |
954 visit(node.receiver); | 909 visit(node.receiver); |
955 visit(node.selector); | 910 visit(node.selector); |
956 } | 911 } |
957 | 912 |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
989 | 944 |
990 @override | 945 @override |
991 visitAssignment(js.Assignment node) { | 946 visitAssignment(js.Assignment node) { |
992 visit(node.leftHandSide); | 947 visit(node.leftHandSide); |
993 visit(node.value); | 948 visit(node.value); |
994 } | 949 } |
995 | 950 |
996 @override | 951 @override |
997 visitIf(js.If node) { | 952 visitIf(js.If node) { |
998 statementOffset = getSyntaxOffset(node); | 953 statementOffset = getSyntaxOffset(node); |
999 visitSubexpression(node, node.condition, statementOffset, | 954 visitSubexpression( |
1000 StepKind.IF_CONDITION); | 955 node, node.condition, statementOffset, StepKind.IF_CONDITION); |
1001 statementOffset = null; | 956 statementOffset = null; |
1002 visit(node.then, BranchKind.CONDITION, true); | 957 visit(node.then, BranchKind.CONDITION, true); |
1003 visit(node.otherwise, BranchKind.CONDITION, false); | 958 visit(node.otherwise, BranchKind.CONDITION, false); |
1004 } | 959 } |
1005 | 960 |
1006 @override | 961 @override |
1007 visitFor(js.For node) { | 962 visitFor(js.For node) { |
1008 int offset = statementOffset = getSyntaxOffset(node); | 963 int offset = statementOffset = getSyntaxOffset(node); |
1009 statementOffset = offset; | 964 statementOffset = offset; |
1010 leftToRightOffset = null; | 965 leftToRightOffset = null; |
1011 if (node.init != null) { | 966 if (node.init != null) { |
1012 visitSubexpression(node, node.init, getSyntaxOffset(node), | 967 visitSubexpression( |
1013 StepKind.FOR_INITIALIZER); | 968 node, node.init, getSyntaxOffset(node), StepKind.FOR_INITIALIZER); |
1014 } | 969 } |
1015 | 970 |
1016 if (node.condition != null) { | 971 if (node.condition != null) { |
1017 visitSubexpression(node, node.condition, getSyntaxOffset(node.condition), | 972 visitSubexpression(node, node.condition, getSyntaxOffset(node.condition), |
1018 StepKind.FOR_CONDITION); | 973 StepKind.FOR_CONDITION); |
1019 } | 974 } |
1020 | 975 |
1021 notifyPushBranch(BranchKind.LOOP); | 976 notifyPushBranch(BranchKind.LOOP); |
1022 visit(node.body); | 977 visit(node.body); |
1023 | 978 |
1024 statementOffset = offset; | 979 statementOffset = offset; |
1025 if (node.update != null) { | 980 if (node.update != null) { |
1026 visitSubexpression(node, node.update, getSyntaxOffset(node.update), | 981 visitSubexpression( |
1027 StepKind.FOR_UPDATE); | 982 node, node.update, getSyntaxOffset(node.update), StepKind.FOR_UPDATE); |
1028 } | 983 } |
1029 | 984 |
1030 notifyPopBranch(); | 985 notifyPopBranch(); |
1031 } | 986 } |
1032 | 987 |
1033 @override | 988 @override |
1034 visitWhile(js.While node) { | 989 visitWhile(js.While node) { |
1035 statementOffset = getSyntaxOffset(node); | 990 statementOffset = getSyntaxOffset(node); |
1036 if (node.condition != null) { | 991 if (node.condition != null) { |
1037 visitSubexpression(node, node.condition, getSyntaxOffset(node), | 992 visitSubexpression(node, node.condition, getSyntaxOffset(node), |
(...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1143 visit(node.name); | 1098 visit(node.name); |
1144 visit(node.value); | 1099 visit(node.value); |
1145 } | 1100 } |
1146 | 1101 |
1147 @override | 1102 @override |
1148 visitRegExpLiteral(js.RegExpLiteral node) {} | 1103 visitRegExpLiteral(js.RegExpLiteral node) {} |
1149 | 1104 |
1150 @override | 1105 @override |
1151 visitSwitch(js.Switch node) { | 1106 visitSwitch(js.Switch node) { |
1152 statementOffset = getSyntaxOffset(node); | 1107 statementOffset = getSyntaxOffset(node); |
1153 visitSubexpression(node, node.key, getSyntaxOffset(node), | 1108 visitSubexpression( |
1154 StepKind.SWITCH_EXPRESSION); | 1109 node, node.key, getSyntaxOffset(node), StepKind.SWITCH_EXPRESSION); |
1155 statementOffset = null; | 1110 statementOffset = null; |
1156 leftToRightOffset = null; | 1111 leftToRightOffset = null; |
1157 for (int i = 0; i < node.cases.length; i++) { | 1112 for (int i = 0; i < node.cases.length; i++) { |
1158 visit(node.cases[i], BranchKind.CASE, i); | 1113 visit(node.cases[i], BranchKind.CASE, i); |
1159 } | 1114 } |
1160 } | 1115 } |
1161 | 1116 |
1162 @override | 1117 @override |
1163 visitCase(js.Case node) { | 1118 visitCase(js.Case node) { |
1164 visit(node.expression); | 1119 visit(node.expression); |
(...skipping 30 matching lines...) Expand all Loading... |
1195 if (leftToRightOffset != null && leftToRightOffset < codeOffset) { | 1150 if (leftToRightOffset != null && leftToRightOffset < codeOffset) { |
1196 leftToRightOffset = codeOffset; | 1151 leftToRightOffset = codeOffset; |
1197 } | 1152 } |
1198 if (leftToRightOffset == null) { | 1153 if (leftToRightOffset == null) { |
1199 leftToRightOffset = statementOffset; | 1154 leftToRightOffset = statementOffset; |
1200 } | 1155 } |
1201 return new Offset(statementOffset, leftToRightOffset, codeOffset); | 1156 return new Offset(statementOffset, leftToRightOffset, codeOffset); |
1202 } | 1157 } |
1203 } | 1158 } |
1204 | 1159 |
1205 | |
1206 class Coverage { | 1160 class Coverage { |
1207 Set<js.Node> _nodesWithInfo = new Set<js.Node>(); | 1161 Set<js.Node> _nodesWithInfo = new Set<js.Node>(); |
1208 int _nodesWithInfoCount = 0; | 1162 int _nodesWithInfoCount = 0; |
1209 Set<js.Node> _nodesWithoutInfo = new Set<js.Node>(); | 1163 Set<js.Node> _nodesWithoutInfo = new Set<js.Node>(); |
1210 int _nodesWithoutInfoCount = 0; | 1164 int _nodesWithoutInfoCount = 0; |
1211 Map<Type, int> _nodesWithoutInfoCountByType = <Type, int>{}; | 1165 Map<Type, int> _nodesWithoutInfoCountByType = <Type, int>{}; |
1212 Set<js.Node> _nodesWithoutOffset = new Set<js.Node>(); | 1166 Set<js.Node> _nodesWithoutOffset = new Set<js.Node>(); |
1213 int _nodesWithoutOffsetCount = 0; | 1167 int _nodesWithoutOffsetCount = 0; |
1214 | 1168 |
1215 void registerNodeWithInfo(js.Node node) { | 1169 void registerNodeWithInfo(js.Node node) { |
(...skipping 14 matching lines...) Expand all Loading... |
1230 _nodesWithoutOffsetCount += _nodesWithoutOffset.length; | 1184 _nodesWithoutOffsetCount += _nodesWithoutOffset.length; |
1231 _nodesWithoutOffset.clear(); | 1185 _nodesWithoutOffset.clear(); |
1232 | 1186 |
1233 _nodesWithoutInfoCount += _nodesWithoutInfo.length; | 1187 _nodesWithoutInfoCount += _nodesWithoutInfo.length; |
1234 for (js.Node node in _nodesWithoutInfo) { | 1188 for (js.Node node in _nodesWithoutInfo) { |
1235 if (node is js.ExpressionStatement) { | 1189 if (node is js.ExpressionStatement) { |
1236 _nodesWithoutInfoCountByType.putIfAbsent( | 1190 _nodesWithoutInfoCountByType.putIfAbsent( |
1237 node.expression.runtimeType, () => 0); | 1191 node.expression.runtimeType, () => 0); |
1238 _nodesWithoutInfoCountByType[node.expression.runtimeType]++; | 1192 _nodesWithoutInfoCountByType[node.expression.runtimeType]++; |
1239 } else { | 1193 } else { |
1240 _nodesWithoutInfoCountByType.putIfAbsent( | 1194 _nodesWithoutInfoCountByType.putIfAbsent(node.runtimeType, () => 0); |
1241 node.runtimeType, () => 0); | |
1242 _nodesWithoutInfoCountByType[node.runtimeType]++; | 1195 _nodesWithoutInfoCountByType[node.runtimeType]++; |
1243 } | 1196 } |
1244 } | 1197 } |
1245 _nodesWithoutInfo.clear(); | 1198 _nodesWithoutInfo.clear(); |
1246 } | 1199 } |
1247 | 1200 |
1248 String getCoverageReport() { | 1201 String getCoverageReport() { |
1249 collapse(); | 1202 collapse(); |
1250 StringBuffer sb = new StringBuffer(); | 1203 StringBuffer sb = new StringBuffer(); |
1251 int total = _nodesWithInfoCount + _nodesWithoutInfoCount; | 1204 int total = _nodesWithInfoCount + _nodesWithoutInfoCount; |
(...skipping 15 matching lines...) Expand all Loading... |
1267 sb.write('s'); | 1220 sb.write('s'); |
1268 } | 1221 } |
1269 sb.write(' without offset.'); | 1222 sb.write(' without offset.'); |
1270 } | 1223 } |
1271 if (_nodesWithoutInfoCount > 0) { | 1224 if (_nodesWithoutInfoCount > 0) { |
1272 sb.write('\nNodes without info ('); | 1225 sb.write('\nNodes without info ('); |
1273 sb.write(_nodesWithoutInfoCount); | 1226 sb.write(_nodesWithoutInfoCount); |
1274 sb.write(') by runtime type:'); | 1227 sb.write(') by runtime type:'); |
1275 List<Type> types = _nodesWithoutInfoCountByType.keys.toList(); | 1228 List<Type> types = _nodesWithoutInfoCountByType.keys.toList(); |
1276 types.sort((a, b) { | 1229 types.sort((a, b) { |
1277 return -_nodesWithoutInfoCountByType[a].compareTo( | 1230 return -_nodesWithoutInfoCountByType[a] |
1278 _nodesWithoutInfoCountByType[b]); | 1231 .compareTo(_nodesWithoutInfoCountByType[b]); |
1279 }); | 1232 }); |
1280 | 1233 |
1281 types.forEach((Type type) { | 1234 types.forEach((Type type) { |
1282 int count = _nodesWithoutInfoCountByType[type]; | 1235 int count = _nodesWithoutInfoCountByType[type]; |
1283 sb.write('\n '); | 1236 sb.write('\n '); |
1284 sb.write(count); | 1237 sb.write(count); |
1285 sb.write(' '); | 1238 sb.write(' '); |
1286 sb.write(type); | 1239 sb.write(type); |
1287 sb.write(' node'); | 1240 sb.write(' node'); |
1288 if (count > 1) { | 1241 if (count > 1) { |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1324 final CodePositionMap codePositions; | 1277 final CodePositionMap codePositions; |
1325 final Coverage coverage; | 1278 final Coverage coverage; |
1326 | 1279 |
1327 CodePositionCoverage(this.codePositions, this.coverage); | 1280 CodePositionCoverage(this.codePositions, this.coverage); |
1328 | 1281 |
1329 @override | 1282 @override |
1330 CodePosition operator [](js.Node node) { | 1283 CodePosition operator [](js.Node node) { |
1331 CodePosition codePosition = codePositions[node]; | 1284 CodePosition codePosition = codePositions[node]; |
1332 if (codePosition == null) { | 1285 if (codePosition == null) { |
1333 coverage.registerNodesWithoutOffset(node); | 1286 coverage.registerNodesWithoutOffset(node); |
1334 } | 1287 } |
1335 return codePosition; | 1288 return codePosition; |
1336 } | 1289 } |
1337 } | 1290 } |
OLD | NEW |