Chromium Code Reviews| 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 |