| 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
|
| index 9cb53e566d977cf1e567ef943f6dc7ee9507ba74..123cbe2a918ac1e427c2b116d9d8a5ad7422ab7a 100644
|
| --- a/tests/compiler/dart2js/sourcemaps/sourcemap_helper.dart
|
| +++ b/tests/compiler/dart2js/sourcemaps/sourcemap_helper.dart
|
| @@ -11,8 +11,10 @@ import 'package:compiler/src/null_compiler_output.dart' show NullSink;
|
| import 'package:compiler/src/elements/elements.dart';
|
| import 'package:compiler/src/helpers/helpers.dart';
|
| import 'package:compiler/src/filenames.dart';
|
| +import 'package:compiler/src/io/code_output.dart';
|
| import 'package:compiler/src/io/source_file.dart';
|
| import 'package:compiler/src/io/source_information.dart';
|
| +import 'package:compiler/src/io/position_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';
|
| @@ -21,15 +23,51 @@ import 'package:compiler/src/source_file_provider.dart';
|
| import '../memory_compiler.dart';
|
| import '../output_collector.dart';
|
|
|
| +class SourceFileSink implements EventSink<String> {
|
| + final String filename;
|
| + StringBuffer sb = new StringBuffer();
|
| + SourceFile sourceFile;
|
| +
|
| + SourceFileSink(this.filename);
|
| +
|
| + @override
|
| + void add(String event) {
|
| + sb.write(event);
|
| + }
|
| +
|
| + @override
|
| + void addError(errorEvent, [StackTrace stackTrace]) {
|
| + // Ignore.
|
| + }
|
| +
|
| + @override
|
| + void close() {
|
| + sourceFile = new StringSourceFile.fromName(filename, sb.toString());
|
| + }
|
| +}
|
| +
|
| class OutputProvider implements CompilerOutput {
|
| - BufferedEventSink jsMapOutput;
|
| + Map<Uri, SourceFileSink> outputMap = <Uri, SourceFileSink>{};
|
| +
|
| + SourceFile getSourceFile(Uri uri) {
|
| + SourceFileSink sink = outputMap[uri];
|
| + if (sink != null) {
|
| + return sink.sourceFile;
|
| + }
|
| + return null;
|
| + }
|
| +
|
| + SourceFileSink createSourceFileSink(String name, String extension) {
|
| + String filename = '$name.$extension';
|
| + SourceFileSink sink = new SourceFileSink(filename);
|
| + Uri uri = Uri.parse(filename);
|
| + outputMap[uri] = sink;
|
| + return sink;
|
| + }
|
|
|
| @override
|
| EventSink<String> createEventSink(String name, String extension) {
|
| - if (extension == 'js.map') {
|
| - return jsMapOutput = new BufferedEventSink();
|
| - }
|
| - return new NullSink('$name.$extension');
|
| + return createSourceFileSink(name, extension);
|
| }
|
| }
|
|
|
| @@ -42,11 +80,8 @@ class CloningOutputProvider extends OutputProvider {
|
| @override
|
| EventSink<String> createEventSink(String name, String extension) {
|
| EventSink<String> output = outputProvider(name, extension);
|
| - if (extension == 'js.map') {
|
| - output = new CloningEventSink(
|
| - [output, jsMapOutput = new BufferedEventSink()]);
|
| - }
|
| - return output;
|
| + return new CloningEventSink(
|
| + [output, createSourceFileSink(name, extension)]);
|
| }
|
| }
|
|
|
| @@ -56,17 +91,23 @@ abstract class SourceFileManager {
|
|
|
| class ProviderSourceFileManager implements SourceFileManager {
|
| final SourceFileProvider sourceFileProvider;
|
| + final OutputProvider outputProvider;
|
|
|
| - ProviderSourceFileManager(this.sourceFileProvider);
|
| + ProviderSourceFileManager(this.sourceFileProvider, this.outputProvider);
|
|
|
| @override
|
| SourceFile getSourceFile(uri) {
|
| - return sourceFileProvider.getSourceFile(uri);
|
| + SourceFile sourceFile = sourceFileProvider.getSourceFile(uri);
|
| + if (sourceFile == null) {
|
| + sourceFile = outputProvider.getSourceFile(uri);
|
| + }
|
| + return sourceFile;
|
| }
|
| }
|
|
|
| class RecordingPrintingContext extends LenientPrintingContext {
|
| CodePositionListener listener;
|
| + Map<js.Node, CodePosition> codePositions = <js.Node, CodePosition>{};
|
|
|
| RecordingPrintingContext(this.listener);
|
|
|
| @@ -75,11 +116,163 @@ class RecordingPrintingContext extends LenientPrintingContext {
|
| int startPosition,
|
| int endPosition,
|
| int closingPosition) {
|
| + codePositions[node] =
|
| + new CodePosition(startPosition, endPosition, closingPosition);
|
| listener.onPositions(
|
| node, startPosition, endPosition, closingPosition);
|
| }
|
| }
|
|
|
| +/// A [SourceMapper] that records the source locations on each node.
|
| +class RecordingSourceMapper implements SourceMapper {
|
| + final SourceMapper sourceMapper;
|
| + final _LocationRecorder nodeToSourceLocationsMap;
|
| +
|
| + RecordingSourceMapper(this.sourceMapper, this.nodeToSourceLocationsMap);
|
| +
|
| + @override
|
| + void register(js.Node node, int codeOffset, SourceLocation sourceLocation) {
|
| + nodeToSourceLocationsMap.register(node, codeOffset, sourceLocation);
|
| + sourceMapper.register(node, codeOffset, sourceLocation);
|
| + }
|
| +}
|
| +
|
| +/// A wrapper of [SourceInformationProcessor] that records source locations and
|
| +/// code positions.
|
| +class RecordingSourceInformationProcessor
|
| + implements SourceInformationProcessor {
|
| + final RecordingSourceInformationStrategy wrapper;
|
| + final SourceInformationProcessor processor;
|
| + final CodePositionRecorder codePositions;
|
| + final LocationMap nodeToSourceLocationsMap;
|
| +
|
| + RecordingSourceInformationProcessor(
|
| + this.wrapper,
|
| + this.processor,
|
| + this.codePositions,
|
| + this.nodeToSourceLocationsMap);
|
| +
|
| + @override
|
| + void onPositions(js.Node node,
|
| + int startPosition,
|
| + int endPosition,
|
| + int closingPosition) {
|
| + codePositions.registerPositions(
|
| + node, startPosition, endPosition, closingPosition);
|
| + processor.onPositions(node, startPosition, endPosition, closingPosition);
|
| + }
|
| +
|
| + @override
|
| + void process(js.Node node, BufferedCodeOutput code) {
|
| + processor.process(node, code);
|
| + wrapper.registerProcess(
|
| + node, code, codePositions, nodeToSourceLocationsMap);
|
| + }
|
| +}
|
| +
|
| +/// Information recording for a use of [SourceInformationProcessor].
|
| +class RecordedSourceInformationProcess {
|
| + final js.Node root;
|
| + final String code;
|
| + final CodePositionRecorder codePositions;
|
| + final LocationMap nodeToSourceLocationsMap;
|
| +
|
| + RecordedSourceInformationProcess(
|
| + this.root,
|
| + this.code,
|
| + this.codePositions,
|
| + this.nodeToSourceLocationsMap);
|
| +}
|
| +
|
| +
|
| +/// A wrapper of [JavaScriptSourceInformationStrategy] that records
|
| +/// [RecordedSourceInformationProcess].
|
| +class RecordingSourceInformationStrategy
|
| + extends JavaScriptSourceInformationStrategy {
|
| + final JavaScriptSourceInformationStrategy strategy;
|
| + final Map<RecordedSourceInformationProcess, js.Node> processMap =
|
| + <RecordedSourceInformationProcess, js.Node>{};
|
| + final Map<js.Node, RecordedSourceInformationProcess> nodeMap =
|
| + <js.Node, RecordedSourceInformationProcess>{};
|
| +
|
| + RecordingSourceInformationStrategy(this.strategy);
|
| +
|
| + @override
|
| + SourceInformationBuilder createBuilderForContext(AstElement element) {
|
| + return strategy.createBuilderForContext(element);
|
| + }
|
| +
|
| + @override
|
| + SourceInformationProcessor createProcessor(SourceMapper sourceMapper) {
|
| + LocationMap nodeToSourceLocationsMap =
|
| + new _LocationRecorder();
|
| + CodePositionRecorder codePositions = new CodePositionRecorder();
|
| + return new RecordingSourceInformationProcessor(
|
| + this,
|
| + strategy.createProcessor(new RecordingSourceMapper(
|
| + sourceMapper, nodeToSourceLocationsMap)),
|
| + codePositions, nodeToSourceLocationsMap);
|
| + }
|
| +
|
| + void registerProcess(js.Node root,
|
| + BufferedCodeOutput code,
|
| + CodePositionRecorder codePositions,
|
| + LocationMap nodeToSourceLocationsMap) {
|
| + RecordedSourceInformationProcess subProcess =
|
| + new RecordedSourceInformationProcess(
|
| + root, code.getText(), codePositions, nodeToSourceLocationsMap);
|
| + processMap[subProcess] = root;
|
| + }
|
| +
|
| + RecordedSourceInformationProcess subProcessForNode(js.Node node) {
|
| + return nodeMap.putIfAbsent(node, () {
|
| + for (RecordedSourceInformationProcess subProcess in processMap.keys) {
|
| + js.Node root = processMap[subProcess];
|
| + FindVisitor visitor = new FindVisitor(node);
|
| + root.accept(visitor);
|
| + if (visitor.found) {
|
| + return new RecordedSourceInformationProcess(
|
| + node,
|
| + subProcess.code,
|
| + subProcess.codePositions,
|
| + new _FilteredLocationMap(
|
| + visitor.nodes, subProcess.nodeToSourceLocationsMap));
|
| + }
|
| + return null;
|
| + }
|
| + });
|
| + }
|
| +}
|
| +
|
| +/// Visitor that collects all nodes that are within a function. Used by the
|
| +/// [RecordingSourceInformationStrategy] to filter what is recorded in a
|
| +/// [RecordedSourceInformationProcess].
|
| +class FindVisitor extends js.BaseVisitor {
|
| + final js.Node soughtNode;
|
| + bool found = false;
|
| + bool add = false;
|
| + final Set<js.Node> nodes = new Set<js.Node>();
|
| +
|
| + FindVisitor(this.soughtNode);
|
| +
|
| + visitNode(js.Node node) {
|
| + if (node == soughtNode) {
|
| + found = true;
|
| + add = true;
|
| + }
|
| + if (add) {
|
| + nodes.add(node);
|
| + }
|
| + node.visitChildren(this);
|
| + if (node == soughtNode) {
|
| + add = false;
|
| + }
|
| + }
|
| +}
|
| +
|
| +const String USE_NEW_SOURCE_INFO = '--use-new-source-info';
|
| +const String DISABLE_INLINING = '--disable-inlining';
|
| +
|
| /// Processor that computes [SourceMapInfo] for the JavaScript compiled for a
|
| /// given Dart file.
|
| class SourceMapProcessor {
|
| @@ -112,11 +305,12 @@ class SourceMapProcessor {
|
| /// Computes the [SourceMapInfo] for the compiled elements.
|
| Future<List<SourceMapInfo>> process(
|
| List<String> options,
|
| - {bool verbose: true}) async {
|
| + {bool verbose: true,
|
| + bool perElement: true}) async {
|
| OutputProvider outputProvider = outputToFile
|
| - ? new OutputProvider()
|
| - : new CloningOutputProvider(targetUri, sourceMapFileUri);
|
| - if (options.contains('--use-new-source-info')) {
|
| + ? new CloningOutputProvider(targetUri, sourceMapFileUri)
|
| + : new OutputProvider();
|
| + if (options.contains(USE_NEW_SOURCE_INFO)) {
|
| if (verbose) print('Using the new source information system.');
|
| useNewSourceInfo = true;
|
| }
|
| @@ -125,7 +319,7 @@ class SourceMapProcessor {
|
| // TODO(johnniwinther): Use [verbose] to avoid showing diagnostics.
|
| options: ['--out=$targetUri', '--source-map=$sourceMapFileUri']
|
| ..addAll(options));
|
| - if (options.contains('--disable-inlining')) {
|
| + if (options.contains(DISABLE_INLINING)) {
|
| if (verbose) print('Inlining disabled');
|
| compiler.disableInlining = true;
|
| }
|
| @@ -133,30 +327,58 @@ class SourceMapProcessor {
|
| JavaScriptBackend backend = compiler.backend;
|
| var handler = compiler.handler;
|
| SourceFileProvider sourceFileProvider = handler.provider;
|
| - sourceFileManager = new ProviderSourceFileManager(sourceFileProvider);
|
| + sourceFileManager = new ProviderSourceFileManager(
|
| + sourceFileProvider,
|
| + outputProvider);
|
| + RecordingSourceInformationStrategy strategy =
|
| + new RecordingSourceInformationStrategy(backend.sourceInformationStrategy);
|
| + backend.sourceInformationStrategy = strategy;
|
| await compiler.run(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();
|
| + if (perElement) {
|
| + backend.generatedCode.forEach((Element element, js.Expression node) {
|
| + RecordedSourceInformationProcess subProcess =
|
| + strategy.subProcessForNode(node);
|
| + if (subProcess == null) {
|
| + // TODO(johnniwinther): Find out when this is happening and if it
|
| + // is benign. (Known to happen for `bool#fromString`)
|
| + print('No subProcess found for $element');
|
| + return;
|
| + }
|
| + LocationMap nodeMap = subProcess.nodeToSourceLocationsMap;
|
| + String code = subProcess.code;
|
| + CodePositionRecorder codePositions = subProcess.codePositions;
|
| + CodePointComputer visitor =
|
| + new CodePointComputer(sourceFileManager, code, nodeMap);
|
| + visitor.apply(node);
|
| + List<CodePoint> codePoints = visitor.codePoints;
|
| + infoList.add(new SourceMapInfo(
|
| + element, code, node,
|
| + codePoints,
|
| + codePositions/*strategy.codePositions*/,
|
| + nodeMap));
|
| + });
|
| + } else {
|
| + // TODO(johnniwinther): Supported multiple output units.
|
| + RecordedSourceInformationProcess process = strategy.processMap.keys.first;
|
| + js.Node node = strategy.processMap[process];
|
| + String code;
|
| + LocationMap nodeMap;
|
| + CodePositionRecorder codePositions;
|
| + nodeMap = process.nodeToSourceLocationsMap;
|
| + code = process.code;
|
| + codePositions = process.codePositions;
|
| CodePointComputer visitor =
|
| new CodePointComputer(sourceFileManager, code, nodeMap);
|
| visitor.apply(node);
|
| List<CodePoint> codePoints = visitor.codePoints;
|
| - infoList.add(new SourceMapInfo(element, code, node, codePoints, nodeMap));
|
| - });
|
| + infoList.add(new SourceMapInfo(
|
| + null, code, node,
|
| + codePoints,
|
| + codePositions,
|
| + nodeMap));
|
| + }
|
|
|
| return infoList;
|
| }
|
| @@ -167,19 +389,43 @@ class SourceMapInfo {
|
| final String name;
|
| final Element element;
|
| final String code;
|
| - final js.Expression node;
|
| + final js.Node node;
|
| final List<CodePoint> codePoints;
|
| - final NodeToSourceLocationsMap nodeMap;
|
| + final CodePositionMap jsCodePositions;
|
| + final LocationMap nodeMap;
|
|
|
| SourceMapInfo(
|
| - Element element, this.code, this.node, this.codePoints, this.nodeMap)
|
| - : this.name = computeElementNameForSourceMaps(element),
|
| + Element element,
|
| + this.code,
|
| + this.node,
|
| + this.codePoints,
|
| + this.jsCodePositions,
|
| + this.nodeMap)
|
| + : this.name =
|
| + element != null ? computeElementNameForSourceMaps(element) : '',
|
| this.element = element;
|
| +
|
| + String toString() {
|
| + return '$name:$element';
|
| + }
|
| }
|
|
|
| /// Collection of JavaScript nodes with their source mapped target offsets
|
| /// and source locations.
|
| -class NodeToSourceLocationsMap implements SourceMapper {
|
| +abstract class LocationMap {
|
| + Iterable<js.Node> get nodes;
|
| +
|
| + Map<int, List<SourceLocation>> operator[] (js.Node node);
|
| +
|
| + factory LocationMap.recorder() = _LocationRecorder;
|
| +
|
| + factory LocationMap.filter(Set<js.Node> nodes, LocationMap map) =
|
| + _FilteredLocationMap;
|
| +
|
| +}
|
| +
|
| +class _LocationRecorder
|
| + implements SourceMapper, LocationMap {
|
| final Map<js.Node, Map<int, List<SourceLocation>>> _nodeMap = {};
|
|
|
| @override
|
| @@ -196,11 +442,25 @@ class NodeToSourceLocationsMap implements SourceMapper {
|
| }
|
| }
|
|
|
| +class _FilteredLocationMap implements LocationMap {
|
| + final Set<js.Node> _nodes;
|
| + final LocationMap map;
|
| +
|
| + _FilteredLocationMap(this._nodes, this.map);
|
| +
|
| + Iterable<js.Node> get nodes => map.nodes.where((n) => _nodes.contains(n));
|
| +
|
| + Map<int, List<SourceLocation>> operator[] (js.Node node) {
|
| + return map[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;
|
| + final LocationMap nodeMap;
|
| List<CodePoint> codePoints = [];
|
|
|
| CodePointComputer(this.sourceFileManager, this.code, this.nodeMap);
|
|
|