6d347961b2
Also simplify use of JavaScriptPrintingOptions. Change-Id: Icdb603edd76b71dbc4a5d913407b96e5e9589265 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/452223 Reviewed-by: Mayank Patke <fishythefish@google.com> Commit-Queue: Stephen Adams <sra@google.com>
837 lines
23 KiB
Dart
837 lines
23 KiB
Dart
// 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 'dart:io';
|
|
import 'package:compiler/compiler_api.dart' as api;
|
|
import 'package:compiler/src/commandline_options.dart';
|
|
import 'package:compiler/src/compiler.dart' show Compiler;
|
|
import 'package:compiler/src/elements/entities.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';
|
|
import 'package:compiler/src/js_model/element_map.dart';
|
|
import 'package:compiler/src/js_model/js_strategy.dart';
|
|
import 'package:compiler/src/source_file_provider.dart';
|
|
import 'package:compiler/src/util/memory_compiler.dart';
|
|
|
|
class SourceFileSink implements api.OutputSink {
|
|
final String filename;
|
|
StringBuffer sb = StringBuffer();
|
|
late final SourceFile sourceFile;
|
|
|
|
SourceFileSink(this.filename);
|
|
|
|
@override
|
|
void add(String event) {
|
|
sb.write(event);
|
|
}
|
|
|
|
@override
|
|
void close() {
|
|
sourceFile = StringSourceFile.fromName(filename, sb.toString());
|
|
}
|
|
}
|
|
|
|
class OutputProvider implements api.CompilerOutput {
|
|
Map<Uri, SourceFileSink> outputMap = <Uri, SourceFileSink>{};
|
|
|
|
SourceFile? getSourceFile(Uri uri) {
|
|
SourceFileSink? sink = outputMap[uri];
|
|
return sink?.sourceFile;
|
|
}
|
|
|
|
SourceFileSink createSourceFileSink(
|
|
String name,
|
|
String extension,
|
|
api.OutputType type,
|
|
) {
|
|
String filename = '$name.$extension';
|
|
SourceFileSink sink = SourceFileSink(filename);
|
|
Uri uri = Uri.parse(filename);
|
|
outputMap[uri] = sink;
|
|
return sink;
|
|
}
|
|
|
|
@override
|
|
api.OutputSink createOutputSink(
|
|
String name,
|
|
String extension,
|
|
api.OutputType type,
|
|
) {
|
|
return createSourceFileSink(name, extension, type);
|
|
}
|
|
|
|
@override
|
|
api.BinaryOutputSink createBinarySink(Uri uri) =>
|
|
throw UnsupportedError("OutputProvider.createBinarySink");
|
|
}
|
|
|
|
class CloningOutputProvider extends OutputProvider {
|
|
RandomAccessFileOutputProvider outputProvider;
|
|
|
|
CloningOutputProvider(Uri jsUri, Uri jsMapUri)
|
|
: outputProvider = RandomAccessFileOutputProvider(
|
|
jsUri,
|
|
jsMapUri,
|
|
onInfo: _ignore,
|
|
onFailure: _fail,
|
|
);
|
|
|
|
static void _ignore(String message) {}
|
|
|
|
static Never _fail(String message) => throw StateError('unreachable');
|
|
|
|
@override
|
|
api.OutputSink createOutputSink(
|
|
String name,
|
|
String extension,
|
|
api.OutputType type,
|
|
) {
|
|
api.OutputSink output = outputProvider.createOutputSink(
|
|
name,
|
|
extension,
|
|
type,
|
|
);
|
|
return CloningOutputSink([
|
|
output,
|
|
createSourceFileSink(name, extension, type),
|
|
]);
|
|
}
|
|
|
|
@override
|
|
api.BinaryOutputSink createBinarySink(Uri uri) =>
|
|
throw UnsupportedError("CloningOutputProvider.createBinarySink");
|
|
}
|
|
|
|
abstract class SourceFileManager {
|
|
SourceFile? getSourceFile(Object? uri);
|
|
}
|
|
|
|
class ProviderSourceFileManager implements SourceFileManager {
|
|
final SourceFileProvider sourceFileProvider;
|
|
final OutputProvider outputProvider;
|
|
|
|
ProviderSourceFileManager(this.sourceFileProvider, this.outputProvider);
|
|
|
|
@override
|
|
SourceFile? getSourceFile(covariant Uri? uri) {
|
|
if (uri == null) return null;
|
|
return (sourceFileProvider.readUtf8FromFileSyncForTesting(uri) ??
|
|
outputProvider.getSourceFile(uri))
|
|
as SourceFile?;
|
|
}
|
|
}
|
|
|
|
class RecordingPrintingContext extends LenientPrintingContext {
|
|
CodePositionListener listener;
|
|
Map<js.Node, CodePosition> codePositions = <js.Node, CodePosition>{};
|
|
|
|
RecordingPrintingContext(this.listener);
|
|
|
|
@override
|
|
void exitNode(
|
|
js.Node node,
|
|
int startPosition,
|
|
int endPosition,
|
|
int? closingPosition,
|
|
) {
|
|
codePositions[node] = CodePosition(
|
|
startPosition,
|
|
endPosition,
|
|
closingPosition,
|
|
);
|
|
listener.onPositions(node, startPosition, endPosition, closingPosition);
|
|
}
|
|
}
|
|
|
|
/// A [SourceMapper] that records the source locations on each node.
|
|
class RecordingSourceMapperProvider implements SourceMapperProvider {
|
|
final SourceMapperProvider sourceMapperProvider;
|
|
final _LocationRecorder nodeToSourceLocationsMap;
|
|
|
|
RecordingSourceMapperProvider(
|
|
this.sourceMapperProvider,
|
|
this.nodeToSourceLocationsMap,
|
|
);
|
|
|
|
@override
|
|
SourceMapper createSourceMapper(String name) {
|
|
return RecordingSourceMapper(
|
|
sourceMapperProvider.createSourceMapper(name),
|
|
nodeToSourceLocationsMap,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 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);
|
|
}
|
|
|
|
@override
|
|
void registerPush(
|
|
int codeOffset,
|
|
SourceLocation? sourceLocation,
|
|
String inlinedMethodName,
|
|
) {
|
|
sourceMapper.registerPush(codeOffset, sourceLocation, inlinedMethodName);
|
|
}
|
|
|
|
@override
|
|
void registerPop(int codeOffset, {bool isEmpty = false}) {
|
|
sourceMapper.registerPop(codeOffset, isEmpty: isEmpty);
|
|
}
|
|
}
|
|
|
|
/// A wrapper of [SourceInformationProcessor] that records source locations and
|
|
/// code positions.
|
|
class RecordingSourceInformationProcessor extends SourceInformationProcessor {
|
|
final RecordingSourceInformationStrategy wrapper;
|
|
final SourceInformationProcessor processor;
|
|
final CodePositionRecorder codePositions;
|
|
final LocationMap nodeToSourceLocationsMap;
|
|
|
|
RecordingSourceInformationProcessor(
|
|
this.wrapper,
|
|
this.processor,
|
|
this.codePositions,
|
|
this.nodeToSourceLocationsMap,
|
|
);
|
|
|
|
@override
|
|
void onStartPosition(js.Node node, int startPosition) {
|
|
processor.onStartPosition(node, startPosition);
|
|
}
|
|
|
|
@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 = {};
|
|
|
|
RecordingSourceInformationStrategy(this.strategy);
|
|
|
|
@override
|
|
void onElementMapAvailable(JsToElementMap elementMap) {
|
|
strategy.onElementMapAvailable(elementMap);
|
|
}
|
|
|
|
@override
|
|
SourceInformationBuilder createBuilderForContext(MemberEntity member) {
|
|
return strategy.createBuilderForContext(member);
|
|
}
|
|
|
|
@override
|
|
SourceInformationProcessor createProcessor(
|
|
SourceMapperProvider provider,
|
|
SourceInformationReader reader,
|
|
) {
|
|
final nodeToSourceLocationsMap = _LocationRecorder();
|
|
final codePositions = CodePositionRecorder();
|
|
return RecordingSourceInformationProcessor(
|
|
this,
|
|
strategy.createProcessor(
|
|
RecordingSourceMapperProvider(provider, nodeToSourceLocationsMap),
|
|
reader,
|
|
),
|
|
codePositions,
|
|
nodeToSourceLocationsMap,
|
|
);
|
|
}
|
|
|
|
void registerProcess(
|
|
js.Node root,
|
|
BufferedCodeOutput code,
|
|
CodePositionRecorder codePositions,
|
|
LocationMap nodeToSourceLocationsMap,
|
|
) {
|
|
RecordedSourceInformationProcess subProcess =
|
|
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 = FindVisitor(node);
|
|
root.accept(visitor);
|
|
if (visitor.found) {
|
|
return RecordedSourceInformationProcess(
|
|
node,
|
|
subProcess.code,
|
|
subProcess.codePositions,
|
|
_FilteredLocationMap(
|
|
visitor.nodes,
|
|
subProcess.nodeToSourceLocationsMap,
|
|
),
|
|
);
|
|
}
|
|
return null;
|
|
}
|
|
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.BaseVisitorVoid {
|
|
final js.Node soughtNode;
|
|
bool found = false;
|
|
bool add = false;
|
|
final Set<js.Node> nodes = Set<js.Node>();
|
|
|
|
FindVisitor(this.soughtNode);
|
|
|
|
@override
|
|
void visitNode(js.Node node) {
|
|
if (node == soughtNode) {
|
|
found = true;
|
|
add = true;
|
|
}
|
|
if (add) {
|
|
nodes.add(node);
|
|
}
|
|
node.visitChildren(this);
|
|
if (node == soughtNode) {
|
|
add = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
class HelperOnlinePositionSourceInformationStrategy
|
|
implements JavaScriptSourceInformationStrategy {
|
|
final List<TraceListener> listeners;
|
|
HelperOnlinePositionSourceInformationStrategy(this.listeners);
|
|
|
|
@override
|
|
SourceInformationProcessor createProcessor(
|
|
SourceMapperProvider provider,
|
|
SourceInformationReader reader,
|
|
) {
|
|
return OnlineSourceInformationProcessor(provider, reader, listeners);
|
|
}
|
|
|
|
@override
|
|
void onComplete() {}
|
|
|
|
@override
|
|
SourceInformation buildSourceMappedMarker() {
|
|
return const SourceMappedMarker();
|
|
}
|
|
|
|
@override
|
|
SourceInformationBuilder createBuilderForContext(
|
|
covariant MemberEntity member,
|
|
) {
|
|
throw UnimplementedError();
|
|
}
|
|
|
|
@override
|
|
void onElementMapAvailable(JsToElementMap elementMap) {}
|
|
}
|
|
|
|
/// 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.
|
|
late final SourceFileManager sourceFileManager;
|
|
|
|
/// Creates a processor for the Dart file [uri].
|
|
SourceMapProcessor(Uri uri, {this.outputToFile = false})
|
|
: inputUri = Uri.base.resolveUri(uri),
|
|
jsPath = 'out.js',
|
|
targetUri = Uri.base.resolve('out.js'),
|
|
sourceMapFileUri = Uri.base.resolve('out.js.map');
|
|
|
|
/// Computes the [SourceMapInfo] for the compiled elements.
|
|
Future<SourceMaps> process(
|
|
List<String> options, {
|
|
bool verbose = true,
|
|
bool perElement = true,
|
|
bool forMain = false,
|
|
}) async {
|
|
OutputProvider outputProvider = outputToFile
|
|
? CloningOutputProvider(targetUri, sourceMapFileUri)
|
|
: OutputProvider();
|
|
if (options.contains(Flags.useNewSourceInfo)) {
|
|
if (verbose) print('Using the source information system.');
|
|
}
|
|
if (options.contains(Flags.disableInlining)) {
|
|
if (verbose) print('Inlining disabled');
|
|
}
|
|
CompilationResult result = await runCompiler(
|
|
entryPoint: inputUri,
|
|
outputProvider: outputProvider,
|
|
// TODO(johnniwinther): Use [verbose] to avoid showing diagnostics.
|
|
options: ['--out=$targetUri', '--source-map=$sourceMapFileUri']
|
|
..addAll(options),
|
|
beforeRun: (compiler) {
|
|
JsBackendStrategy backendStrategy = compiler.backendStrategy;
|
|
dynamic handler = compiler.handler;
|
|
SourceFileProvider sourceFileProvider = handler.provider;
|
|
sourceFileManager = ProviderSourceFileManager(
|
|
sourceFileProvider,
|
|
outputProvider,
|
|
);
|
|
RecordingSourceInformationStrategy strategy =
|
|
RecordingSourceInformationStrategy(
|
|
backendStrategy.sourceInformationStrategy
|
|
as JavaScriptSourceInformationStrategy,
|
|
);
|
|
backendStrategy.sourceInformationStrategy = strategy;
|
|
},
|
|
);
|
|
if (!result.isSuccess) {
|
|
throw "Compilation failed.";
|
|
}
|
|
|
|
var compiler = result.compiler!;
|
|
JsBackendStrategy backendStrategy = compiler.backendStrategy;
|
|
final strategy =
|
|
backendStrategy.sourceInformationStrategy
|
|
as RecordingSourceInformationStrategy;
|
|
SourceMapInfo? mainSourceMapInfo;
|
|
Map<MemberEntity, SourceMapInfo> elementSourceMapInfos =
|
|
<MemberEntity, SourceMapInfo>{};
|
|
if (perElement) {
|
|
backendStrategy.generatedCode.forEach((_element, js.Expression node) {
|
|
MemberEntity element = _element;
|
|
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 = CodePointComputer(
|
|
sourceFileManager,
|
|
code,
|
|
nodeMap,
|
|
);
|
|
final outBuffer = NoopCodeOutput();
|
|
SourceInformationProcessor sourceInformationProcessor =
|
|
HelperOnlinePositionSourceInformationStrategy([
|
|
visitor,
|
|
]).createProcessor(
|
|
SourceMapperProviderImpl(outBuffer),
|
|
const SourceInformationReader(),
|
|
);
|
|
|
|
js.Dart2JSJavaScriptPrintingContext context =
|
|
js.Dart2JSJavaScriptPrintingContext(
|
|
null,
|
|
outBuffer,
|
|
sourceInformationProcessor,
|
|
const js.JavaScriptAnnotationMonitor(),
|
|
);
|
|
js.Printer printer = js.Printer(
|
|
const js.JavaScriptPrintingOptions(),
|
|
context,
|
|
);
|
|
printer.visit(node);
|
|
List<CodePoint> codePoints = visitor.codePoints;
|
|
elementSourceMapInfos[element] = SourceMapInfo(
|
|
element,
|
|
code,
|
|
node,
|
|
codePoints,
|
|
codePositions,
|
|
nodeMap,
|
|
);
|
|
});
|
|
}
|
|
if (forMain) {
|
|
// 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 = CodePointComputer(
|
|
sourceFileManager,
|
|
code,
|
|
nodeMap,
|
|
);
|
|
final outBuffer = NoopCodeOutput();
|
|
SourceInformationProcessor sourceInformationProcessor =
|
|
HelperOnlinePositionSourceInformationStrategy([
|
|
visitor,
|
|
]).createProcessor(
|
|
SourceMapperProviderImpl(outBuffer),
|
|
const SourceInformationReader(),
|
|
);
|
|
|
|
js.Dart2JSJavaScriptPrintingContext context =
|
|
js.Dart2JSJavaScriptPrintingContext(
|
|
null,
|
|
outBuffer,
|
|
sourceInformationProcessor,
|
|
const js.JavaScriptAnnotationMonitor(),
|
|
);
|
|
js.Printer printer = js.Printer(
|
|
const js.JavaScriptPrintingOptions(),
|
|
context,
|
|
);
|
|
printer.visit(node);
|
|
List<CodePoint> codePoints = visitor.codePoints;
|
|
mainSourceMapInfo = SourceMapInfo(
|
|
null,
|
|
code,
|
|
node,
|
|
codePoints,
|
|
codePositions,
|
|
nodeMap,
|
|
);
|
|
}
|
|
|
|
return SourceMaps(
|
|
compiler,
|
|
sourceFileManager,
|
|
mainSourceMapInfo,
|
|
elementSourceMapInfos,
|
|
);
|
|
}
|
|
}
|
|
|
|
class SourceMaps {
|
|
final Compiler compiler;
|
|
final SourceFileManager sourceFileManager;
|
|
// TODO(johnniwinther): Supported multiple output units.
|
|
final SourceMapInfo? mainSourceMapInfo;
|
|
final Map<MemberEntity, SourceMapInfo> elementSourceMapInfos;
|
|
|
|
SourceMaps(
|
|
this.compiler,
|
|
this.sourceFileManager,
|
|
this.mainSourceMapInfo,
|
|
this.elementSourceMapInfos,
|
|
);
|
|
}
|
|
|
|
/// Source mapping information for the JavaScript code of an [Element].
|
|
class SourceMapInfo {
|
|
final String? name;
|
|
final MemberEntity? element;
|
|
final String code;
|
|
final js.Node node;
|
|
final List<CodePoint> codePoints;
|
|
final CodePositionMap jsCodePositions;
|
|
final LocationMap nodeMap;
|
|
|
|
SourceMapInfo(
|
|
this.element,
|
|
this.code,
|
|
this.node,
|
|
this.codePoints,
|
|
this.jsCodePositions,
|
|
this.nodeMap,
|
|
) : this.name = element != null
|
|
? computeElementNameForSourceMaps(element)
|
|
: '';
|
|
|
|
@override
|
|
String toString() {
|
|
return '$name:$element';
|
|
}
|
|
}
|
|
|
|
/// Collection of JavaScript nodes with their source mapped target offsets
|
|
/// and source locations.
|
|
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
|
|
void register(js.Node node, int codeOffset, SourceLocation sourceLocation) {
|
|
_nodeMap
|
|
.putIfAbsent(node, () => {})
|
|
.putIfAbsent(codeOffset, () => [])
|
|
.add(sourceLocation);
|
|
}
|
|
|
|
@override
|
|
void registerPush(
|
|
int codeOffset,
|
|
SourceLocation? sourceLocation,
|
|
String inlinedMethodName,
|
|
) {}
|
|
|
|
@override
|
|
void registerPop(int codeOffset, {bool isEmpty = false}) {}
|
|
|
|
@override
|
|
Iterable<js.Node> get nodes => _nodeMap.keys;
|
|
|
|
@override
|
|
Map<int, List<SourceLocation>>? operator [](js.Node node) {
|
|
return _nodeMap[node];
|
|
}
|
|
}
|
|
|
|
class _FilteredLocationMap implements LocationMap {
|
|
final Set<js.Node> _nodes;
|
|
final LocationMap map;
|
|
|
|
_FilteredLocationMap(this._nodes, this.map);
|
|
|
|
@override
|
|
Iterable<js.Node> get nodes => map.nodes.where((n) => _nodes.contains(n));
|
|
|
|
@override
|
|
Map<int, List<SourceLocation>>? operator [](js.Node node) {
|
|
return map[node];
|
|
}
|
|
}
|
|
|
|
/// Visitor that computes the [CodePoint]s for source mapping locations.
|
|
class CodePointComputer extends TraceListener {
|
|
final SourceFileManager sourceFileManager;
|
|
final String code;
|
|
final LocationMap nodeMap;
|
|
List<CodePoint> codePoints = [];
|
|
|
|
CodePointComputer(this.sourceFileManager, this.code, this.nodeMap);
|
|
|
|
String nodeToString(js.Node node) {
|
|
js.JavaScriptPrintingOptions options = js.JavaScriptPrintingOptions(
|
|
minify: true,
|
|
preferSemicolonToNewlineInMinifiedOutput: true,
|
|
);
|
|
LenientPrintingContext printingContext = LenientPrintingContext();
|
|
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;
|
|
}
|
|
|
|
/// Called when [node] defines a step of the given [kind] at the given
|
|
/// [offset] when the generated JavaScript code.
|
|
@override
|
|
void onStep(js.Node node, Offset offset, StepKind kind) {
|
|
if (kind == StepKind.access) return;
|
|
register(kind, node);
|
|
}
|
|
|
|
void register(StepKind kind, js.Node node, {bool expectInfo = true}) {
|
|
String dartCodeFromSourceLocation(SourceLocation sourceLocation) {
|
|
SourceFile? sourceFile = sourceFileManager.getSourceFile(
|
|
sourceLocation.sourceUri,
|
|
);
|
|
if (sourceFile == null) {
|
|
return sourceLocation.shortText;
|
|
}
|
|
return sourceFile.kernelSource
|
|
.getTextLine(sourceLocation.line)!
|
|
.substring(sourceLocation.column - 1)
|
|
.trim();
|
|
}
|
|
|
|
void addLocation(
|
|
SourceLocation? sourceLocation,
|
|
String jsCode,
|
|
int? targetOffset,
|
|
) {
|
|
if (sourceLocation == null) {
|
|
if (expectInfo) {
|
|
final sourceInformation =
|
|
node.sourceInformation as SourceInformation?;
|
|
SourceLocation? sourceLocation;
|
|
String? dartCode;
|
|
if (sourceInformation != null) {
|
|
sourceLocation = sourceInformation.sourceLocations.first;
|
|
dartCode = dartCodeFromSourceLocation(sourceLocation);
|
|
}
|
|
codePoints.add(
|
|
new CodePoint(
|
|
kind,
|
|
jsCode,
|
|
targetOffset,
|
|
sourceLocation,
|
|
dartCode,
|
|
isMissing: true,
|
|
),
|
|
);
|
|
}
|
|
} else {
|
|
codePoints.add(
|
|
new CodePoint(
|
|
kind,
|
|
jsCode,
|
|
targetOffset,
|
|
sourceLocation,
|
|
dartCodeFromSourceLocation(sourceLocation),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
Map<int, List<SourceLocation>>? locationMap = nodeMap[node];
|
|
if (locationMap == null) {
|
|
addLocation(null, nodeToString(node), null);
|
|
} else {
|
|
locationMap.forEach((int targetOffset, List<SourceLocation> locations) {
|
|
String jsCode = nodeToString(node);
|
|
for (SourceLocation location in locations) {
|
|
addLocation(location, jsCode, targetOffset);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A JavaScript code point and its mapped dart source location.
|
|
class CodePoint {
|
|
final StepKind kind;
|
|
final String jsCode;
|
|
final int? targetOffset;
|
|
final SourceLocation? sourceLocation;
|
|
final String? dartCode;
|
|
final bool isMissing;
|
|
|
|
CodePoint(
|
|
this.kind,
|
|
this.jsCode,
|
|
this.targetOffset,
|
|
this.sourceLocation,
|
|
this.dartCode, {
|
|
this.isMissing = false,
|
|
});
|
|
|
|
@override
|
|
String toString() {
|
|
return 'CodePoint[kind=$kind,js=$jsCode,dart=$dartCode,'
|
|
'location=$sourceLocation]';
|
|
}
|
|
}
|
|
|
|
class IOSourceFileManager implements SourceFileManager {
|
|
final Uri base;
|
|
|
|
Map<Uri, SourceFile> sourceFiles = <Uri, SourceFile>{};
|
|
|
|
IOSourceFileManager(this.base);
|
|
|
|
@override
|
|
SourceFile getSourceFile(Object? uri) {
|
|
Uri absoluteUri;
|
|
if (uri is Uri) {
|
|
absoluteUri = base.resolveUri(uri);
|
|
} else {
|
|
absoluteUri = base.resolve(uri as String);
|
|
}
|
|
return sourceFiles.putIfAbsent(absoluteUri, () {
|
|
String text = File.fromUri(absoluteUri).readAsStringSync();
|
|
return StringSourceFile.fromUri(absoluteUri, text);
|
|
});
|
|
}
|
|
}
|