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 transformModuleFormat, 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 = transformModuleFormat(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( | 407 /// Adjusts the source paths in [sourceMap] to be relative to [sourceMapPath], |
351 this.name, this.errors, this.code, this.sourceMap, this.summaryBytes); | 408 /// and returns the new map. |
| 409 // TODO(jmesserly): find a new home for this. |
| 410 Map placeSourceMap(Map sourceMap, String sourceMapPath) { |
| 411 var dir = path.dirname(sourceMapPath); |
352 | 412 |
353 JSModuleFile.invalid(this.name, this.errors) | 413 var map = new Map.from(sourceMap); |
354 : code = null, | 414 List list = new List.from(map['sources']); |
355 sourceMap = null, | 415 map['sources'] = list; |
356 summaryBytes = null; | 416 for (int i = 0; i < list.length; i++) { |
357 | 417 list[i] = |
358 /// True if this library was successfully compiled. | 418 path.toUri(path.relative(path.fromUri(list[i]), from: dir)).toString(); |
359 bool get isValid => code != null; | |
360 | |
361 /// Adjusts the source paths in [sourceMap] to be relative to [sourceMapPath], | |
362 /// and returns the new map. | |
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 map; |
376 } | 421 } |
OLD | NEW |