Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(461)

Side by Side Diff: pkg/compiler/lib/src/io/position_information.dart

Issue 1617083002: Base JavaScript code position computation on JavaScript tracer. (Closed) Base URL: https://github.com/dart-lang/sdk.git@master
Patch Set: Created 4 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698