Index: tests/compiler/dart2js/sourcemaps/sourcemap_helper.dart |
diff --git a/tests/compiler/dart2js/sourcemaps/sourcemap_helper.dart b/tests/compiler/dart2js/sourcemaps/sourcemap_helper.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..d7aa763be8dc34f44ac9c2243d9688a64552e893 |
--- /dev/null |
+++ b/tests/compiler/dart2js/sourcemaps/sourcemap_helper.dart |
@@ -0,0 +1,331 @@ |
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+library sourcemap.helper; |
+ |
+import 'dart:async'; |
+import 'package:compiler/src/apiimpl.dart' as api; |
+import 'package:compiler/src/dart2jslib.dart' show NullSink; |
+import "package:compiler/src/elements/elements.dart"; |
+import 'package:compiler/src/filenames.dart'; |
+import 'package:compiler/src/io/source_file.dart'; |
+import 'package:compiler/src/io/source_information.dart'; |
+import 'package:compiler/src/js/js.dart' as js; |
+import 'package:compiler/src/js/js_debug.dart'; |
+import 'package:compiler/src/js/js_source_mapping.dart'; |
+import 'package:compiler/src/js_backend/js_backend.dart'; |
+import 'package:compiler/src/source_file_provider.dart'; |
+import '../memory_compiler.dart'; |
+import '../output_collector.dart'; |
+ |
+class OutputProvider { |
+ BufferedEventSink jsMapOutput; |
+ |
+ EventSink<String> call(String name, String extension) { |
+ if (extension == 'js.map') { |
+ return jsMapOutput = new BufferedEventSink(); |
+ } |
+ return new NullSink('$name.$extension'); |
+ } |
+} |
+ |
+class CloningOutputProvider extends OutputProvider { |
+ RandomAccessFileOutputProvider outputProvider; |
+ |
+ CloningOutputProvider(Uri jsUri, Uri jsMapUri) |
+ : outputProvider = new RandomAccessFileOutputProvider(jsUri, jsMapUri); |
+ |
+ EventSink<String> call(String name, String extension) { |
+ EventSink<String> output = outputProvider(name, extension); |
+ if (extension == 'js.map') { |
+ output = new CloningEventSink( |
+ [output, jsMapOutput = new BufferedEventSink()]); |
+ } |
+ return output; |
+ } |
+} |
+ |
+abstract class SourceFileManager { |
+ SourceFile getSourceFile(var uri); |
+} |
+ |
+class ProviderSourceFileManager implements SourceFileManager { |
+ final SourceFileProvider sourceFileProvider; |
+ |
+ ProviderSourceFileManager(this.sourceFileProvider); |
+ |
+ @override |
+ SourceFile getSourceFile(uri) { |
+ return sourceFileProvider.getSourceFile(uri); |
+ } |
+} |
+ |
+class RecordingPrintingContext extends LenientPrintingContext { |
+ CodePositionListener listener; |
+ |
+ RecordingPrintingContext(this.listener); |
+ |
+ @override |
+ void exitNode(js.Node node, |
+ int startPosition, |
+ int endPosition, |
+ int closingPosition) { |
+ listener.onPositions( |
+ node, startPosition, endPosition, closingPosition); |
+ } |
+} |
+ |
+/// Processor that computes [SourceMapInfo] for the JavaScript compiled for a |
+/// given Dart file. |
+class SourceMapProcessor { |
+ /// If `true` the output from the compilation is written to files. |
+ final bool outputToFile; |
+ |
+ /// The [Uri] of the Dart entrypoint. |
+ Uri inputUri; |
+ |
+ /// The name of the JavaScript output file. |
+ String jsPath; |
+ |
+ /// The [Uri] of the JavaScript output file. |
+ Uri targetUri; |
+ |
+ /// The [Uri] of the JavaScript source map file. |
+ Uri sourceMapFileUri; |
+ |
+ /// The [SourceFileManager] created for the processing. |
+ SourceFileManager sourceFileManager; |
+ |
+ /// Creates a processor for the Dart file [filename]. |
+ SourceMapProcessor(String filename, {this.outputToFile: false}) { |
+ inputUri = Uri.base.resolve(nativeToUriPath(filename)); |
+ jsPath = 'out.js'; |
+ targetUri = Uri.base.resolve(jsPath); |
+ sourceMapFileUri = Uri.base.resolve('${jsPath}.map'); |
+ } |
+ |
+ /// Computes the [SourceMapInfo] for the compiled elements. |
+ Future<List<SourceMapInfo>> process(List<String> options) async { |
+ OutputProvider outputProvider = outputToFile |
+ ? new OutputProvider() |
+ : new CloningOutputProvider(targetUri, sourceMapFileUri); |
+ if (options.contains('--use-new-source-info')) { |
+ print('Using the new source information system.'); |
+ useNewSourceInfo = true; |
+ } |
+ api.Compiler compiler = await compilerFor({}, |
+ outputProvider: outputProvider, |
+ options: ['--out=$targetUri', '--source-map=$sourceMapFileUri'] |
+ ..addAll(options)); |
+ if (options.contains('--disable-inlining')) { |
+ print('Inlining disabled'); |
+ compiler.disableInlining = true; |
+ } |
+ |
+ JavaScriptBackend backend = compiler.backend; |
+ var handler = compiler.handler; |
+ SourceFileProvider sourceFileProvider = handler.provider; |
+ sourceFileManager = new ProviderSourceFileManager(sourceFileProvider); |
+ await compiler.runCompiler(inputUri); |
+ |
+ List<SourceMapInfo> infoList = <SourceMapInfo>[]; |
+ backend.generatedCode.forEach((Element element, js.Expression node) { |
+ js.JavaScriptPrintingOptions options = |
+ new js.JavaScriptPrintingOptions(); |
+ JavaScriptSourceInformationStrategy sourceInformationStrategy = |
+ compiler.backend.sourceInformationStrategy; |
+ NodeToSourceLocationsMap nodeMap = new NodeToSourceLocationsMap(); |
+ SourceInformationProcessor sourceInformationProcessor = |
+ sourceInformationStrategy.createProcessor(nodeMap); |
+ RecordingPrintingContext printingContext = |
+ new RecordingPrintingContext(sourceInformationProcessor); |
+ new js.Printer(options, printingContext).visit(node); |
+ sourceInformationProcessor.process(node); |
+ |
+ String code = printingContext.getText(); |
+ CodePointComputer visitor = |
+ new CodePointComputer(sourceFileManager, code, nodeMap); |
+ visitor.apply(node); |
+ List<CodePoint> codePoints = visitor.codePoints; |
+ infoList.add(new SourceMapInfo(element, code, node, codePoints, nodeMap)); |
+ }); |
+ |
+ return infoList; |
+ } |
+} |
+ |
+/// Source mapping information for the JavaScript code of an [Element]. |
+class SourceMapInfo { |
+ final String name; |
+ final Element element; |
+ final String code; |
+ final js.Expression node; |
+ final List<CodePoint> codePoints; |
+ final NodeToSourceLocationsMap nodeMap; |
+ |
+ SourceMapInfo( |
+ Element element, this.code, this.node, this.codePoints, this.nodeMap) |
+ : this.name = computeElementNameForSourceMaps(element), |
+ this.element = element; |
+} |
+ |
+/// Collection of JavaScript nodes with their source mapped target offsets |
+/// and source locations. |
+class NodeToSourceLocationsMap implements SourceMapper { |
+ final Map<js.Node, Map<int, List<SourceLocation>>> _nodeMap = {}; |
+ |
+ @override |
+ void register(js.Node node, int codeOffset, SourceLocation sourceLocation) { |
+ _nodeMap.putIfAbsent(node, () => {}) |
+ .putIfAbsent(codeOffset, () => []) |
+ .add(sourceLocation); |
+ } |
+ |
+ Iterable<js.Node> get nodes => _nodeMap.keys; |
+ |
+ Map<int, List<SourceLocation>> operator[] (js.Node node) { |
+ return _nodeMap[node]; |
+ } |
+} |
+ |
+/// Visitor that computes the [CodePoint]s for source mapping locations. |
+class CodePointComputer extends js.BaseVisitor { |
+ final SourceFileManager sourceFileManager; |
+ final String code; |
+ final NodeToSourceLocationsMap nodeMap; |
+ List<CodePoint> codePoints = []; |
+ |
+ CodePointComputer(this.sourceFileManager, this.code, this.nodeMap); |
+ |
+ String nodeToString(js.Node node) { |
+ js.JavaScriptPrintingOptions options = new js.JavaScriptPrintingOptions( |
+ shouldCompressOutput: true, |
+ preferSemicolonToNewlineInMinifiedOutput: true); |
+ LenientPrintingContext printingContext = new LenientPrintingContext(); |
+ new js.Printer(options, printingContext).visit(node); |
+ return printingContext.buffer.toString(); |
+ } |
+ |
+ String positionToString(int position) { |
+ String line = code.substring(position); |
+ int nl = line.indexOf('\n'); |
+ if (nl != -1) { |
+ line = line.substring(0, nl); |
+ } |
+ return line; |
+ } |
+ |
+ void register(String kind, js.Node node, {bool expectInfo: true}) { |
+ |
+ String dartCodeFromSourceLocation(SourceLocation sourceLocation) { |
+ SourceFile sourceFile = |
+ sourceFileManager.getSourceFile(sourceLocation.sourceUri); |
+ return sourceFile.getLineText(sourceLocation.line) |
+ .substring(sourceLocation.column).trim(); |
+ } |
+ |
+ void addLocation(SourceLocation sourceLocation, String jsCode) { |
+ if (sourceLocation == null) { |
+ if (expectInfo) { |
+ SourceInformation sourceInformation = node.sourceInformation; |
+ SourceLocation sourceLocation; |
+ String dartCode; |
+ if (sourceInformation != null) { |
+ sourceLocation = sourceInformation.sourceLocations.first; |
+ dartCode = dartCodeFromSourceLocation(sourceLocation); |
+ } |
+ codePoints.add(new CodePoint( |
+ kind, jsCode, sourceLocation, dartCode, isMissing: true)); |
+ } |
+ } else { |
+ codePoints.add(new CodePoint(kind, jsCode, sourceLocation, |
+ dartCodeFromSourceLocation(sourceLocation))); |
+ } |
+ } |
+ |
+ Map<int, List<SourceLocation>> locationMap = nodeMap[node]; |
+ if (locationMap == null) { |
+ addLocation(null, nodeToString(node)); |
+ } else { |
+ locationMap.forEach((int targetOffset, List<SourceLocation> locations) { |
+ String jsCode = positionToString(targetOffset); |
+ for (SourceLocation location in locations) { |
+ addLocation(location, jsCode); |
+ } |
+ }); |
+ } |
+ } |
+ |
+ void apply(js.Node node) { |
+ node.accept(this); |
+ } |
+ |
+ void visitNode(js.Node node) { |
+ register('${node.runtimeType}', node, expectInfo: false); |
+ super.visitNode(node); |
+ } |
+ |
+ @override |
+ void visitNew(js.New node) { |
+ node.arguments.forEach(apply); |
+ register('New', node); |
+ } |
+ |
+ @override |
+ void visitReturn(js.Return node) { |
+ if (node.value != null) { |
+ apply(node.value); |
+ } |
+ register('Return', node); |
+ } |
+ |
+ @override |
+ void visitCall(js.Call node) { |
+ apply(node.target); |
+ node.arguments.forEach(apply); |
+ register('Call (${node.target.runtimeType})', node); |
+ } |
+ |
+ @override |
+ void visitFun(js.Fun node) { |
+ node.visitChildren(this); |
+ register('Fun', node); |
+ } |
+ |
+ @override |
+ visitExpressionStatement(js.ExpressionStatement node) { |
+ node.visitChildren(this); |
+ } |
+ |
+ @override |
+ visitBinary(js.Binary node) { |
+ node.visitChildren(this); |
+ } |
+ |
+ @override |
+ visitAccess(js.PropertyAccess node) { |
+ node.visitChildren(this); |
+ } |
+} |
+ |
+/// A JavaScript code point and its mapped dart source location. |
+class CodePoint { |
+ final String kind; |
+ final String jsCode; |
+ final SourceLocation sourceLocation; |
+ final String dartCode; |
+ final bool isMissing; |
+ |
+ CodePoint( |
+ this.kind, |
+ this.jsCode, |
+ this.sourceLocation, |
+ this.dartCode, |
+ {this.isMissing: false}); |
+ |
+ String toString() { |
+ return 'CodePoint[kind=$kind,js=$jsCode,dart=$dartCode,' |
+ 'location=$sourceLocation]'; |
+ } |
+} |