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