| Index: lib/src/compiler.dart
|
| diff --git a/lib/src/compiler.dart b/lib/src/compiler.dart
|
| index f1a4fa2d7a96d08c201c6c78195919478c6963d0..66abf586caaf87c436abf96c6c5f9670ca32a0ae 100644
|
| --- a/lib/src/compiler.dart
|
| +++ b/lib/src/compiler.dart
|
| @@ -6,15 +6,16 @@ library compiler;
|
|
|
| import 'dart:async';
|
| import 'dart:collection' show SplayTreeMap;
|
| +import 'package:analyzer_experimental/src/generated/ast.dart' show Directive;
|
| +import 'package:csslib/parser.dart' as css;
|
| +import 'package:csslib/visitor.dart' show InfoVisitor, StyleSheet;
|
| import 'package:html5lib/dom.dart';
|
| import 'package:html5lib/parser.dart';
|
| -import 'package:csslib/parser.dart' as css;
|
| -import 'package:csslib/visitor.dart';
|
|
|
| import 'analyzer.dart';
|
| import 'code_printer.dart';
|
| import 'codegen.dart' as codegen;
|
| -import 'directive_parser.dart' show parseDartCode;
|
| +import 'dart_parser.dart';
|
| import 'emitters.dart';
|
| import 'file_system.dart';
|
| import 'file_system/path.dart';
|
| @@ -22,6 +23,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';
|
|
|
| @@ -56,6 +58,11 @@ class Compiler {
|
| PathInfo _pathInfo;
|
| Messages _messages;
|
|
|
| + FutureGroup _tasks;
|
| + Set _processed;
|
| +
|
| + bool _useObservers = false;
|
| +
|
| /** Information about source [files] given their href. */
|
| final Map<Path, FileInfo> info = new SplayTreeMap<Path, FileInfo>();
|
|
|
| @@ -102,6 +109,7 @@ class Compiler {
|
| }
|
| return _parseAndDiscover(_mainPath).then((_) {
|
| _analyze();
|
| + _transformDart();
|
| _emit();
|
| });
|
| }
|
| @@ -112,49 +120,54 @@ 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));
|
| + } else if (component.userCode != null) {
|
| + _processImports(component);
|
| + }
|
| + }
|
| }
|
|
|
| /** Asynchronously parse [path] as an .html file. */
|
| @@ -182,20 +195,188 @@ 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.inlinedCode =
|
| + parseDartCode(fileInfo.path, dartFile.code, _messages);
|
| +
|
| + _processImports(fileInfo);
|
| + }
|
| +
|
| + void _processImports(LibraryInfo library) {
|
| + if (library.userCode == null) return;
|
| +
|
| + for (var directive in library.userCode.directives) {
|
| + var src = _getDirectivePath(library, directive);
|
| + if (src == null) {
|
| + var uri = getDirectiveUri(directive).value;
|
| + if (uri.startsWith('package:web_ui/observe')) {
|
| + _useObservers = true;
|
| + }
|
| + } else if (!_processed.contains(src)) {
|
| + _processed.add(src);
|
| + _tasks.add(_parseDartFile(src).then(_processDartFile));
|
| + }
|
| + }
|
| + }
|
| +
|
| + Path _getDirectivePath(LibraryInfo libInfo, Directive directive) {
|
| + var uri = getDirectiveUri(directive).value;
|
| + 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 = _findAllDartLibraries();
|
| +
|
| + var transformed = [];
|
| + for (var library in libraries) {
|
| + if (transformObservables(library.userCode, _messages)) {
|
| + // TODO(jmesserly): what about ObservableList/Map/Set?
|
| + _useObservers = true;
|
| + transformed.add(library);
|
| + }
|
| + }
|
| +
|
| + _findModifiedDartFiles(libraries, transformed);
|
| +
|
| + libraries.forEach(_fixImports);
|
| +
|
| + _emitModifiedDartFiles(libraries);
|
| + }
|
| +
|
| + /**
|
| + * Finds all Dart code libraries.
|
| + * Each library will have [LibraryInfo.inlinedCode] that is non-null.
|
| + * Also each inlinedCode will be unique.
|
| + */
|
| + List<LibraryInfo> _findAllDartLibraries() {
|
| + var libs = <LibraryInfo>[];
|
| + void _addLibrary(LibraryInfo lib) {
|
| + if (lib.inlinedCode != null) libs.add(lib);
|
| + }
|
| +
|
| + for (var sourceFile in files) {
|
| + var file = info[sourceFile.path];
|
| + _addLibrary(file);
|
| + file.declaredComponents.forEach(_addLibrary);
|
| + }
|
| +
|
| + // Assert that each file path is unique.
|
| + assert(_uniquePaths(libs));
|
| + return libs;
|
| + }
|
| +
|
| + bool _uniquePaths(List<LibraryInfo> libs) {
|
| + var seen = new Set();
|
| + for (var lib in libs) {
|
| + if (seen.contains(lib.inlinedCode)) {
|
| + throw new StateError('internal error: '
|
| + 'duplicate user code for ${lib.inputPath}. Files were: $files');
|
| + }
|
| + seen.add(lib.inlinedCode);
|
| + }
|
| + return true;
|
| + }
|
| +
|
| + /**
|
| + * Queue modified Dart files to be written.
|
| + * This will not write files that are handled by [WebComponentEmitter] and
|
| + * [MainPageEmitter].
|
| + */
|
| + void _emitModifiedDartFiles(List<LibraryInfo> libraries) {
|
| + for (var lib in libraries) {
|
| + // 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 (lib.modified && lib is FileInfo &&
|
| + lib.htmlFile == null && !lib.isEntryPoint) {
|
| +
|
| + var printer = emitDartFile(lib, _pathInfo);
|
| + _emitFile(lib, printer, lib.inputPath);
|
| + }
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * This method computes which Dart files have been modified, starting
|
| + * from [transformed] and marking recursively through all files that import
|
| + * the modified files.
|
| + */
|
| + void _findModifiedDartFiles(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);
|
| + if (importPath == null) continue;
|
| +
|
| + var importInfo = info[importPath];
|
| + if (importInfo != null) {
|
| + importInfo.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;
|
| + }
|
| + }
|
| +
|
| + void _fixImports(LibraryInfo library) {
|
| + var fileOutputPath = _pathInfo.outputLibraryPath(library);
|
| +
|
| + // Fix imports. Modified files must use the generated path, otherwise
|
| + // we need to make the path relative to the input.
|
| + for (var directive in library.userCode.directives) {
|
| + var importPath = _getDirectivePath(library, directive);
|
| + if (importPath == null) continue;
|
| + var importInfo = info[importPath];
|
| + if (importInfo == null) continue;
|
| +
|
| + String newUri;
|
| + if (importInfo.modified) {
|
| + // Use the generated URI for this file.
|
| + newUri = _pathInfo.relativePath(library, importInfo).toString();
|
| + } else {
|
| + // Get the relative path to the input file.
|
| + newUri = _pathInfo.transformUrl(library.inputPath,
|
| + getDirectiveUri(directive).value);
|
| + }
|
| + setDirectiveUri(directive, createStringLiteral(newUri));
|
| }
|
| }
|
|
|
| @@ -244,7 +425,8 @@ class Compiler {
|
| var bootstrapPath = file.path.directoryPath.append(bootstrapName);
|
| var bootstrapOutPath = _pathInfo.outputPath(bootstrapPath, '');
|
| output.add(new OutputFile(bootstrapOutPath, codegen.bootstrapCode(
|
| - _pathInfo.relativePath(new FileInfo(bootstrapPath), fileInfo))));
|
| + _pathInfo.relativePath(new FileInfo(bootstrapPath), fileInfo),
|
| + _useObservers)));
|
|
|
| var document = file.document;
|
| bool dartLoaderFound = false;
|
|
|