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 USE_NEW_SOURCE_INFO = true; | |
floitsch
2015/06/29 08:55:52
If we can assign to it, it's not a constant and sh
Johnni Winther
2015/06/29 12:36:30
Done.
| |
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 |