| OLD | NEW |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, 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 library compiler; | 5 library compiler; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:collection' show SplayTreeMap; | 8 import 'dart:collection' show SplayTreeMap; |
| 9 import 'dart:uri'; |
| 9 import 'package:html5lib/dom.dart'; | 10 import 'package:html5lib/dom.dart'; |
| 10 import 'package:html5lib/parser.dart'; | 11 import 'package:html5lib/parser.dart'; |
| 11 | 12 |
| 12 import 'analyzer.dart'; | 13 import 'analyzer.dart'; |
| 13 import 'code_printer.dart'; | 14 import 'code_printer.dart'; |
| 14 import 'codegen.dart' as codegen; | 15 import 'codegen.dart' as codegen; |
| 15 import 'directive_parser.dart' show parseDartCode; | 16 import 'directive_parser.dart' show parseDartCode; |
| 16 import 'emitters.dart'; | 17 import 'emitters.dart'; |
| 17 import 'file_system.dart'; | 18 import 'file_system.dart'; |
| 18 import 'file_system/path.dart'; | 19 import 'file_system/path.dart'; |
| 19 import 'files.dart'; | 20 import 'files.dart'; |
| 20 import 'html_cleaner.dart'; | 21 import 'html_cleaner.dart'; |
| 21 import 'info.dart'; | 22 import 'info.dart'; |
| 22 import 'messages.dart'; | 23 import 'messages.dart'; |
| 24 import 'observable_transform.dart' show transformObservables; |
| 23 import 'options.dart'; | 25 import 'options.dart'; |
| 24 import 'utils.dart'; | 26 import 'utils.dart'; |
| 25 | 27 |
| 26 /** | 28 /** |
| 27 * Parses an HTML file [contents] and returns a DOM-like tree. | 29 * Parses an HTML file [contents] and returns a DOM-like tree. |
| 28 * Note that [contents] will be a [String] if coming from a browser-based | 30 * Note that [contents] will be a [String] if coming from a browser-based |
| 29 * [FileSystem], or it will be a [List<int>] if running on the command line. | 31 * [FileSystem], or it will be a [List<int>] if running on the command line. |
| 30 * | 32 * |
| 31 * Adds emitted error/warning to [messages], if [messages] is supplied. | 33 * Adds emitted error/warning to [messages], if [messages] is supplied. |
| 32 */ | 34 */ |
| (...skipping 13 matching lines...) Expand all Loading... |
| 46 class Compiler { | 48 class Compiler { |
| 47 final FileSystem fileSystem; | 49 final FileSystem fileSystem; |
| 48 final CompilerOptions options; | 50 final CompilerOptions options; |
| 49 final List<SourceFile> files = <SourceFile>[]; | 51 final List<SourceFile> files = <SourceFile>[]; |
| 50 final List<OutputFile> output = <OutputFile>[]; | 52 final List<OutputFile> output = <OutputFile>[]; |
| 51 | 53 |
| 52 Path _mainPath; | 54 Path _mainPath; |
| 53 PathInfo _pathInfo; | 55 PathInfo _pathInfo; |
| 54 Messages _messages; | 56 Messages _messages; |
| 55 | 57 |
| 58 FutureGroup _tasks; |
| 59 Set _processed; |
| 60 |
| 56 /** Information about source [files] given their href. */ | 61 /** Information about source [files] given their href. */ |
| 57 final Map<Path, FileInfo> info = new SplayTreeMap<Path, FileInfo>(); | 62 final Map<Path, FileInfo> info = new SplayTreeMap<Path, FileInfo>(); |
| 58 | 63 |
| 59 /** | 64 /** |
| 60 * Creates a compiler with [options] using [fileSystem]. | 65 * Creates a compiler with [options] using [fileSystem]. |
| 61 * | 66 * |
| 62 * Adds emitted error/warning messages to [messages], if [messages] is | 67 * Adds emitted error/warning messages to [messages], if [messages] is |
| 63 * supplied. | 68 * supplied. |
| 64 */ | 69 */ |
| 65 Compiler(this.fileSystem, this.options, this._messages, {String currentDir}) { | 70 Compiler(this.fileSystem, this.options, this._messages, {String currentDir}) { |
| (...skipping 26 matching lines...) Expand all Loading... |
| 92 | 97 |
| 93 /** Compile the application starting from the given [mainFile]. */ | 98 /** Compile the application starting from the given [mainFile]. */ |
| 94 Future run() { | 99 Future run() { |
| 95 if (_mainPath.filename.endsWith('.dart')) { | 100 if (_mainPath.filename.endsWith('.dart')) { |
| 96 _messages.error("Please provide an HTML file as your entry point.", | 101 _messages.error("Please provide an HTML file as your entry point.", |
| 97 null, file: _mainPath); | 102 null, file: _mainPath); |
| 98 return new Future.immediate(null); | 103 return new Future.immediate(null); |
| 99 } | 104 } |
| 100 return _parseAndDiscover(_mainPath).then((_) { | 105 return _parseAndDiscover(_mainPath).then((_) { |
| 101 _analyze(); | 106 _analyze(); |
| 107 _transformDart(); |
| 102 _emit(); | 108 _emit(); |
| 103 }); | 109 }); |
| 104 } | 110 } |
| 105 | 111 |
| 106 /** | 112 /** |
| 107 * Asynchronously parse [inputFile] and transitively discover web components | 113 * Asynchronously parse [inputFile] and transitively discover web components |
| 108 * to load and parse. Returns a future that completes when all files are | 114 * to load and parse. Returns a future that completes when all files are |
| 109 * processed. | 115 * processed. |
| 110 */ | 116 */ |
| 111 Future _parseAndDiscover(Path inputFile) { | 117 Future _parseAndDiscover(Path inputFile) { |
| 112 var tasks = new FutureGroup(); | 118 _tasks = new FutureGroup(); |
| 113 bool isEntry = !options.componentsOnly; | 119 _processed = new Set(); |
| 120 _processed.add(inputFile); |
| 121 _tasks.add(_parseHtmlFile(inputFile).then(_processHtmlFile)); |
| 122 return _tasks.future; |
| 123 } |
| 114 | 124 |
| 115 var processed = new Set(); | 125 bool _shouldProcessFile(SourceFile file) => |
| 116 processHtmlFile(SourceFile file) { | 126 file != null && _pathInfo.checkInputPath(file.path, _messages); |
| 117 if (file == null) return; | |
| 118 if (!_pathInfo.checkInputPath(file.path, _messages)) return; | |
| 119 | 127 |
| 120 files.add(file); | 128 void _processHtmlFile(SourceFile file) { |
| 129 if (!_shouldProcessFile(file)) return; |
| 121 | 130 |
| 122 var fileInfo = _time('Analyzed definitions', file.path, | 131 bool isEntryPoint = _processed.length == 1; |
| 123 () => analyzeDefinitions(file, _messages, isEntryPoint: isEntry)); | |
| 124 isEntry = false; | |
| 125 info[file.path] = fileInfo; | |
| 126 | 132 |
| 127 // Load component files referenced by [file]. | 133 files.add(file); |
| 128 for (var href in fileInfo.componentLinks) { | |
| 129 if (!processed.contains(href)) { | |
| 130 processed.add(href); | |
| 131 tasks.add(_parseHtmlFile(href).then(processHtmlFile)); | |
| 132 } | |
| 133 } | |
| 134 | 134 |
| 135 // Load .dart files being referenced in the page. | 135 var fileInfo = _time('Analyzed definitions', file.path, |
| 136 var src = fileInfo.externalFile; | 136 () => analyzeDefinitions(file, _messages, isEntryPoint: isEntryPoint)); |
| 137 if (src != null && !processed.contains(src)) { | 137 info[file.path] = fileInfo; |
| 138 processed.add(src); | |
| 139 tasks.add(_parseDartFile(src).then(_addDartFile)); | |
| 140 } | |
| 141 | 138 |
| 142 // Load .dart files being referenced in components. | 139 _processImports(fileInfo); |
| 143 for (var component in fileInfo.declaredComponents) { | 140 |
| 144 var src = component.externalFile; | 141 // Load component files referenced by [file]. |
| 145 if (src != null && !processed.contains(src)) { | 142 for (var href in fileInfo.componentLinks) { |
| 146 processed.add(src); | 143 if (!_processed.contains(href)) { |
| 147 tasks.add(_parseDartFile(src).then(_addDartFile)); | 144 _processed.add(href); |
| 148 } | 145 _tasks.add(_parseHtmlFile(href).then(_processHtmlFile)); |
| 149 } | 146 } |
| 150 } | 147 } |
| 151 | 148 |
| 152 processed.add(inputFile); | 149 // Load .dart files being referenced in the page. |
| 153 tasks.add(_parseHtmlFile(inputFile).then(processHtmlFile)); | 150 var src = fileInfo.externalFile; |
| 154 return tasks.future; | 151 if (src != null && !_processed.contains(src)) { |
| 152 _processed.add(src); |
| 153 _tasks.add(_parseDartFile(src).then(_processDartFile)); |
| 154 } |
| 155 |
| 156 // Load .dart files being referenced in components. |
| 157 for (var component in fileInfo.declaredComponents) { |
| 158 var src = component.externalFile; |
| 159 if (src != null && !_processed.contains(src)) { |
| 160 _processed.add(src); |
| 161 _tasks.add(_parseDartFile(src).then(_processDartFile)); |
| 162 } |
| 163 } |
| 155 } | 164 } |
| 156 | 165 |
| 157 /** Asynchronously parse [path] as an .html file. */ | 166 /** Asynchronously parse [path] as an .html file. */ |
| 158 Future<SourceFile> _parseHtmlFile(Path path) { | 167 Future<SourceFile> _parseHtmlFile(Path path) { |
| 159 return fileSystem.readTextOrBytes(path).then((source) { | 168 return fileSystem.readTextOrBytes(path).then((source) { |
| 160 var file = new SourceFile(path); | 169 var file = new SourceFile(path); |
| 161 file.document = _time('Parsed', path, | 170 file.document = _time('Parsed', path, |
| 162 () => parseHtml(source, path, _messages)); | 171 () => parseHtml(source, path, _messages)); |
| 163 return file; | 172 return file; |
| 164 }) | 173 }) |
| 165 .catchError((e) => _readError(e, path)); | 174 .catchError((e) => _readError(e, path)); |
| 166 } | 175 } |
| 167 | 176 |
| 168 /** Parse [filename] and treat it as a .dart file. */ | 177 /** Parse [filename] and treat it as a .dart file. */ |
| 169 Future<SourceFile> _parseDartFile(Path path) { | 178 Future<SourceFile> _parseDartFile(Path path) { |
| 170 return fileSystem.readText(path) | 179 return fileSystem.readText(path) |
| 171 .then((code) => new SourceFile(path, isDart: true)..code = code) | 180 .then((code) => new SourceFile(path, isDart: true)..code = code) |
| 172 .catchError((e) => _readError(e, path)); | 181 .catchError((e) => _readError(e, path)); |
| 173 } | 182 } |
| 174 | 183 |
| 175 SourceFile _readError(error, Path path) { | 184 SourceFile _readError(error, Path path) { |
| 176 _messages.error('exception while reading file, original message:\n $error', | 185 _messages.error('exception while reading file, original message:\n $error', |
| 177 null, file: path); | 186 null, file: path); |
| 178 | 187 |
| 179 return null; | 188 return null; |
| 180 } | 189 } |
| 181 | 190 |
| 182 void _addDartFile(SourceFile dartFile) { | 191 void _processDartFile(SourceFile dartFile) { |
| 183 if (dartFile == null) return; | 192 if (!_shouldProcessFile(dartFile)) return; |
| 184 if (!_pathInfo.checkInputPath(dartFile.path, _messages)) return; | |
| 185 | 193 |
| 186 files.add(dartFile); | 194 files.add(dartFile); |
| 187 | 195 |
| 188 var fileInfo = new FileInfo(dartFile.path); | 196 var fileInfo = new FileInfo(dartFile.path); |
| 189 info[dartFile.path] = fileInfo; | 197 info[dartFile.path] = fileInfo; |
| 190 fileInfo.inlinedCode = dartFile.code; | 198 fileInfo.userCode = parseDartCode(dartFile.code, |
| 191 fileInfo.userCode = parseDartCode(fileInfo.inlinedCode, | 199 fileInfo.path, messages: _messages); |
| 192 fileInfo.path, messages:_messages); | 200 |
| 193 if (fileInfo.userCode.partOf != null) { | 201 _processImports(fileInfo); |
| 194 _messages.error('expected a library, not a part.', null, | 202 } |
| 195 file: dartFile.path); | 203 |
| 204 void _processImports(FileInfo fileInfo) { |
| 205 if (fileInfo.userCode == null) return; |
| 206 |
| 207 for (var directive in fileInfo.userCode.directives) { |
| 208 var src = _getDirectivePath(fileInfo, directive.uri); |
| 209 if (src == null) continue; |
| 210 if (!_processed.contains(src)) { |
| 211 _processed.add(src); |
| 212 _tasks.add(_parseDartFile(src).then(_processDartFile)); |
| 213 } |
| 196 } | 214 } |
| 197 } | 215 } |
| 198 | 216 |
| 217 Path _getDirectivePath(LibraryInfo libInfo, String uri) { |
| 218 if (uri.startsWith('dart:')) return null; |
| 219 |
| 220 if (uri.startsWith('package:')) { |
| 221 // Don't process our own package -- we'll implement @observable manually. |
| 222 if (uri.startsWith('package:web_ui/')) return null; |
| 223 |
| 224 return _mainPath.directoryPath.join(new Path('packages')) |
| 225 .join(new Path(uri.substring(8))); |
| 226 } else { |
| 227 return libInfo.inputPath.directoryPath.join(new Path(uri)); |
| 228 } |
| 229 } |
| 230 |
| 231 /** |
| 232 * Transform Dart source code. |
| 233 * Currently, the only transformation is [transformObservables]. |
| 234 * Calls _emitModifiedDartFiles to write the transformed files. |
| 235 */ |
| 236 void _transformDart() { |
| 237 var libraries = <LibraryInfo>[]; |
| 238 for (var sourceFile in files) { |
| 239 var file = info[sourceFile.path]; |
| 240 libraries.add(file); |
| 241 libraries.addAll(file.declaredComponents); |
| 242 } |
| 243 // Prefer to process the .dart file if it is external. |
| 244 for (var i = 0; i < libraries.length; i++) { |
| 245 var external = libraries[i].externalCode; |
| 246 if (external != null) libraries[i] = external; |
| 247 } |
| 248 libraries = libraries.where((lib) => lib.userCode != null).toList(); |
| 249 |
| 250 var transformed = []; |
| 251 for (var library in libraries) { |
| 252 // TODO(jmesserly): does it make sense for us to warn about Dart parse |
| 253 // errors? It seems useful, but the VM or dart2js would still issue these |
| 254 // messages anyway later. |
| 255 if (transformObservables(library, messages: _messages)) { |
| 256 transformed.add(library); |
| 257 } |
| 258 } |
| 259 |
| 260 _emitModifiedDartFiles(libraries, transformed); |
| 261 } |
| 262 |
| 263 /** |
| 264 * This method rewrites imports transitively for any modified dart files, |
| 265 * and queues the [outputs] to be written. This will not write files that |
| 266 * are handled by [WebComponentEmitter] and [MainPageEmitter]. |
| 267 */ |
| 268 void _emitModifiedDartFiles(List<LibraryInfo> libraries, |
| 269 List<FileInfo> transformed) { |
| 270 |
| 271 if (transformed.length == 0) return; |
| 272 |
| 273 // Compute files that reference each file, then use this information to |
| 274 // flip the modified bit transitively. This is a lot simpler than trying |
| 275 // to compute it the other way because of circular references. |
| 276 for (var library in libraries) { |
| 277 for (var directive in library.userCode.directives) { |
| 278 var importPath = _getDirectivePath(library, directive.uri); |
| 279 if (importPath == null) continue; |
| 280 |
| 281 info[importPath].referencedBy.add(library); |
| 282 } |
| 283 } |
| 284 |
| 285 // Propegate the modified bit to anything that references a modified file. |
| 286 void setModified(LibraryInfo library) { |
| 287 if (library.modified) return; |
| 288 library.modified = true; |
| 289 library.referencedBy.forEach(setModified); |
| 290 } |
| 291 transformed.forEach(setModified); |
| 292 |
| 293 for (var library in libraries) { |
| 294 // We don't need this anymore, so free it. |
| 295 library.referencedBy = null; |
| 296 |
| 297 if (!library.modified) continue; |
| 298 |
| 299 var fileOutputPath = _pathInfo.outputLibraryPath(library); |
| 300 |
| 301 // Fix imports of modified files to use the generated path. |
| 302 for (var directive in library.userCode.directives) { |
| 303 var importPath = _getDirectivePath(library, directive.uri); |
| 304 if (importPath == null) continue; |
| 305 var importInfo = info[importPath]; |
| 306 |
| 307 if (importInfo.modified && !directive.generated) { |
| 308 // Use the generated URI for this file. |
| 309 directive.generated = true; |
| 310 directive.uri = _pathInfo.outputLibraryPath(importInfo) |
| 311 .relativeTo(fileOutputPath.directoryPath).toString(); |
| 312 } |
| 313 } |
| 314 |
| 315 // Components will get emitted by WebComponentEmitter, and the |
| 316 // entry point will get emitted by MainPageEmitter. |
| 317 // So we only need to worry about other .dart files. |
| 318 if (library is FileInfo && library.htmlFile == null) { |
| 319 // Add an output file for the transformed .dart code: |
| 320 output.add(new OutputFile(fileOutputPath, |
| 321 emitDartFile(library, _pathInfo), source: library.inputPath)); |
| 322 } |
| 323 } |
| 324 } |
| 325 |
| 199 /** Run the analyzer on every input html file. */ | 326 /** Run the analyzer on every input html file. */ |
| 200 void _analyze() { | 327 void _analyze() { |
| 201 var uniqueIds = new IntIterator(); | 328 var uniqueIds = new IntIterator(); |
| 202 for (var file in files) { | 329 for (var file in files) { |
| 203 if (file.isDart) continue; | 330 if (file.isDart) continue; |
| 204 _time('Analyzed contents', file.path, | 331 _time('Analyzed contents', file.path, |
| 205 () => analyzeFile(file, info, uniqueIds, _messages)); | 332 () => analyzeFile(file, info, uniqueIds, _messages)); |
| 206 } | 333 } |
| 207 } | 334 } |
| 208 | 335 |
| (...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 311 _messages.warning('file should start with <!DOCTYPE html> ' | 438 _messages.warning('file should start with <!DOCTYPE html> ' |
| 312 'to avoid the possibility of it being parsed in quirks mode in IE. ' | 439 'to avoid the possibility of it being parsed in quirks mode in IE. ' |
| 313 'See http://www.w3.org/TR/html5-diff/#doctype', | 440 'See http://www.w3.org/TR/html5-diff/#doctype', |
| 314 doctype.sourceSpan, file: file.path); | 441 doctype.sourceSpan, file: file.path); |
| 315 } | 442 } |
| 316 } | 443 } |
| 317 document.nodes.insertAt(commentIndex, parseFragment( | 444 document.nodes.insertAt(commentIndex, parseFragment( |
| 318 '\n<!-- This file was auto-generated from ${file.path}. -->\n')); | 445 '\n<!-- This file was auto-generated from ${file.path}. -->\n')); |
| 319 } | 446 } |
| 320 } | 447 } |
| 321 | |
| 322 | |
| OLD | NEW |