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); |