OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library sourcemap.helper; |
| 6 |
| 7 import 'dart:async'; |
| 8 import 'package:compiler/src/apiimpl.dart' as api; |
| 9 import 'package:compiler/src/dart2jslib.dart' show NullSink; |
| 10 import "package:compiler/src/elements/elements.dart"; |
| 11 import 'package:compiler/src/filenames.dart'; |
| 12 import 'package:compiler/src/io/source_file.dart'; |
| 13 import 'package:compiler/src/io/source_information.dart'; |
| 14 import 'package:compiler/src/js/js.dart' as js; |
| 15 import 'package:compiler/src/js/js_debug.dart'; |
| 16 import 'package:compiler/src/js/js_source_mapping.dart'; |
| 17 import 'package:compiler/src/js_backend/js_backend.dart'; |
| 18 import 'package:compiler/src/source_file_provider.dart'; |
| 19 import '../memory_compiler.dart'; |
| 20 import '../output_collector.dart'; |
| 21 |
| 22 class OutputProvider { |
| 23 BufferedEventSink jsMapOutput; |
| 24 |
| 25 EventSink<String> call(String name, String extension) { |
| 26 if (extension == 'js.map') { |
| 27 return jsMapOutput = new BufferedEventSink(); |
| 28 } |
| 29 return new NullSink('$name.$extension'); |
| 30 } |
| 31 } |
| 32 |
| 33 class CloningOutputProvider extends OutputProvider { |
| 34 RandomAccessFileOutputProvider outputProvider; |
| 35 |
| 36 CloningOutputProvider(Uri jsUri, Uri jsMapUri) |
| 37 : outputProvider = new RandomAccessFileOutputProvider(jsUri, jsMapUri); |
| 38 |
| 39 EventSink<String> call(String name, String extension) { |
| 40 EventSink<String> output = outputProvider(name, extension); |
| 41 if (extension == 'js.map') { |
| 42 output = new CloningEventSink( |
| 43 [output, jsMapOutput = new BufferedEventSink()]); |
| 44 } |
| 45 return output; |
| 46 } |
| 47 } |
| 48 |
| 49 abstract class SourceFileManager { |
| 50 SourceFile getSourceFile(var uri); |
| 51 } |
| 52 |
| 53 class ProviderSourceFileManager implements SourceFileManager { |
| 54 final SourceFileProvider sourceFileProvider; |
| 55 |
| 56 ProviderSourceFileManager(this.sourceFileProvider); |
| 57 |
| 58 @override |
| 59 SourceFile getSourceFile(uri) { |
| 60 return sourceFileProvider.getSourceFile(uri); |
| 61 } |
| 62 } |
| 63 |
| 64 class RecordingPrintingContext extends LenientPrintingContext { |
| 65 CodePositionListener listener; |
| 66 |
| 67 RecordingPrintingContext(this.listener); |
| 68 |
| 69 @override |
| 70 void exitNode(js.Node node, |
| 71 int startPosition, |
| 72 int endPosition, |
| 73 int closingPosition) { |
| 74 listener.onPositions( |
| 75 node, startPosition, endPosition, closingPosition); |
| 76 } |
| 77 } |
| 78 |
| 79 /// Processor that computes [SourceMapInfo] for the JavaScript compiled for a |
| 80 /// given Dart file. |
| 81 class SourceMapProcessor { |
| 82 /// If `true` the output from the compilation is written to files. |
| 83 final bool outputToFile; |
| 84 |
| 85 /// The [Uri] of the Dart entrypoint. |
| 86 Uri inputUri; |
| 87 |
| 88 /// The name of the JavaScript output file. |
| 89 String jsPath; |
| 90 |
| 91 /// The [Uri] of the JavaScript output file. |
| 92 Uri targetUri; |
| 93 |
| 94 /// The [Uri] of the JavaScript source map file. |
| 95 Uri sourceMapFileUri; |
| 96 |
| 97 /// The [SourceFileManager] created for the processing. |
| 98 SourceFileManager sourceFileManager; |
| 99 |
| 100 /// Creates a processor for the Dart file [filename]. |
| 101 SourceMapProcessor(String filename, {this.outputToFile: false}) { |
| 102 inputUri = Uri.base.resolve(nativeToUriPath(filename)); |
| 103 jsPath = 'out.js'; |
| 104 targetUri = Uri.base.resolve(jsPath); |
| 105 sourceMapFileUri = Uri.base.resolve('${jsPath}.map'); |
| 106 } |
| 107 |
| 108 /// Computes the [SourceMapInfo] for the compiled elements. |
| 109 Future<List<SourceMapInfo>> process(List<String> options) async { |
| 110 OutputProvider outputProvider = outputToFile |
| 111 ? new OutputProvider() |
| 112 : new CloningOutputProvider(targetUri, sourceMapFileUri); |
| 113 if (options.contains('--use-new-source-info')) { |
| 114 print('Using the new source information system.'); |
| 115 useNewSourceInfo = true; |
| 116 } |
| 117 api.Compiler compiler = await compilerFor({}, |
| 118 outputProvider: outputProvider, |
| 119 options: ['--out=$targetUri', '--source-map=$sourceMapFileUri'] |
| 120 ..addAll(options)); |
| 121 if (options.contains('--disable-inlining')) { |
| 122 print('Inlining disabled'); |
| 123 compiler.disableInlining = true; |
| 124 } |
| 125 |
| 126 JavaScriptBackend backend = compiler.backend; |
| 127 var handler = compiler.handler; |
| 128 SourceFileProvider sourceFileProvider = handler.provider; |
| 129 sourceFileManager = new ProviderSourceFileManager(sourceFileProvider); |
| 130 await compiler.runCompiler(inputUri); |
| 131 |
| 132 List<SourceMapInfo> infoList = <SourceMapInfo>[]; |
| 133 backend.generatedCode.forEach((Element element, js.Expression node) { |
| 134 js.JavaScriptPrintingOptions options = |
| 135 new js.JavaScriptPrintingOptions(); |
| 136 JavaScriptSourceInformationStrategy sourceInformationStrategy = |
| 137 compiler.backend.sourceInformationStrategy; |
| 138 NodeToSourceLocationsMap nodeMap = new NodeToSourceLocationsMap(); |
| 139 SourceInformationProcessor sourceInformationProcessor = |
| 140 sourceInformationStrategy.createProcessor(nodeMap); |
| 141 RecordingPrintingContext printingContext = |
| 142 new RecordingPrintingContext(sourceInformationProcessor); |
| 143 new js.Printer(options, printingContext).visit(node); |
| 144 sourceInformationProcessor.process(node); |
| 145 |
| 146 String code = printingContext.getText(); |
| 147 CodePointComputer visitor = |
| 148 new CodePointComputer(sourceFileManager, code, nodeMap); |
| 149 visitor.apply(node); |
| 150 List<CodePoint> codePoints = visitor.codePoints; |
| 151 infoList.add(new SourceMapInfo(element, code, node, codePoints, nodeMap)); |
| 152 }); |
| 153 |
| 154 return infoList; |
| 155 } |
| 156 } |
| 157 |
| 158 /// Source mapping information for the JavaScript code of an [Element]. |
| 159 class SourceMapInfo { |
| 160 final String name; |
| 161 final Element element; |
| 162 final String code; |
| 163 final js.Expression node; |
| 164 final List<CodePoint> codePoints; |
| 165 final NodeToSourceLocationsMap nodeMap; |
| 166 |
| 167 SourceMapInfo( |
| 168 Element element, this.code, this.node, this.codePoints, this.nodeMap) |
| 169 : this.name = computeElementNameForSourceMaps(element), |
| 170 this.element = element; |
| 171 } |
| 172 |
| 173 /// Collection of JavaScript nodes with their source mapped target offsets |
| 174 /// and source locations. |
| 175 class NodeToSourceLocationsMap implements SourceMapper { |
| 176 final Map<js.Node, Map<int, List<SourceLocation>>> _nodeMap = {}; |
| 177 |
| 178 @override |
| 179 void register(js.Node node, int codeOffset, SourceLocation sourceLocation) { |
| 180 _nodeMap.putIfAbsent(node, () => {}) |
| 181 .putIfAbsent(codeOffset, () => []) |
| 182 .add(sourceLocation); |
| 183 } |
| 184 |
| 185 Iterable<js.Node> get nodes => _nodeMap.keys; |
| 186 |
| 187 Map<int, List<SourceLocation>> operator[] (js.Node node) { |
| 188 return _nodeMap[node]; |
| 189 } |
| 190 } |
| 191 |
| 192 /// Visitor that computes the [CodePoint]s for source mapping locations. |
| 193 class CodePointComputer extends js.BaseVisitor { |
| 194 final SourceFileManager sourceFileManager; |
| 195 final String code; |
| 196 final NodeToSourceLocationsMap nodeMap; |
| 197 List<CodePoint> codePoints = []; |
| 198 |
| 199 CodePointComputer(this.sourceFileManager, this.code, this.nodeMap); |
| 200 |
| 201 String nodeToString(js.Node node) { |
| 202 js.JavaScriptPrintingOptions options = new js.JavaScriptPrintingOptions( |
| 203 shouldCompressOutput: true, |
| 204 preferSemicolonToNewlineInMinifiedOutput: true); |
| 205 LenientPrintingContext printingContext = new LenientPrintingContext(); |
| 206 new js.Printer(options, printingContext).visit(node); |
| 207 return printingContext.buffer.toString(); |
| 208 } |
| 209 |
| 210 String positionToString(int position) { |
| 211 String line = code.substring(position); |
| 212 int nl = line.indexOf('\n'); |
| 213 if (nl != -1) { |
| 214 line = line.substring(0, nl); |
| 215 } |
| 216 return line; |
| 217 } |
| 218 |
| 219 void register(String kind, js.Node node, {bool expectInfo: true}) { |
| 220 |
| 221 String dartCodeFromSourceLocation(SourceLocation sourceLocation) { |
| 222 SourceFile sourceFile = |
| 223 sourceFileManager.getSourceFile(sourceLocation.sourceUri); |
| 224 return sourceFile.getLineText(sourceLocation.line) |
| 225 .substring(sourceLocation.column).trim(); |
| 226 } |
| 227 |
| 228 void addLocation(SourceLocation sourceLocation, String jsCode) { |
| 229 if (sourceLocation == null) { |
| 230 if (expectInfo) { |
| 231 SourceInformation sourceInformation = node.sourceInformation; |
| 232 SourceLocation sourceLocation; |
| 233 String dartCode; |
| 234 if (sourceInformation != null) { |
| 235 sourceLocation = sourceInformation.sourceLocations.first; |
| 236 dartCode = dartCodeFromSourceLocation(sourceLocation); |
| 237 } |
| 238 codePoints.add(new CodePoint( |
| 239 kind, jsCode, sourceLocation, dartCode, isMissing: true)); |
| 240 } |
| 241 } else { |
| 242 codePoints.add(new CodePoint(kind, jsCode, sourceLocation, |
| 243 dartCodeFromSourceLocation(sourceLocation))); |
| 244 } |
| 245 } |
| 246 |
| 247 Map<int, List<SourceLocation>> locationMap = nodeMap[node]; |
| 248 if (locationMap == null) { |
| 249 addLocation(null, nodeToString(node)); |
| 250 } else { |
| 251 locationMap.forEach((int targetOffset, List<SourceLocation> locations) { |
| 252 String jsCode = positionToString(targetOffset); |
| 253 for (SourceLocation location in locations) { |
| 254 addLocation(location, jsCode); |
| 255 } |
| 256 }); |
| 257 } |
| 258 } |
| 259 |
| 260 void apply(js.Node node) { |
| 261 node.accept(this); |
| 262 } |
| 263 |
| 264 void visitNode(js.Node node) { |
| 265 register('${node.runtimeType}', node, expectInfo: false); |
| 266 super.visitNode(node); |
| 267 } |
| 268 |
| 269 @override |
| 270 void visitNew(js.New node) { |
| 271 node.arguments.forEach(apply); |
| 272 register('New', node); |
| 273 } |
| 274 |
| 275 @override |
| 276 void visitReturn(js.Return node) { |
| 277 if (node.value != null) { |
| 278 apply(node.value); |
| 279 } |
| 280 register('Return', node); |
| 281 } |
| 282 |
| 283 @override |
| 284 void visitCall(js.Call node) { |
| 285 apply(node.target); |
| 286 node.arguments.forEach(apply); |
| 287 register('Call (${node.target.runtimeType})', node); |
| 288 } |
| 289 |
| 290 @override |
| 291 void visitFun(js.Fun node) { |
| 292 node.visitChildren(this); |
| 293 register('Fun', node); |
| 294 } |
| 295 |
| 296 @override |
| 297 visitExpressionStatement(js.ExpressionStatement node) { |
| 298 node.visitChildren(this); |
| 299 } |
| 300 |
| 301 @override |
| 302 visitBinary(js.Binary node) { |
| 303 node.visitChildren(this); |
| 304 } |
| 305 |
| 306 @override |
| 307 visitAccess(js.PropertyAccess node) { |
| 308 node.visitChildren(this); |
| 309 } |
| 310 } |
| 311 |
| 312 /// A JavaScript code point and its mapped dart source location. |
| 313 class CodePoint { |
| 314 final String kind; |
| 315 final String jsCode; |
| 316 final SourceLocation sourceLocation; |
| 317 final String dartCode; |
| 318 final bool isMissing; |
| 319 |
| 320 CodePoint( |
| 321 this.kind, |
| 322 this.jsCode, |
| 323 this.sourceLocation, |
| 324 this.dartCode, |
| 325 {this.isMissing: false}); |
| 326 |
| 327 String toString() { |
| 328 return 'CodePoint[kind=$kind,js=$jsCode,dart=$dartCode,' |
| 329 'location=$sourceLocation]'; |
| 330 } |
| 331 } |
OLD | NEW |