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 library sourcemap.helper; | 5 library sourcemap.helper; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:io'; |
8 import 'package:compiler/compiler_new.dart'; | 9 import 'package:compiler/compiler_new.dart'; |
9 import 'package:compiler/src/apiimpl.dart' as api; | 10 import 'package:compiler/src/apiimpl.dart' as api; |
10 import 'package:compiler/src/null_compiler_output.dart' show NullSink; | 11 import 'package:compiler/src/null_compiler_output.dart' show NullSink; |
11 import 'package:compiler/src/elements/elements.dart'; | 12 import 'package:compiler/src/elements/elements.dart'; |
12 import 'package:compiler/src/helpers/helpers.dart'; | 13 import 'package:compiler/src/helpers/helpers.dart'; |
13 import 'package:compiler/src/filenames.dart'; | 14 import 'package:compiler/src/filenames.dart'; |
14 import 'package:compiler/src/io/code_output.dart'; | 15 import 'package:compiler/src/io/code_output.dart'; |
15 import 'package:compiler/src/io/source_file.dart'; | 16 import 'package:compiler/src/io/source_file.dart'; |
16 import 'package:compiler/src/io/source_information.dart'; | 17 import 'package:compiler/src/io/source_information.dart'; |
17 import 'package:compiler/src/io/position_information.dart'; | 18 import 'package:compiler/src/io/position_information.dart'; |
(...skipping 278 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
296 | 297 |
297 /// Creates a processor for the Dart file [filename]. | 298 /// Creates a processor for the Dart file [filename]. |
298 SourceMapProcessor(String filename, {this.outputToFile: false}) { | 299 SourceMapProcessor(String filename, {this.outputToFile: false}) { |
299 inputUri = Uri.base.resolve(nativeToUriPath(filename)); | 300 inputUri = Uri.base.resolve(nativeToUriPath(filename)); |
300 jsPath = 'out.js'; | 301 jsPath = 'out.js'; |
301 targetUri = Uri.base.resolve(jsPath); | 302 targetUri = Uri.base.resolve(jsPath); |
302 sourceMapFileUri = Uri.base.resolve('${jsPath}.map'); | 303 sourceMapFileUri = Uri.base.resolve('${jsPath}.map'); |
303 } | 304 } |
304 | 305 |
305 /// Computes the [SourceMapInfo] for the compiled elements. | 306 /// Computes the [SourceMapInfo] for the compiled elements. |
306 Future<List<SourceMapInfo>> process( | 307 Future<SourceMaps> process( |
307 List<String> options, | 308 List<String> options, |
308 {bool verbose: true, | 309 {bool verbose: true, |
309 bool perElement: true}) async { | 310 bool perElement: true, |
| 311 bool forMain: false}) async { |
310 OutputProvider outputProvider = outputToFile | 312 OutputProvider outputProvider = outputToFile |
311 ? new CloningOutputProvider(targetUri, sourceMapFileUri) | 313 ? new CloningOutputProvider(targetUri, sourceMapFileUri) |
312 : new OutputProvider(); | 314 : new OutputProvider(); |
313 if (options.contains(USE_NEW_SOURCE_INFO)) { | 315 if (options.contains(USE_NEW_SOURCE_INFO)) { |
314 if (verbose) print('Using the new source information system.'); | 316 if (verbose) print('Using the new source information system.'); |
315 useNewSourceInfo = true; | 317 useNewSourceInfo = true; |
316 } | 318 } |
317 api.CompilerImpl compiler = await compilerFor( | 319 api.CompilerImpl compiler = await compilerFor( |
318 outputProvider: outputProvider, | 320 outputProvider: outputProvider, |
319 // TODO(johnniwinther): Use [verbose] to avoid showing diagnostics. | 321 // TODO(johnniwinther): Use [verbose] to avoid showing diagnostics. |
320 options: ['--out=$targetUri', '--source-map=$sourceMapFileUri'] | 322 options: ['--out=$targetUri', '--source-map=$sourceMapFileUri'] |
321 ..addAll(options)); | 323 ..addAll(options)); |
322 if (options.contains(DISABLE_INLINING)) { | 324 if (options.contains(DISABLE_INLINING)) { |
323 if (verbose) print('Inlining disabled'); | 325 if (verbose) print('Inlining disabled'); |
324 compiler.disableInlining = true; | 326 compiler.disableInlining = true; |
325 } | 327 } |
326 | 328 |
327 JavaScriptBackend backend = compiler.backend; | 329 JavaScriptBackend backend = compiler.backend; |
328 var handler = compiler.handler; | 330 var handler = compiler.handler; |
329 SourceFileProvider sourceFileProvider = handler.provider; | 331 SourceFileProvider sourceFileProvider = handler.provider; |
330 sourceFileManager = new ProviderSourceFileManager( | 332 sourceFileManager = new ProviderSourceFileManager( |
331 sourceFileProvider, | 333 sourceFileProvider, |
332 outputProvider); | 334 outputProvider); |
333 RecordingSourceInformationStrategy strategy = | 335 RecordingSourceInformationStrategy strategy = |
334 new RecordingSourceInformationStrategy(backend.sourceInformationStrategy
); | 336 new RecordingSourceInformationStrategy(backend.sourceInformationStrategy
); |
335 backend.sourceInformationStrategy = strategy; | 337 backend.sourceInformationStrategy = strategy; |
336 await compiler.run(inputUri); | 338 await compiler.run(inputUri); |
337 | 339 |
338 List<SourceMapInfo> infoList = <SourceMapInfo>[]; | 340 SourceMapInfo mainSourceMapInfo; |
| 341 Map<Element, SourceMapInfo> elementSourceMapInfos = |
| 342 <Element, SourceMapInfo>{}; |
339 if (perElement) { | 343 if (perElement) { |
340 backend.generatedCode.forEach((Element element, js.Expression node) { | 344 backend.generatedCode.forEach((Element element, js.Expression node) { |
341 RecordedSourceInformationProcess subProcess = | 345 RecordedSourceInformationProcess subProcess = |
342 strategy.subProcessForNode(node); | 346 strategy.subProcessForNode(node); |
343 if (subProcess == null) { | 347 if (subProcess == null) { |
344 // TODO(johnniwinther): Find out when this is happening and if it | 348 // TODO(johnniwinther): Find out when this is happening and if it |
345 // is benign. (Known to happen for `bool#fromString`) | 349 // is benign. (Known to happen for `bool#fromString`) |
346 print('No subProcess found for $element'); | 350 print('No subProcess found for $element'); |
347 return; | 351 return; |
348 } | 352 } |
349 LocationMap nodeMap = subProcess.nodeToSourceLocationsMap; | 353 LocationMap nodeMap = subProcess.nodeToSourceLocationsMap; |
350 String code = subProcess.code; | 354 String code = subProcess.code; |
351 CodePositionRecorder codePositions = subProcess.codePositions; | 355 CodePositionRecorder codePositions = subProcess.codePositions; |
352 CodePointComputer visitor = | 356 CodePointComputer visitor = |
353 new CodePointComputer(sourceFileManager, code, nodeMap); | 357 new CodePointComputer(sourceFileManager, code, nodeMap); |
354 visitor.apply(node); | 358 new JavaScriptTracer(codePositions, [visitor]).apply(node); |
355 List<CodePoint> codePoints = visitor.codePoints; | 359 List<CodePoint> codePoints = visitor.codePoints; |
356 infoList.add(new SourceMapInfo( | 360 elementSourceMapInfos[element] = new SourceMapInfo( |
357 element, code, node, | 361 element, |
| 362 code, |
| 363 node, |
358 codePoints, | 364 codePoints, |
359 codePositions/*strategy.codePositions*/, | 365 codePositions, |
360 nodeMap)); | 366 nodeMap); |
361 }); | 367 }); |
362 } else { | 368 } |
| 369 if (forMain) { |
363 // TODO(johnniwinther): Supported multiple output units. | 370 // TODO(johnniwinther): Supported multiple output units. |
364 RecordedSourceInformationProcess process = strategy.processMap.keys.first; | 371 RecordedSourceInformationProcess process = strategy.processMap.keys.first; |
365 js.Node node = strategy.processMap[process]; | 372 js.Node node = strategy.processMap[process]; |
366 String code; | 373 String code; |
367 LocationMap nodeMap; | 374 LocationMap nodeMap; |
368 CodePositionRecorder codePositions; | 375 CodePositionRecorder codePositions; |
369 nodeMap = process.nodeToSourceLocationsMap; | 376 nodeMap = process.nodeToSourceLocationsMap; |
370 code = process.code; | 377 code = process.code; |
371 codePositions = process.codePositions; | 378 codePositions = process.codePositions; |
372 CodePointComputer visitor = | 379 CodePointComputer visitor = |
373 new CodePointComputer(sourceFileManager, code, nodeMap); | 380 new CodePointComputer(sourceFileManager, code, nodeMap); |
374 visitor.apply(node); | 381 new JavaScriptTracer(codePositions, [visitor]).apply(node); |
375 List<CodePoint> codePoints = visitor.codePoints; | 382 List<CodePoint> codePoints = visitor.codePoints; |
376 infoList.add(new SourceMapInfo( | 383 mainSourceMapInfo = new SourceMapInfo( |
377 null, code, node, | 384 null, code, node, |
378 codePoints, | 385 codePoints, |
379 codePositions, | 386 codePositions, |
380 nodeMap)); | 387 nodeMap); |
381 } | 388 } |
382 | 389 |
383 return infoList; | 390 return new SourceMaps( |
| 391 sourceFileManager, mainSourceMapInfo, elementSourceMapInfos); |
384 } | 392 } |
385 } | 393 } |
386 | 394 |
| 395 class SourceMaps { |
| 396 final SourceFileManager sourceFileManager; |
| 397 // TODO(johnniwinther): Supported multiple output units. |
| 398 final SourceMapInfo mainSourceMapInfo; |
| 399 final Map<Element, SourceMapInfo> elementSourceMapInfos; |
| 400 |
| 401 SourceMaps( |
| 402 this.sourceFileManager, |
| 403 this.mainSourceMapInfo, |
| 404 this.elementSourceMapInfos); |
| 405 } |
| 406 |
387 /// Source mapping information for the JavaScript code of an [Element]. | 407 /// Source mapping information for the JavaScript code of an [Element]. |
388 class SourceMapInfo { | 408 class SourceMapInfo { |
389 final String name; | 409 final String name; |
390 final Element element; | 410 final Element element; |
391 final String code; | 411 final String code; |
392 final js.Node node; | 412 final js.Node node; |
393 final List<CodePoint> codePoints; | 413 final List<CodePoint> codePoints; |
394 final CodePositionMap jsCodePositions; | 414 final CodePositionMap jsCodePositions; |
395 final LocationMap nodeMap; | 415 final LocationMap nodeMap; |
396 | 416 |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
450 | 470 |
451 Iterable<js.Node> get nodes => map.nodes.where((n) => _nodes.contains(n)); | 471 Iterable<js.Node> get nodes => map.nodes.where((n) => _nodes.contains(n)); |
452 | 472 |
453 Map<int, List<SourceLocation>> operator[] (js.Node node) { | 473 Map<int, List<SourceLocation>> operator[] (js.Node node) { |
454 return map[node]; | 474 return map[node]; |
455 } | 475 } |
456 } | 476 } |
457 | 477 |
458 | 478 |
459 /// Visitor that computes the [CodePoint]s for source mapping locations. | 479 /// Visitor that computes the [CodePoint]s for source mapping locations. |
460 class CodePointComputer extends js.BaseVisitor { | 480 class CodePointComputer extends TraceListener { |
461 final SourceFileManager sourceFileManager; | 481 final SourceFileManager sourceFileManager; |
462 final String code; | 482 final String code; |
463 final LocationMap nodeMap; | 483 final LocationMap nodeMap; |
464 List<CodePoint> codePoints = []; | 484 List<CodePoint> codePoints = []; |
465 | 485 |
466 CodePointComputer(this.sourceFileManager, this.code, this.nodeMap); | 486 CodePointComputer(this.sourceFileManager, this.code, this.nodeMap); |
467 | 487 |
468 String nodeToString(js.Node node) { | 488 String nodeToString(js.Node node) { |
469 js.JavaScriptPrintingOptions options = new js.JavaScriptPrintingOptions( | 489 js.JavaScriptPrintingOptions options = new js.JavaScriptPrintingOptions( |
470 shouldCompressOutput: true, | 490 shouldCompressOutput: true, |
471 preferSemicolonToNewlineInMinifiedOutput: true); | 491 preferSemicolonToNewlineInMinifiedOutput: true); |
472 LenientPrintingContext printingContext = new LenientPrintingContext(); | 492 LenientPrintingContext printingContext = new LenientPrintingContext(); |
473 new js.Printer(options, printingContext).visit(node); | 493 new js.Printer(options, printingContext).visit(node); |
474 return printingContext.buffer.toString(); | 494 return printingContext.buffer.toString(); |
475 } | 495 } |
476 | 496 |
477 String positionToString(int position) { | 497 String positionToString(int position) { |
478 String line = code.substring(position); | 498 String line = code.substring(position); |
479 int nl = line.indexOf('\n'); | 499 int nl = line.indexOf('\n'); |
480 if (nl != -1) { | 500 if (nl != -1) { |
481 line = line.substring(0, nl); | 501 line = line.substring(0, nl); |
482 } | 502 } |
483 return line; | 503 return line; |
484 } | 504 } |
485 | 505 |
| 506 /// Called when [node] defines a step of the given [kind] at the given |
| 507 /// [offset] when the generated JavaScript code. |
| 508 void onStep(js.Node node, Offset offset, StepKind kind) { |
| 509 register('$kind', node); |
| 510 } |
| 511 |
486 void register(String kind, js.Node node, {bool expectInfo: true}) { | 512 void register(String kind, js.Node node, {bool expectInfo: true}) { |
487 | 513 |
488 String dartCodeFromSourceLocation(SourceLocation sourceLocation) { | 514 String dartCodeFromSourceLocation(SourceLocation sourceLocation) { |
489 SourceFile sourceFile = | 515 SourceFile sourceFile = |
490 sourceFileManager.getSourceFile(sourceLocation.sourceUri); | 516 sourceFileManager.getSourceFile(sourceLocation.sourceUri); |
| 517 if (sourceFile == null) { |
| 518 return sourceLocation.shortText; |
| 519 } |
491 return sourceFile.getLineText(sourceLocation.line) | 520 return sourceFile.getLineText(sourceLocation.line) |
492 .substring(sourceLocation.column).trim(); | 521 .substring(sourceLocation.column).trim(); |
493 } | 522 } |
494 | 523 |
495 void addLocation(SourceLocation sourceLocation, String jsCode) { | 524 void addLocation(SourceLocation sourceLocation, String jsCode) { |
496 if (sourceLocation == null) { | 525 if (sourceLocation == null) { |
497 if (expectInfo) { | 526 if (expectInfo) { |
498 SourceInformation sourceInformation = node.sourceInformation; | 527 SourceInformation sourceInformation = node.sourceInformation; |
499 SourceLocation sourceLocation; | 528 SourceLocation sourceLocation; |
500 String dartCode; | 529 String dartCode; |
(...skipping 15 matching lines...) Expand all Loading... |
516 addLocation(null, nodeToString(node)); | 545 addLocation(null, nodeToString(node)); |
517 } else { | 546 } else { |
518 locationMap.forEach((int targetOffset, List<SourceLocation> locations) { | 547 locationMap.forEach((int targetOffset, List<SourceLocation> locations) { |
519 String jsCode = nodeToString(node); | 548 String jsCode = nodeToString(node); |
520 for (SourceLocation location in locations) { | 549 for (SourceLocation location in locations) { |
521 addLocation(location, jsCode); | 550 addLocation(location, jsCode); |
522 } | 551 } |
523 }); | 552 }); |
524 } | 553 } |
525 } | 554 } |
526 | |
527 void apply(js.Node node) { | |
528 node.accept(this); | |
529 } | |
530 | |
531 void visitNode(js.Node node) { | |
532 register('${node.runtimeType}', node, expectInfo: false); | |
533 super.visitNode(node); | |
534 } | |
535 | |
536 @override | |
537 void visitNew(js.New node) { | |
538 node.arguments.forEach(apply); | |
539 register('New', node); | |
540 } | |
541 | |
542 @override | |
543 void visitReturn(js.Return node) { | |
544 if (node.value != null) { | |
545 apply(node.value); | |
546 } | |
547 register('Return', node); | |
548 } | |
549 | |
550 @override | |
551 void visitCall(js.Call node) { | |
552 apply(node.target); | |
553 node.arguments.forEach(apply); | |
554 register('Call (${node.target.runtimeType})', node); | |
555 } | |
556 | |
557 @override | |
558 void visitFun(js.Fun node) { | |
559 node.visitChildren(this); | |
560 register('Fun', node); | |
561 } | |
562 | |
563 @override | |
564 visitExpressionStatement(js.ExpressionStatement node) { | |
565 node.visitChildren(this); | |
566 } | |
567 | |
568 @override | |
569 visitBinary(js.Binary node) { | |
570 node.visitChildren(this); | |
571 } | |
572 | |
573 @override | |
574 visitAccess(js.PropertyAccess node) { | |
575 node.visitChildren(this); | |
576 } | |
577 } | 555 } |
578 | 556 |
579 /// A JavaScript code point and its mapped dart source location. | 557 /// A JavaScript code point and its mapped dart source location. |
580 class CodePoint { | 558 class CodePoint { |
581 final String kind; | 559 final String kind; |
582 final String jsCode; | 560 final String jsCode; |
583 final SourceLocation sourceLocation; | 561 final SourceLocation sourceLocation; |
584 final String dartCode; | 562 final String dartCode; |
585 final bool isMissing; | 563 final bool isMissing; |
586 | 564 |
587 CodePoint( | 565 CodePoint( |
588 this.kind, | 566 this.kind, |
589 this.jsCode, | 567 this.jsCode, |
590 this.sourceLocation, | 568 this.sourceLocation, |
591 this.dartCode, | 569 this.dartCode, |
592 {this.isMissing: false}); | 570 {this.isMissing: false}); |
593 | 571 |
594 String toString() { | 572 String toString() { |
595 return 'CodePoint[kind=$kind,js=$jsCode,dart=$dartCode,' | 573 return 'CodePoint[kind=$kind,js=$jsCode,dart=$dartCode,' |
596 'location=$sourceLocation]'; | 574 'location=$sourceLocation]'; |
597 } | 575 } |
598 } | 576 } |
| 577 |
| 578 class IOSourceFileManager implements SourceFileManager { |
| 579 final Uri base; |
| 580 |
| 581 Map<Uri, SourceFile> sourceFiles = <Uri, SourceFile>{}; |
| 582 |
| 583 IOSourceFileManager(this.base); |
| 584 |
| 585 SourceFile getSourceFile(var uri) { |
| 586 Uri absoluteUri; |
| 587 if (uri is Uri) { |
| 588 absoluteUri = base.resolveUri(uri); |
| 589 } else { |
| 590 absoluteUri = base.resolve(uri); |
| 591 } |
| 592 return sourceFiles.putIfAbsent(absoluteUri, () { |
| 593 String text = new File.fromUri(absoluteUri).readAsStringSync(); |
| 594 return new StringSourceFile.fromUri(absoluteUri, text); |
| 595 }); |
| 596 } |
| 597 } |
OLD | NEW |