OLD | NEW |
---|---|
1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 import 'dart:collection' show HashSet, Queue; | 5 import 'dart:collection' show HashSet, Queue; |
6 import 'dart:convert' show JSON; | |
7 import 'dart:io' show File; | |
6 import 'package:analyzer/dart/element/element.dart' show LibraryElement; | 8 import 'package:analyzer/dart/element/element.dart' show LibraryElement; |
7 import 'package:analyzer/analyzer.dart' | 9 import 'package:analyzer/analyzer.dart' |
8 show AnalysisError, CompilationUnit, ErrorSeverity; | 10 show AnalysisError, CompilationUnit, ErrorSeverity; |
9 import 'package:analyzer/file_system/file_system.dart' show ResourceProvider; | 11 import 'package:analyzer/file_system/file_system.dart' show ResourceProvider; |
10 import 'package:analyzer/src/generated/engine.dart' show AnalysisContext; | 12 import 'package:analyzer/src/generated/engine.dart' show AnalysisContext; |
11 import 'package:analyzer/src/generated/source.dart' show DartUriResolver; | 13 import 'package:analyzer/src/generated/source.dart' show DartUriResolver; |
12 import 'package:analyzer/src/generated/source_io.dart' | 14 import 'package:analyzer/src/generated/source_io.dart' |
13 show Source, SourceKind, UriResolver; | 15 show Source, SourceKind, UriResolver; |
14 import 'package:analyzer/src/summary/package_bundle_reader.dart' | 16 import 'package:analyzer/src/summary/package_bundle_reader.dart' |
15 show InSummarySource; | 17 show InSummarySource; |
16 import 'package:args/args.dart' show ArgParser, ArgResults; | 18 import 'package:args/args.dart' show ArgParser, ArgResults; |
17 import 'package:args/src/usage_exception.dart' show UsageException; | 19 import 'package:args/src/usage_exception.dart' show UsageException; |
18 import 'package:func/func.dart' show Func1; | 20 import 'package:func/func.dart' show Func1; |
19 import 'package:path/path.dart' as path; | 21 import 'package:path/path.dart' as path; |
22 import 'package:source_maps/source_maps.dart'; | |
20 | 23 |
21 import '../analyzer/context.dart' | 24 import '../analyzer/context.dart' |
22 show AnalyzerOptions, createAnalysisContextWithSources; | 25 show AnalyzerOptions, createAnalysisContextWithSources; |
23 import 'extension_types.dart' show ExtensionTypeSet; | 26 import '../js_ast/js_ast.dart' as JS; |
24 import 'code_generator.dart' show CodeGenerator; | 27 import 'code_generator.dart' show CodeGenerator; |
25 import 'error_helpers.dart' show errorSeverity, formatError, sortErrors; | 28 import 'error_helpers.dart' show errorSeverity, formatError, sortErrors; |
29 import 'extension_types.dart' show ExtensionTypeSet; | |
30 import 'js_names.dart' as JS; | |
31 import 'module_builder.dart' show lowerModuleFormat, ModuleFormat; | |
32 import 'source_map_printer.dart' show SourceMapPrintingContext; | |
26 | 33 |
27 /// Compiles a set of Dart files into a single JavaScript module. | 34 /// Compiles a set of Dart files into a single JavaScript module. |
28 /// | 35 /// |
29 /// For a single [BuildUnit] definition, this will produce a [JSModuleFile]. | 36 /// For a single [BuildUnit] definition, this will produce a [JSModuleFile]. |
30 /// Those objects are record types that record the data consumed and produced | 37 /// Those objects are record types that record the data consumed and produced |
31 /// for a single compile. | 38 /// for a single compile. |
32 /// | 39 /// |
33 /// This class exists to cache global state associated with a single in-memory | 40 /// This class exists to cache global state associated with a single in-memory |
34 /// AnalysisContext, such as information about extension types in the Dart SDK. | 41 /// AnalysisContext, such as information about extension types in the Dart SDK. |
35 /// It can be used once to produce a single module, or reused to save warm-up | 42 /// It can be used once to produce a single module, or reused to save warm-up |
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
128 | 135 |
129 sortErrors(context, errors); | 136 sortErrors(context, errors); |
130 var messages = <String>[]; | 137 var messages = <String>[]; |
131 for (var e in errors) { | 138 for (var e in errors) { |
132 var m = formatError(context, e); | 139 var m = formatError(context, e); |
133 if (m != null) messages.add(m); | 140 if (m != null) messages.add(m); |
134 } | 141 } |
135 | 142 |
136 if (!options.unsafeForceCompile && | 143 if (!options.unsafeForceCompile && |
137 errors.any((e) => errorSeverity(context, e) == ErrorSeverity.ERROR)) { | 144 errors.any((e) => errorSeverity(context, e) == ErrorSeverity.ERROR)) { |
138 return new JSModuleFile.invalid(unit.name, messages); | 145 return new JSModuleFile.invalid(unit.name, messages, options); |
139 } | 146 } |
140 | 147 |
141 var codeGenerator = new CodeGenerator(context, options, _extensionTypes); | 148 var codeGenerator = new CodeGenerator(context, options, _extensionTypes); |
142 return codeGenerator.compile(unit, trees, messages); | 149 return codeGenerator.compile(unit, trees, messages); |
143 } | 150 } |
144 } | 151 } |
145 | 152 |
146 enum ModuleFormat { es6, legacy, node } | |
147 | |
148 ModuleFormat parseModuleFormat(String s) => { | |
149 'es6': ModuleFormat.es6, | |
150 'node': ModuleFormat.node, | |
151 'legacy': ModuleFormat.legacy | |
152 }[s]; | |
153 | |
154 class CompilerOptions { | 153 class CompilerOptions { |
155 /// Whether to emit the source mapping file. | 154 /// Whether to emit the source mapping file. |
156 /// | 155 /// |
157 /// This supports debugging the original source code instead of the generated | 156 /// This supports debugging the original source code instead of the generated |
158 /// code. | 157 /// code. |
159 final bool sourceMap; | 158 final bool sourceMap; |
160 | 159 |
161 /// If [sourceMap] is emitted, this will emit a `sourceMappingUrl` comment | 160 /// If [sourceMap] is emitted, this will emit a `sourceMappingUrl` comment |
162 /// into the output JavaScript module. | 161 /// into the output JavaScript module. |
163 final bool sourceMapComment; | 162 final bool sourceMapComment; |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
202 /// Supporting the syntax: | 201 /// Supporting the syntax: |
203 /// * Chrome Canary (51) | 202 /// * Chrome Canary (51) |
204 /// * Firefox | 203 /// * Firefox |
205 /// | 204 /// |
206 /// Not yet supporting: | 205 /// Not yet supporting: |
207 /// * Atom (1.5.4) | 206 /// * Atom (1.5.4) |
208 /// * Electron (0.36.3) | 207 /// * Electron (0.36.3) |
209 // TODO(ochafik): Simplify this code when our target platforms catch up. | 208 // TODO(ochafik): Simplify this code when our target platforms catch up. |
210 final bool destructureNamedParams; | 209 final bool destructureNamedParams; |
211 | 210 |
212 /// Which module format to support. | |
213 /// Currently 'es6' and 'legacy' are supported. | |
214 final ModuleFormat moduleFormat; | |
215 | |
216 const CompilerOptions( | 211 const CompilerOptions( |
217 {this.sourceMap: true, | 212 {this.sourceMap: true, |
218 this.sourceMapComment: true, | 213 this.sourceMapComment: true, |
219 this.summarizeApi: true, | 214 this.summarizeApi: true, |
220 this.summaryExtension: 'sum', | 215 this.summaryExtension: 'sum', |
221 this.unsafeForceCompile: false, | 216 this.unsafeForceCompile: false, |
222 this.emitMetadata: false, | 217 this.emitMetadata: false, |
223 this.closure: false, | 218 this.closure: false, |
224 this.destructureNamedParams: false, | 219 this.destructureNamedParams: false, |
225 this.moduleFormat: ModuleFormat.legacy, | |
226 this.hoistInstanceCreation: true, | 220 this.hoistInstanceCreation: true, |
227 this.hoistSignatureTypes: false, | 221 this.hoistSignatureTypes: false, |
228 this.nameTypeTests: true, | 222 this.nameTypeTests: true, |
229 this.hoistTypeTests: true, | 223 this.hoistTypeTests: true, |
230 this.useAngular2Whitelist: false}); | 224 this.useAngular2Whitelist: false}); |
231 | 225 |
232 CompilerOptions.fromArguments(ArgResults args) | 226 CompilerOptions.fromArguments(ArgResults args) |
233 : sourceMap = args['source-map'], | 227 : sourceMap = args['source-map'], |
234 sourceMapComment = args['source-map-comment'], | 228 sourceMapComment = args['source-map-comment'], |
235 summarizeApi = args['summarize'], | 229 summarizeApi = args['summarize'], |
236 summaryExtension = args['summary-extension'], | 230 summaryExtension = args['summary-extension'], |
237 unsafeForceCompile = args['unsafe-force-compile'], | 231 unsafeForceCompile = args['unsafe-force-compile'], |
238 emitMetadata = args['emit-metadata'], | 232 emitMetadata = args['emit-metadata'], |
239 closure = args['closure-experimental'], | 233 closure = args['closure-experimental'], |
240 destructureNamedParams = args['destructure-named-params'], | 234 destructureNamedParams = args['destructure-named-params'], |
241 moduleFormat = parseModuleFormat(args['modules']), | |
242 hoistInstanceCreation = args['hoist-instance-creation'], | 235 hoistInstanceCreation = args['hoist-instance-creation'], |
243 hoistSignatureTypes = args['hoist-signature-types'], | 236 hoistSignatureTypes = args['hoist-signature-types'], |
244 nameTypeTests = args['name-type-tests'], | 237 nameTypeTests = args['name-type-tests'], |
245 hoistTypeTests = args['hoist-type-tests'], | 238 hoistTypeTests = args['hoist-type-tests'], |
246 useAngular2Whitelist = args['unsafe-angular2-whitelist']; | 239 useAngular2Whitelist = args['unsafe-angular2-whitelist']; |
247 | 240 |
248 static void addArguments(ArgParser parser) { | 241 static void addArguments(ArgParser parser) { |
249 parser | 242 parser |
250 ..addFlag('summarize', help: 'emit an API summary file', defaultsTo: true) | 243 ..addFlag('summarize', help: 'emit an API summary file', defaultsTo: true) |
251 ..addOption('summary-extension', | 244 ..addOption('summary-extension', |
252 help: 'file extension for Dart summary files', | 245 help: 'file extension for Dart summary files', |
253 defaultsTo: 'sum', | 246 defaultsTo: 'sum', |
254 hide: true) | 247 hide: true) |
255 ..addFlag('source-map', help: 'emit source mapping', defaultsTo: true) | 248 ..addFlag('source-map', help: 'emit source mapping', defaultsTo: true) |
256 ..addFlag('source-map-comment', | 249 ..addFlag('source-map-comment', |
257 help: 'adds a sourceMappingURL comment to the end of the JS,\n' | 250 help: 'adds a sourceMappingURL comment to the end of the JS,\n' |
258 'disable if using X-SourceMap header', | 251 'disable if using X-SourceMap header', |
259 defaultsTo: true, | 252 defaultsTo: true, |
260 hide: true) | 253 hide: true) |
261 ..addOption('modules', | |
262 help: 'module pattern to emit', | |
263 allowed: ['es6', 'legacy', 'node'], | |
264 allowedHelp: { | |
265 'es6': 'es6 modules', | |
266 'legacy': 'a custom format used by dartdevc, similar to AMD', | |
267 'node': 'node.js modules (https://nodejs.org/api/modules.html)' | |
268 }, | |
269 defaultsTo: 'legacy') | |
270 ..addFlag('emit-metadata', | 254 ..addFlag('emit-metadata', |
271 help: 'emit metadata annotations queriable via mirrors', | 255 help: 'emit metadata annotations queriable via mirrors', |
272 defaultsTo: false) | 256 defaultsTo: false) |
273 ..addFlag('closure-experimental', | 257 ..addFlag('closure-experimental', |
274 help: 'emit Closure Compiler-friendly code (experimental)', | 258 help: 'emit Closure Compiler-friendly code (experimental)', |
275 defaultsTo: false) | 259 defaultsTo: false) |
276 ..addFlag('destructure-named-params', | 260 ..addFlag('destructure-named-params', |
277 help: 'Destructure named parameters', defaultsTo: false, hide: true) | 261 help: 'Destructure named parameters', defaultsTo: false, hide: true) |
278 ..addFlag('unsafe-force-compile', | 262 ..addFlag('unsafe-force-compile', |
279 help: 'Compile code even if it has errors. ಠ_ಠ\n' | 263 help: 'Compile code even if it has errors. ಠ_ಠ\n' |
(...skipping 14 matching lines...) Expand all Loading... | |
294 help: 'Hoist types used in type tests', defaultsTo: true, hide: true) | 278 help: 'Hoist types used in type tests', defaultsTo: true, hide: true) |
295 ..addFlag('unsafe-angular2-whitelist', defaultsTo: false, hide: true); | 279 ..addFlag('unsafe-angular2-whitelist', defaultsTo: false, hide: true); |
296 } | 280 } |
297 } | 281 } |
298 | 282 |
299 /// A unit of Dart code that can be built into a single JavaScript module. | 283 /// A unit of Dart code that can be built into a single JavaScript module. |
300 class BuildUnit { | 284 class BuildUnit { |
301 /// The name of this module. | 285 /// The name of this module. |
302 final String name; | 286 final String name; |
303 | 287 |
304 /// Library root. All library names are relative to this path/prefix. | 288 /// All library names are relative to this path/prefix. |
305 final String libraryRoot; | 289 final String libraryRoot; |
306 | 290 |
307 /// The list of sources in this module. | 291 /// The list of sources in this module. |
308 /// | 292 /// |
309 /// The set of Dart files can be arbitrarily large, but it must contain | 293 /// The set of Dart files can be arbitrarily large, but it must contain |
310 /// complete libraries including all of their parts, as well as all libraries | 294 /// complete libraries including all of their parts, as well as all libraries |
311 /// that are part of a library cycle. | 295 /// that are part of a library cycle. |
312 final List<String> sources; | 296 final List<String> sources; |
313 | 297 |
314 /// Given an imported library URI, this will determine to what Dart/JS module | 298 /// Given an imported library URI, this will determine to what Dart/JS module |
315 /// it belongs to. | 299 /// it belongs to. |
316 // TODO(jmesserly): we should replace this with another way of tracking | 300 // TODO(jmesserly): we should replace this with another way of tracking |
317 // build units. | 301 // build units. |
318 final Func1<Source, String> libraryToModule; | 302 final Func1<Source, String> libraryToModule; |
319 | 303 |
320 BuildUnit(this.name, this.libraryRoot, this.sources, this.libraryToModule); | 304 BuildUnit(this.name, this.libraryRoot, this.sources, this.libraryToModule); |
321 } | 305 } |
322 | 306 |
323 /// The output of Dart->JS compilation. | 307 /// The output of Dart->JS compilation. |
324 /// | 308 /// |
325 /// This contains the file contents of the JS module, as well as a list of | 309 /// This contains the file contents of the JS module, as well as a list of |
326 /// Dart libraries that are contained in this module. | 310 /// Dart libraries that are contained in this module. |
327 class JSModuleFile { | 311 class JSModuleFile { |
328 /// The name of this module. | 312 /// The name of this module. |
329 final String name; | 313 final String name; |
330 | 314 |
331 /// The list of messages (errors and warnings) | 315 /// The list of messages (errors and warnings) |
332 final List<String> errors; | 316 final List<String> errors; |
333 | 317 |
318 /// The AST that will be used to generate the [code] and [sourceMap] for this | |
319 /// module. | |
320 final JS.Program moduleTree; | |
321 | |
322 /// The compiler options used to generate this module. | |
323 final CompilerOptions options; | |
324 | |
325 /// The binary contents of the API summary file, including APIs from each of | |
326 /// the libraries in this module. | |
327 final List<int> summaryBytes; | |
328 | |
329 JSModuleFile( | |
330 this.name, this.errors, this.options, this.moduleTree, this.summaryBytes); | |
331 | |
332 JSModuleFile.invalid(this.name, this.errors, this.options) | |
333 : moduleTree = null, | |
334 summaryBytes = null; | |
335 | |
336 /// True if this library was successfully compiled. | |
337 bool get isValid => moduleTree != null; | |
338 | |
339 /// Gets the source code and source map for this JS module, given the | |
340 /// locations where the JS file and map file will be served from. | |
341 /// | |
342 /// Relative URLs will be used to point from the .js file to the .map file | |
343 // | |
344 // TODO(jmesserly): this should match our old logic, but I'm not sure we are | |
345 // correctly handling the pointer from the .js file to the .map file. | |
346 JSModuleCode getCode(ModuleFormat format, String jsUrl, String mapUrl) { | |
347 var opts = new JS.JavaScriptPrintingOptions( | |
348 emitTypes: options.closure, | |
349 allowKeywordsInProperties: true, | |
350 allowSingleLineIfStatements: true); | |
351 JS.SimpleJavaScriptPrintingContext printer; | |
352 SourceMapBuilder sourceMap; | |
353 if (options.sourceMap) { | |
354 var sourceMapContext = new SourceMapPrintingContext(); | |
355 sourceMap = sourceMapContext.sourceMap; | |
356 printer = sourceMapContext; | |
357 } else { | |
358 printer = new JS.SimpleJavaScriptPrintingContext(); | |
359 } | |
360 | |
361 var tree = lowerModuleFormat(format, moduleTree); | |
362 tree.accept( | |
363 new JS.Printer(opts, printer, localNamer: new JS.TemporaryNamer(tree))); | |
364 | |
365 if (options.sourceMap && options.sourceMapComment) { | |
366 printer.emit('\n//# sourceMappingURL=$mapUrl\n'); | |
367 } | |
368 | |
369 Map builtMap; | |
370 if (sourceMap != null) { | |
371 builtMap = placeSourceMap(sourceMap.build(jsUrl), mapUrl); | |
372 } | |
373 return new JSModuleCode(printer.getText(), builtMap); | |
374 } | |
375 | |
376 /// Similar to [getCode] but immediately writes the resulting files. | |
377 /// | |
378 /// If [mapPath] is not supplied but [options.sourceMap] is set, mapPath | |
379 /// will default to [jsPath].map. | |
380 void writeCodeSync(ModuleFormat format, String jsPath, [String mapPath]) { | |
381 if (mapPath == null) mapPath = jsPath + '.map'; | |
382 var code = getCode(format, jsPath, mapPath); | |
383 new File(jsPath).writeAsStringSync(code.code); | |
384 if (code.sourceMap != null) { | |
385 new File(mapPath).writeAsStringSync(JSON.encode(code.sourceMap)); | |
386 } | |
387 } | |
388 } | |
389 | |
390 /// The output of compiling a JavaScript module in a particular format. | |
391 class JSModuleCode { | |
334 /// The JavaScript code for this module. | 392 /// The JavaScript code for this module. |
335 /// | 393 /// |
336 /// If a [sourceMap] is available, this will include the `sourceMappingURL` | 394 /// If a [sourceMap] is available, this will include the `sourceMappingURL` |
337 /// comment at end of the file. | 395 /// comment at end of the file. |
338 final String code; | 396 final String code; |
339 | 397 |
340 /// The JSON of the source map, if generated, otherwise `null`. | 398 /// The JSON of the source map, if generated, otherwise `null`. |
341 /// | 399 /// |
342 /// The source paths will initially be absolute paths. They can be adjusted | 400 /// The source paths will initially be absolute paths. They can be adjusted |
343 /// using [placeSourceMap]. | 401 /// using [placeSourceMap]. |
344 final Map sourceMap; | 402 final Map sourceMap; |
345 | 403 |
346 /// The binary contents of the API summary file, including APIs from each of | 404 JSModuleCode(this.code, this.sourceMap); |
347 /// the [libraries] in this module. | 405 } |
348 final List<int> summaryBytes; | |
349 | 406 |
350 JSModuleFile( | |
351 this.name, this.errors, this.code, this.sourceMap, this.summaryBytes); | |
352 | 407 |
353 JSModuleFile.invalid(this.name, this.errors) | 408 /// Adjusts the source paths in [sourceMap] to be relative to [sourceMapPath], |
354 : code = null, | 409 /// and returns the new map. |
355 sourceMap = null, | 410 // TODO(jmesserly): find a new home for this. |
356 summaryBytes = null; | 411 Map placeSourceMap(Map sourceMap, String sourceMapPath) { |
412 var dir = path.dirname(sourceMapPath); | |
357 | 413 |
358 /// True if this library was successfully compiled. | 414 var map = new Map.from(sourceMap); |
359 bool get isValid => code != null; | 415 List list = new List.from(map['sources']); |
nweiz
2016/08/24 23:33:26
Nit: "var list"
Jennifer Messerly
2016/08/25 16:21:39
yeah this method just moved from somewhere. Never
| |
360 | 416 map['sources'] = list; |
361 /// Adjusts the source paths in [sourceMap] to be relative to [sourceMapPath], | 417 for (int i = 0; i < list.length; i++) { |
nweiz
2016/08/24 23:33:26
Nit: "var i"
Jennifer Messerly
2016/08/25 16:21:39
Done.
| |
362 /// and returns the new map. | 418 list[i] = path.relative(list[i], from: dir); |
nweiz
2016/08/24 23:33:26
Source map locations are URIs, not local filesyste
Jennifer Messerly
2016/08/25 16:21:39
Done. The reason it was working: it only needs to
| |
363 /// | |
364 /// See also [writeSourceMap]. | |
365 Map placeSourceMap(String sourceMapPath) { | |
366 var dir = path.dirname(sourceMapPath); | |
367 | |
368 var map = new Map.from(this.sourceMap); | |
369 List list = new List.from(map['sources']); | |
370 map['sources'] = list; | |
371 for (int i = 0; i < list.length; i++) { | |
372 list[i] = path.relative(list[i], from: dir); | |
373 } | |
374 return map; | |
375 } | 419 } |
420 return sourceMap; | |
nweiz
2016/08/24 23:33:26
I really don't like returning *and* modifying a pa
vsm
2016/08/25 12:19:24
John: did you mean to return the new map (as per y
Jennifer Messerly
2016/08/25 16:21:39
good catch. yes it should be returning the copy. A
| |
376 } | 421 } |
OLD | NEW |