| 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]';
|
| + }
|
| +}
|
|
|