Index: lib/src/compiler/compiler.dart |
diff --git a/lib/src/compiler/compiler.dart b/lib/src/compiler/compiler.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..31fb4999b5caeaf1c9078b60e956191f8fa83893 |
--- /dev/null |
+++ b/lib/src/compiler/compiler.dart |
@@ -0,0 +1,269 @@ |
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+import 'package:args/args.dart' show ArgParser, ArgResults; |
+import 'package:analyzer/analyzer.dart' |
+ show AnalysisError, CompilationUnit, ErrorSeverity; |
+import 'package:analyzer/src/generated/engine.dart' show AnalysisContext; |
+import 'package:analyzer/src/generated/java_engine.dart' show AnalysisException; |
+import 'package:analyzer/src/generated/source_io.dart' show Source, SourceKind; |
+import 'package:func/func.dart' show Func1; |
+import 'package:path/path.dart' as path; |
+ |
+import '../analyzer/context.dart' |
+ show AnalyzerOptions, createAnalysisContextWithSources; |
+import 'extension_types.dart' show ExtensionTypeSet; |
+import 'code_generator.dart' show CodeGenerator; |
+import 'error_helpers.dart' show errorSeverity, formatError, sortErrors; |
+ |
+/// Compiles a set of Dart files into a single JavaScript module. |
+/// |
+/// For a single [BuildUnit] definition, this will produce a [JSModuleFile]. |
+/// Those objects are record types that record the data consumed and produced |
+/// for a single compile. |
+/// |
+/// This class exists to cache global state associated with a single in-memory |
+/// AnalysisContext, such as information about extension types in the Dart SDK. |
+/// It can be used once to produce a single module, or reused to save warm-up |
+/// time. (Currently there is no warm up, but there may be in the future.) |
+/// |
+/// The SDK source code is assumed to be immutable for the life of this class. |
+/// |
+/// For all other files, it is up to the [AnalysisContext] to decide whether or |
+/// not any caching is performed. By default an analysis context will assume |
+/// sources are immutable for the life of the context, and cache information |
+/// about them. |
+class ModuleCompiler { |
+ final AnalysisContext context; |
+ final _extensionTypes = new ExtensionTypeSet(); |
+ |
+ ModuleCompiler.withContext(this.context); |
+ |
+ ModuleCompiler(AnalyzerOptions analyzerOptions) |
+ : this.withContext(createAnalysisContextWithSources(analyzerOptions)); |
+ |
+ /// Compiles a single Dart build unit into a JavaScript module. |
+ /// |
+ /// *Warning* - this may require resolving the entire world. |
+ /// If that is not desired, the analysis context must be pre-configured using |
+ /// summaries before calling this method. |
+ JSModuleFile compile(BuildUnit unit, CompilerOptions options) { |
+ var trees = <CompilationUnit>[]; |
+ var errors = <AnalysisError>[]; |
+ |
+ for (var sourcePath in unit.sources) { |
+ String sourceUri = sourcePath; |
+ if (path.isRelative(sourcePath)) { |
+ sourceUri = path.absolute(sourceUri); |
+ } |
+ sourceUri = path.toUri(sourceUri).toString(); |
+ Source source = context.sourceFactory.forUri(sourceUri); |
+ if (source == null) { |
+ throw new AnalysisException('could not create a source for $sourcePath.' |
+ ' The file name is in the wrong format or was not found.'); |
+ } |
+ |
+ // Ignore parts. They need to be handled in the context of their library. |
+ if (context.getKindOf(source) == SourceKind.PART) { |
+ continue; |
+ } |
+ |
+ var resolvedTree = context.resolveCompilationUnit2(source, source); |
+ trees.add(resolvedTree); |
+ errors.addAll(context.computeErrors(source)); |
+ |
+ var library = resolvedTree.element.library; |
+ for (var part in library.parts) { |
+ trees.add(context.resolveCompilationUnit(part.source, library)); |
+ errors.addAll(context.computeErrors(part.source)); |
+ } |
+ } |
+ |
+ sortErrors(context, errors); |
+ var messages = <String>[]; |
+ for (var e in errors) { |
+ var m = formatError(context, e); |
+ if (m != null) messages.add(m); |
+ } |
+ |
+ if (!options.unsafeForceCompile && |
+ errors.any((e) => errorSeverity(context, e) == ErrorSeverity.ERROR)) { |
+ return new JSModuleFile.invalid(unit.name, messages); |
+ } |
+ |
+ var codeGenerator = new CodeGenerator(context, options, _extensionTypes); |
+ return codeGenerator.compile(unit, trees, messages); |
+ } |
+} |
+ |
+enum ModuleFormat { es6, legacy, node } |
+ |
+ModuleFormat parseModuleFormat(String s) => { |
+ 'es6': ModuleFormat.es6, |
+ 'node': ModuleFormat.node, |
+ 'legacy': ModuleFormat.legacy |
+ }[s]; |
+ |
+class CompilerOptions { |
+ /// Whether to emit the source mapping file. |
+ /// |
+ /// This supports debugging the original source code instead of the generated |
+ /// code. |
+ final bool sourceMap; |
+ |
+ /// If [sourceMap] is emitted, this will emit a `sourceMappingUrl` comment |
+ /// into the output JavaScript module. |
+ final bool sourceMapComment; |
+ |
+ /// Whether to emit a summary file containing API signatures. |
+ /// |
+ /// This is required for a modular build process. |
+ final bool summarizeApi; |
+ |
+ /// Whether to force compilation of code with static errors. |
+ final bool unsafeForceCompile; |
+ |
+ /// Whether to emit Closure Compiler-friendly code. |
+ final bool closure; |
+ |
+ /// Enable ES6 destructuring of named parameters. Off by default. |
+ /// |
+ /// Older V8 versions do not accept default values with destructuring in |
+ /// arrow functions yet (e.g. `({a} = {}) => 1`) but happily accepts them |
+ /// with regular functions (e.g. `function({a} = {}) { return 1 }`). |
+ /// |
+ /// Supporting the syntax: |
+ /// * Chrome Canary (51) |
+ /// * Firefox |
+ /// |
+ /// Not yet supporting: |
+ /// * Atom (1.5.4) |
+ /// * Electron (0.36.3) |
+ // TODO(ochafik): Simplify this code when our target platforms catch up. |
+ final bool destructureNamedParams; |
+ |
+ /// Which module format to support. |
+ /// Currently 'es6' and 'legacy' are supported. |
+ final ModuleFormat moduleFormat; |
+ |
+ const CompilerOptions( |
+ {this.sourceMap: true, |
+ this.sourceMapComment: true, |
+ this.summarizeApi: true, |
+ this.unsafeForceCompile: false, |
+ this.closure: false, |
+ this.destructureNamedParams: false, |
+ this.moduleFormat: ModuleFormat.legacy}); |
+ |
+ CompilerOptions.fromArguments(ArgResults args) |
+ : sourceMap = args['source-map'], |
+ sourceMapComment = args['source-map-comment'], |
+ summarizeApi = args['summarize'], |
+ unsafeForceCompile = args['unsafe-force-compile'], |
+ closure = args['closure-experimental'], |
+ destructureNamedParams = args['destructure-named-params'], |
+ moduleFormat = parseModuleFormat(args['modules']); |
+ |
+ static ArgParser addArguments(ArgParser parser) => parser |
+ ..addFlag('summarize', help: 'emit an API summary file', defaultsTo: true) |
+ ..addFlag('source-map', help: 'emit source mapping', defaultsTo: true) |
+ ..addFlag('source-map-comment', |
+ help: 'adds a sourceMappingURL comment to the end of the JS,\n' |
+ 'disable if using X-SourceMap header', |
+ defaultsTo: true) |
+ ..addOption('modules', |
+ help: 'module pattern to emit', |
+ allowed: ['es6', 'legacy', 'node'], |
+ allowedHelp: { |
+ 'es6': 'es6 modules', |
+ 'legacy': 'a custom format used by dartdevc, similar to AMD', |
+ 'node': 'node.js modules (https://nodejs.org/api/modules.html)' |
+ }, |
+ defaultsTo: 'legacy') |
+ ..addFlag('closure-experimental', |
+ help: 'emit Closure Compiler-friendly code (experimental)', |
+ defaultsTo: false) |
+ ..addFlag('destructure-named-params', |
+ help: 'Destructure named parameters', defaultsTo: false) |
+ ..addFlag('unsafe-force-compile', |
+ help: 'Compile code even if it has errors. ಠ_ಠ\n' |
+ 'This has undefined behavior!', |
+ defaultsTo: false); |
+} |
+ |
+/// A unit of Dart code that can be built into a single JavaScript module. |
+class BuildUnit { |
+ /// The name of this module. |
+ final String name; |
+ |
+ /// The list of sources in this module. |
+ /// |
+ /// The set of Dart files can be arbitrarily large, but it must contain |
+ /// complete libraries including all of their parts, as well as all libraries |
+ /// that are part of a library cycle. |
+ final List<String> sources; |
+ |
+ /// Given an imported library URI, this will determine to what Dart/JS module |
+ /// it belongs to. |
+ // TODO(jmesserly): we should replace this with another way of tracking |
+ // build units. |
+ final Func1<Source, String> libraryToModule; |
+ |
+ BuildUnit(this.name, this.sources, this.libraryToModule); |
+} |
+ |
+/// The output of Dart->JS compilation. |
+/// |
+/// This contains the file contents of the JS module, as well as a list of |
+/// Dart libraries that are contained in this module. |
+class JSModuleFile { |
+ /// The name of this module. |
+ final String name; |
+ |
+ /// The list of messages (errors and warnings) |
+ final List<String> errors; |
+ |
+ /// The JavaScript code for this module. |
+ /// |
+ /// If a [sourceMap] is available, this will include the `sourceMappingURL` |
+ /// comment at end of the file. |
+ final String code; |
+ |
+ /// The JSON of the source map, if generated, otherwise `null`. |
+ /// |
+ /// The source paths will initially be absolute paths. They can be adjusted |
+ /// using [placeSourceMap]. |
+ final Map sourceMap; |
+ |
+ /// The binary contents of the API summary file, including APIs from each of |
+ /// the [libraries] in this module. |
+ final List<int> summaryBytes; |
+ |
+ JSModuleFile( |
+ this.name, this.errors, this.code, this.sourceMap, this.summaryBytes); |
+ |
+ JSModuleFile.invalid(this.name, this.errors) |
+ : code = null, |
+ sourceMap = null, |
+ summaryBytes = null; |
+ |
+ /// True if this library was successfully compiled. |
+ bool get isValid => code != null; |
+ |
+ /// Adjusts the source paths in [sourceMap] to be relative to [sourceMapPath], |
+ /// and returns the new map. |
+ /// |
+ /// See also [writeSourceMap]. |
+ Map placeSourceMap(String sourceMapPath) { |
+ var dir = path.dirname(sourceMapPath); |
+ |
+ var map = new Map.from(this.sourceMap); |
+ List list = new List.from(map['sources']); |
+ map['sources'] = list; |
+ for (int i = 0; i < list.length; i++) { |
+ list[i] = path.relative(list[i], from: dir); |
+ } |
+ return map; |
+ } |
+} |