18bc70b898
Bug: https://github.com/dart-lang/sdk/issues/44569 Change-Id: Iacb5553755af38e931431cb5aa4e3495cd34539f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/190602 Reviewed-by: Sigmund Cherem <sigmund@google.com> Commit-Queue: Samuel Rawlins <srawlins@google.com>
628 lines
20 KiB
Dart
628 lines
20 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.
|
|
|
|
// @dart = 2.7
|
|
|
|
library sourcemap.helper;
|
|
|
|
import 'dart:async';
|
|
import 'dart:io';
|
|
import 'package:compiler/compiler_new.dart';
|
|
import 'package:compiler/src/apiimpl.dart' as api;
|
|
import 'package:compiler/src/commandline_options.dart';
|
|
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/js_strategy.dart';
|
|
import 'package:compiler/src/source_file_provider.dart';
|
|
import '../../helpers/memory_compiler.dart';
|
|
|
|
class SourceFileSink implements OutputSink {
|
|
final String filename;
|
|
StringBuffer sb = new StringBuffer();
|
|
SourceFile sourceFile;
|
|
|
|
SourceFileSink(this.filename);
|
|
|
|
@override
|
|
void add(String event) {
|
|
sb.write(event);
|
|
}
|
|
|
|
@override
|
|
void close() {
|
|
sourceFile = new StringSourceFile.fromName(filename, sb.toString());
|
|
}
|
|
}
|
|
|
|
class OutputProvider implements CompilerOutput {
|
|
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, OutputType type) {
|
|
String filename = '$name.$extension';
|
|
SourceFileSink sink = new SourceFileSink(filename);
|
|
Uri uri = Uri.parse(filename);
|
|
outputMap[uri] = sink;
|
|
return sink;
|
|
}
|
|
|
|
@override
|
|
OutputSink createOutputSink(String name, String extension, OutputType type) {
|
|
return createSourceFileSink(name, extension, type);
|
|
}
|
|
|
|
@override
|
|
BinaryOutputSink createBinarySink(Uri uri) =>
|
|
throw new UnsupportedError("OutputProvider.createBinarySink");
|
|
}
|
|
|
|
class CloningOutputProvider extends OutputProvider {
|
|
RandomAccessFileOutputProvider outputProvider;
|
|
|
|
CloningOutputProvider(Uri jsUri, Uri jsMapUri)
|
|
: outputProvider = new RandomAccessFileOutputProvider(jsUri, jsMapUri);
|
|
|
|
@override
|
|
OutputSink createOutputSink(String name, String extension, OutputType type) {
|
|
OutputSink output = outputProvider.createOutputSink(name, extension, type);
|
|
return new CloningOutputSink(
|
|
[output, createSourceFileSink(name, extension, type)]);
|
|
}
|
|
|
|
@override
|
|
BinaryOutputSink createBinarySink(Uri uri) =>
|
|
throw new UnsupportedError("CloningOutputProvider.createBinarySink");
|
|
}
|
|
|
|
abstract class SourceFileManager {
|
|
SourceFile getSourceFile(var uri);
|
|
}
|
|
|
|
class ProviderSourceFileManager implements SourceFileManager {
|
|
final SourceFileProvider sourceFileProvider;
|
|
final OutputProvider outputProvider;
|
|
|
|
ProviderSourceFileManager(this.sourceFileProvider, this.outputProvider);
|
|
|
|
@override
|
|
SourceFile getSourceFile(uri) {
|
|
SourceFile sourceFile = sourceFileProvider.getUtf8SourceFile(uri);
|
|
sourceFile ??= sourceFileProvider.autoReadFromFile(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);
|
|
|
|
@override
|
|
void exitNode(
|
|
js.Node node, 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 RecordingSourceMapperProvider implements SourceMapperProvider {
|
|
final SourceMapperProvider sourceMapperProvider;
|
|
final _LocationRecorder nodeToSourceLocationsMap;
|
|
|
|
RecordingSourceMapperProvider(
|
|
this.sourceMapperProvider, this.nodeToSourceLocationsMap);
|
|
|
|
@override
|
|
SourceMapper createSourceMapper(String name) {
|
|
return new 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 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(MemberEntity member) {
|
|
return strategy.createBuilderForContext(member);
|
|
}
|
|
|
|
@override
|
|
SourceInformationProcessor createProcessor(
|
|
SourceMapperProvider provider, SourceInformationReader reader) {
|
|
LocationMap nodeToSourceLocationsMap = new _LocationRecorder();
|
|
CodePositionRecorder codePositions = new CodePositionRecorder();
|
|
return new RecordingSourceInformationProcessor(
|
|
this,
|
|
strategy.createProcessor(
|
|
new RecordingSourceMapperProvider(
|
|
provider, nodeToSourceLocationsMap),
|
|
reader),
|
|
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;
|
|
}
|
|
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);
|
|
|
|
@override
|
|
visitNode(js.Node node) {
|
|
if (node == soughtNode) {
|
|
found = true;
|
|
add = true;
|
|
}
|
|
if (add) {
|
|
nodes.add(node);
|
|
}
|
|
node.visitChildren(this);
|
|
if (node == soughtNode) {
|
|
add = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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 [uri].
|
|
SourceMapProcessor(Uri uri, {this.outputToFile: false}) {
|
|
inputUri = Uri.base.resolveUri(uri);
|
|
jsPath = 'out.js';
|
|
targetUri = Uri.base.resolve(jsPath);
|
|
sourceMapFileUri = Uri.base.resolve('${jsPath}.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
|
|
? new CloningOutputProvider(targetUri, sourceMapFileUri)
|
|
: new OutputProvider();
|
|
if (options.contains(Flags.useNewSourceInfo)) {
|
|
if (verbose) print('Using the new 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 =
|
|
new ProviderSourceFileManager(sourceFileProvider, outputProvider);
|
|
RecordingSourceInformationStrategy strategy =
|
|
new RecordingSourceInformationStrategy(
|
|
backendStrategy.sourceInformationStrategy);
|
|
backendStrategy.sourceInformationStrategy = strategy;
|
|
});
|
|
if (!result.isSuccess) {
|
|
throw "Compilation failed.";
|
|
}
|
|
|
|
api.CompilerImpl compiler = result.compiler;
|
|
JsBackendStrategy backendStrategy = compiler.backendStrategy;
|
|
RecordingSourceInformationStrategy strategy =
|
|
backendStrategy.sourceInformationStrategy;
|
|
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 =
|
|
new CodePointComputer(sourceFileManager, code, nodeMap);
|
|
new JavaScriptTracer(
|
|
codePositions, const SourceInformationReader(), [visitor])
|
|
.apply(node);
|
|
List<CodePoint> codePoints = visitor.codePoints;
|
|
elementSourceMapInfos[element] = new 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 =
|
|
new CodePointComputer(sourceFileManager, code, nodeMap);
|
|
new JavaScriptTracer(
|
|
codePositions, const SourceInformationReader(), [visitor])
|
|
.apply(node);
|
|
List<CodePoint> codePoints = visitor.codePoints;
|
|
mainSourceMapInfo = new SourceMapInfo(
|
|
null, code, node, codePoints, codePositions, nodeMap);
|
|
}
|
|
|
|
return new SourceMaps(
|
|
compiler, sourceFileManager, mainSourceMapInfo, elementSourceMapInfos);
|
|
}
|
|
}
|
|
|
|
class SourceMaps {
|
|
final api.CompilerImpl 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(MemberEntity element, this.code, this.node, this.codePoints,
|
|
this.jsCodePositions, this.nodeMap)
|
|
: this.name =
|
|
element != null ? computeElementNameForSourceMaps(element) : '',
|
|
this.element = 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 = 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;
|
|
}
|
|
|
|
/// 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) {
|
|
SourceInformation sourceInformation = node.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(var uri) {
|
|
Uri absoluteUri;
|
|
if (uri is Uri) {
|
|
absoluteUri = base.resolveUri(uri);
|
|
} else {
|
|
absoluteUri = base.resolve(uri);
|
|
}
|
|
return sourceFiles.putIfAbsent(absoluteUri, () {
|
|
String text = new File.fromUri(absoluteUri).readAsStringSync();
|
|
return new StringSourceFile.fromUri(absoluteUri, text);
|
|
});
|
|
}
|
|
}
|