Index: lib/src/compiler.dart |
diff --git a/lib/src/compiler.dart b/lib/src/compiler.dart |
index c12ffce6078049ec100e3d7c075624088d24817f..c526b27e6724a2e6621fb179b8027f9be5cf8133 100644 |
--- a/lib/src/compiler.dart |
+++ b/lib/src/compiler.dart |
@@ -6,6 +6,7 @@ library compiler; |
import 'dart:async'; |
import 'dart:collection' show SplayTreeMap; |
+import 'dart:uri'; |
import 'package:html5lib/dom.dart'; |
import 'package:html5lib/parser.dart'; |
@@ -20,6 +21,7 @@ import 'files.dart'; |
import 'html_cleaner.dart'; |
import 'info.dart'; |
import 'messages.dart'; |
+import 'observable_transform.dart' show transformObservables; |
import 'options.dart'; |
import 'utils.dart'; |
@@ -53,6 +55,9 @@ class Compiler { |
PathInfo _pathInfo; |
Messages _messages; |
+ FutureGroup _tasks; |
+ Set _processed; |
+ |
/** Information about source [files] given their href. */ |
final Map<Path, FileInfo> info = new SplayTreeMap<Path, FileInfo>(); |
@@ -99,6 +104,7 @@ class Compiler { |
} |
return _parseAndDiscover(_mainPath).then((_) { |
_analyze(); |
+ _transformDart(); |
_emit(); |
}); |
} |
@@ -109,49 +115,52 @@ class Compiler { |
* processed. |
*/ |
Future _parseAndDiscover(Path inputFile) { |
- var tasks = new FutureGroup(); |
- bool isEntry = !options.componentsOnly; |
- |
- var processed = new Set(); |
- processHtmlFile(SourceFile file) { |
- if (file == null) return; |
- if (!_pathInfo.checkInputPath(file.path, _messages)) return; |
- |
- files.add(file); |
- |
- var fileInfo = _time('Analyzed definitions', file.path, |
- () => analyzeDefinitions(file, _messages, isEntryPoint: isEntry)); |
- isEntry = false; |
- info[file.path] = fileInfo; |
- |
- // Load component files referenced by [file]. |
- for (var href in fileInfo.componentLinks) { |
- if (!processed.contains(href)) { |
- processed.add(href); |
- tasks.add(_parseHtmlFile(href).then(processHtmlFile)); |
- } |
- } |
+ _tasks = new FutureGroup(); |
+ _processed = new Set(); |
+ _processed.add(inputFile); |
+ _tasks.add(_parseHtmlFile(inputFile).then(_processHtmlFile)); |
+ return _tasks.future; |
+ } |
- // Load .dart files being referenced in the page. |
- var src = fileInfo.externalFile; |
- if (src != null && !processed.contains(src)) { |
- processed.add(src); |
- tasks.add(_parseDartFile(src).then(_addDartFile)); |
- } |
+ bool _shouldProcessFile(SourceFile file) => |
+ file != null && _pathInfo.checkInputPath(file.path, _messages); |
- // Load .dart files being referenced in components. |
- for (var component in fileInfo.declaredComponents) { |
- var src = component.externalFile; |
- if (src != null && !processed.contains(src)) { |
- processed.add(src); |
- tasks.add(_parseDartFile(src).then(_addDartFile)); |
- } |
+ void _processHtmlFile(SourceFile file) { |
+ if (!_shouldProcessFile(file)) return; |
+ |
+ bool isEntryPoint = _processed.length == 1; |
+ |
+ files.add(file); |
+ |
+ var fileInfo = _time('Analyzed definitions', file.path, |
+ () => analyzeDefinitions(file, _messages, isEntryPoint: isEntryPoint)); |
+ info[file.path] = fileInfo; |
+ |
+ _processImports(fileInfo); |
+ |
+ // Load component files referenced by [file]. |
+ for (var href in fileInfo.componentLinks) { |
+ if (!_processed.contains(href)) { |
+ _processed.add(href); |
+ _tasks.add(_parseHtmlFile(href).then(_processHtmlFile)); |
} |
} |
- processed.add(inputFile); |
- tasks.add(_parseHtmlFile(inputFile).then(processHtmlFile)); |
- return tasks.future; |
+ // Load .dart files being referenced in the page. |
+ var src = fileInfo.externalFile; |
+ if (src != null && !_processed.contains(src)) { |
+ _processed.add(src); |
+ _tasks.add(_parseDartFile(src).then(_processDartFile)); |
+ } |
+ |
+ // Load .dart files being referenced in components. |
+ for (var component in fileInfo.declaredComponents) { |
+ var src = component.externalFile; |
+ if (src != null && !_processed.contains(src)) { |
+ _processed.add(src); |
+ _tasks.add(_parseDartFile(src).then(_processDartFile)); |
+ } |
+ } |
} |
/** Asynchronously parse [path] as an .html file. */ |
@@ -179,20 +188,138 @@ class Compiler { |
return null; |
} |
- void _addDartFile(SourceFile dartFile) { |
- if (dartFile == null) return; |
- if (!_pathInfo.checkInputPath(dartFile.path, _messages)) return; |
+ void _processDartFile(SourceFile dartFile) { |
+ if (!_shouldProcessFile(dartFile)) return; |
files.add(dartFile); |
var fileInfo = new FileInfo(dartFile.path); |
info[dartFile.path] = fileInfo; |
- fileInfo.inlinedCode = dartFile.code; |
- fileInfo.userCode = parseDartCode(fileInfo.inlinedCode, |
- fileInfo.path, messages:_messages); |
- if (fileInfo.userCode.partOf != null) { |
- _messages.error('expected a library, not a part.', null, |
- file: dartFile.path); |
+ fileInfo.userCode = parseDartCode(dartFile.code, |
+ fileInfo.path, messages: _messages); |
+ |
+ _processImports(fileInfo); |
+ } |
+ |
+ void _processImports(FileInfo fileInfo) { |
+ if (fileInfo.userCode == null) return; |
+ |
+ for (var directive in fileInfo.userCode.directives) { |
+ var src = _getDirectivePath(fileInfo, directive.uri); |
+ if (src == null) continue; |
+ if (!_processed.contains(src)) { |
+ _processed.add(src); |
+ _tasks.add(_parseDartFile(src).then(_processDartFile)); |
+ } |
+ } |
+ } |
+ |
+ Path _getDirectivePath(LibraryInfo libInfo, String uri) { |
+ if (uri.startsWith('dart:')) return null; |
+ |
+ if (uri.startsWith('package:')) { |
+ // Don't process our own package -- we'll implement @observable manually. |
+ if (uri.startsWith('package:web_ui/')) return null; |
+ |
+ return _mainPath.directoryPath.join(new Path('packages')) |
+ .join(new Path(uri.substring(8))); |
+ } else { |
+ return libInfo.inputPath.directoryPath.join(new Path(uri)); |
+ } |
+ } |
+ |
+ /** |
+ * Transform Dart source code. |
+ * Currently, the only transformation is [transformObservables]. |
+ * Calls _emitModifiedDartFiles to write the transformed files. |
+ */ |
+ void _transformDart() { |
+ var libraries = <LibraryInfo>[]; |
+ for (var sourceFile in files) { |
+ var file = info[sourceFile.path]; |
+ libraries.add(file); |
+ libraries.addAll(file.declaredComponents); |
+ } |
+ // Prefer to process the .dart file if it is external. |
+ for (var i = 0; i < libraries.length; i++) { |
+ var external = libraries[i].externalCode; |
+ if (external != null) libraries[i] = external; |
+ } |
+ libraries = libraries.where((lib) => lib.userCode != null).toList(); |
+ |
+ var transformed = []; |
+ for (var library in libraries) { |
+ // TODO(jmesserly): does it make sense for us to warn about Dart parse |
+ // errors? It seems useful, but the VM or dart2js would still issue these |
+ // messages anyway later. |
+ if (transformObservables(library, messages: _messages)) { |
+ transformed.add(library); |
+ } |
+ } |
+ |
+ _emitModifiedDartFiles(libraries, transformed); |
+ } |
+ |
+ /** |
+ * This method rewrites imports transitively for any modified dart files, |
+ * and queues the [outputs] to be written. This will not write files that |
+ * are handled by [WebComponentEmitter] and [MainPageEmitter]. |
+ */ |
+ void _emitModifiedDartFiles(List<LibraryInfo> libraries, |
+ List<FileInfo> transformed) { |
+ |
+ if (transformed.length == 0) return; |
+ |
+ // Compute files that reference each file, then use this information to |
+ // flip the modified bit transitively. This is a lot simpler than trying |
+ // to compute it the other way because of circular references. |
+ for (var library in libraries) { |
+ for (var directive in library.userCode.directives) { |
+ var importPath = _getDirectivePath(library, directive.uri); |
+ if (importPath == null) continue; |
+ |
+ info[importPath].referencedBy.add(library); |
+ } |
+ } |
+ |
+ // Propegate the modified bit to anything that references a modified file. |
+ void setModified(LibraryInfo library) { |
+ if (library.modified) return; |
+ library.modified = true; |
+ library.referencedBy.forEach(setModified); |
+ } |
+ transformed.forEach(setModified); |
+ |
+ for (var library in libraries) { |
+ // We don't need this anymore, so free it. |
+ library.referencedBy = null; |
+ |
+ if (!library.modified) continue; |
+ |
+ var fileOutputPath = _pathInfo.outputLibraryPath(library); |
+ |
+ // Fix imports of modified files to use the generated path. |
+ for (var directive in library.userCode.directives) { |
+ var importPath = _getDirectivePath(library, directive.uri); |
+ if (importPath == null) continue; |
+ var importInfo = info[importPath]; |
+ |
+ if (importInfo.modified && !directive.generated) { |
+ // Use the generated URI for this file. |
+ directive.generated = true; |
+ directive.uri = _pathInfo.outputLibraryPath(importInfo) |
+ .relativeTo(fileOutputPath.directoryPath).toString(); |
+ } |
+ } |
+ |
+ // Components will get emitted by WebComponentEmitter, and the |
+ // entry point will get emitted by MainPageEmitter. |
+ // So we only need to worry about other .dart files. |
+ if (library is FileInfo && library.htmlFile == null) { |
+ // Add an output file for the transformed .dart code: |
+ output.add(new OutputFile(fileOutputPath, |
+ emitDartFile(library, _pathInfo), source: library.inputPath)); |
+ } |
} |
} |
@@ -318,5 +445,3 @@ class Compiler { |
'\n<!-- This file was auto-generated from ${file.path}. -->\n')); |
} |
} |
- |
- |