Chromium Code Reviews| 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..a407a54f93d80c1a362f1dab606a32b5a414e386 |
| --- /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.'); |
| + USE_NEW_SOURCE_INFO = true; |
|
floitsch
2015/06/29 08:55:52
If we can assign to it, it's not a constant and sh
Johnni Winther
2015/06/29 12:36:30
Done.
|
| + } |
| + 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]'; |
| + } |
| +} |