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 'package:analyzer_experimental/src/generated/ast.dart' show Directive; |
| 10 import 'package:csslib/parser.dart' as css; |
| 11 import 'package:csslib/visitor.dart' show InfoVisitor, StyleSheet; |
9 import 'package:html5lib/dom.dart'; | 12 import 'package:html5lib/dom.dart'; |
10 import 'package:html5lib/parser.dart'; | 13 import 'package:html5lib/parser.dart'; |
11 import 'package:csslib/parser.dart' as css; | |
12 import 'package:csslib/visitor.dart'; | |
13 | 14 |
14 import 'analyzer.dart'; | 15 import 'analyzer.dart'; |
15 import 'code_printer.dart'; | 16 import 'code_printer.dart'; |
16 import 'codegen.dart' as codegen; | 17 import 'codegen.dart' as codegen; |
17 import 'directive_parser.dart' show parseDartCode; | 18 import 'dart_parser.dart'; |
18 import 'emitters.dart'; | 19 import 'emitters.dart'; |
19 import 'file_system.dart'; | 20 import 'file_system.dart'; |
20 import 'file_system/path.dart'; | 21 import 'file_system/path.dart'; |
21 import 'files.dart'; | 22 import 'files.dart'; |
22 import 'html_cleaner.dart'; | 23 import 'html_cleaner.dart'; |
23 import 'info.dart'; | 24 import 'info.dart'; |
24 import 'messages.dart'; | 25 import 'messages.dart'; |
| 26 import 'observable_transform.dart' show transformObservables; |
25 import 'options.dart'; | 27 import 'options.dart'; |
26 import 'utils.dart'; | 28 import 'utils.dart'; |
27 | 29 |
28 /** | 30 /** |
29 * Parses an HTML file [contents] and returns a DOM-like tree. | 31 * Parses an HTML file [contents] and returns a DOM-like tree. |
30 * Note that [contents] will be a [String] if coming from a browser-based | 32 * Note that [contents] will be a [String] if coming from a browser-based |
31 * [FileSystem], or it will be a [List<int>] if running on the command line. | 33 * [FileSystem], or it will be a [List<int>] if running on the command line. |
32 * | 34 * |
33 * Adds emitted error/warning to [messages], if [messages] is supplied. | 35 * Adds emitted error/warning to [messages], if [messages] is supplied. |
34 */ | 36 */ |
(...skipping 14 matching lines...) Expand all Loading... |
49 class Compiler { | 51 class Compiler { |
50 final FileSystem fileSystem; | 52 final FileSystem fileSystem; |
51 final CompilerOptions options; | 53 final CompilerOptions options; |
52 final List<SourceFile> files = <SourceFile>[]; | 54 final List<SourceFile> files = <SourceFile>[]; |
53 final List<OutputFile> output = <OutputFile>[]; | 55 final List<OutputFile> output = <OutputFile>[]; |
54 | 56 |
55 Path _mainPath; | 57 Path _mainPath; |
56 PathInfo _pathInfo; | 58 PathInfo _pathInfo; |
57 Messages _messages; | 59 Messages _messages; |
58 | 60 |
| 61 FutureGroup _tasks; |
| 62 Set _processed; |
| 63 |
| 64 bool _useObservers = false; |
| 65 |
59 /** Information about source [files] given their href. */ | 66 /** Information about source [files] given their href. */ |
60 final Map<Path, FileInfo> info = new SplayTreeMap<Path, FileInfo>(); | 67 final Map<Path, FileInfo> info = new SplayTreeMap<Path, FileInfo>(); |
61 | 68 |
62 /** | 69 /** |
63 * Creates a compiler with [options] using [fileSystem]. | 70 * Creates a compiler with [options] using [fileSystem]. |
64 * | 71 * |
65 * Adds emitted error/warning messages to [messages], if [messages] is | 72 * Adds emitted error/warning messages to [messages], if [messages] is |
66 * supplied. | 73 * supplied. |
67 */ | 74 */ |
68 Compiler(this.fileSystem, this.options, this._messages, {String currentDir}) { | 75 Compiler(this.fileSystem, this.options, this._messages, {String currentDir}) { |
(...skipping 26 matching lines...) Expand all Loading... |
95 | 102 |
96 /** Compile the application starting from the given [mainFile]. */ | 103 /** Compile the application starting from the given [mainFile]. */ |
97 Future run() { | 104 Future run() { |
98 if (_mainPath.filename.endsWith('.dart')) { | 105 if (_mainPath.filename.endsWith('.dart')) { |
99 _messages.error("Please provide an HTML file as your entry point.", | 106 _messages.error("Please provide an HTML file as your entry point.", |
100 null, file: _mainPath); | 107 null, file: _mainPath); |
101 return new Future.immediate(null); | 108 return new Future.immediate(null); |
102 } | 109 } |
103 return _parseAndDiscover(_mainPath).then((_) { | 110 return _parseAndDiscover(_mainPath).then((_) { |
104 _analyze(); | 111 _analyze(); |
| 112 _transformDart(); |
105 _emit(); | 113 _emit(); |
106 }); | 114 }); |
107 } | 115 } |
108 | 116 |
109 /** | 117 /** |
110 * Asynchronously parse [inputFile] and transitively discover web components | 118 * Asynchronously parse [inputFile] and transitively discover web components |
111 * to load and parse. Returns a future that completes when all files are | 119 * to load and parse. Returns a future that completes when all files are |
112 * processed. | 120 * processed. |
113 */ | 121 */ |
114 Future _parseAndDiscover(Path inputFile) { | 122 Future _parseAndDiscover(Path inputFile) { |
115 var tasks = new FutureGroup(); | 123 _tasks = new FutureGroup(); |
116 bool isEntry = !options.componentsOnly; | 124 _processed = new Set(); |
| 125 _processed.add(inputFile); |
| 126 _tasks.add(_parseHtmlFile(inputFile).then(_processHtmlFile)); |
| 127 return _tasks.future; |
| 128 } |
117 | 129 |
118 var processed = new Set(); | 130 bool _shouldProcessFile(SourceFile file) => |
119 processHtmlFile(SourceFile file) { | 131 file != null && _pathInfo.checkInputPath(file.path, _messages); |
120 if (file == null) return; | |
121 if (!_pathInfo.checkInputPath(file.path, _messages)) return; | |
122 | 132 |
123 files.add(file); | 133 void _processHtmlFile(SourceFile file) { |
| 134 if (!_shouldProcessFile(file)) return; |
124 | 135 |
125 var fileInfo = _time('Analyzed definitions', file.path, | 136 bool isEntryPoint = _processed.length == 1; |
126 () => analyzeDefinitions(file, _messages, isEntryPoint: isEntry)); | |
127 isEntry = false; | |
128 info[file.path] = fileInfo; | |
129 | 137 |
130 // Load component files referenced by [file]. | 138 files.add(file); |
131 for (var href in fileInfo.componentLinks) { | |
132 if (!processed.contains(href)) { | |
133 processed.add(href); | |
134 tasks.add(_parseHtmlFile(href).then(processHtmlFile)); | |
135 } | |
136 } | |
137 | 139 |
138 // Load .dart files being referenced in the page. | 140 var fileInfo = _time('Analyzed definitions', file.path, |
139 var src = fileInfo.externalFile; | 141 () => analyzeDefinitions(file, _messages, isEntryPoint: isEntryPoint)); |
140 if (src != null && !processed.contains(src)) { | 142 info[file.path] = fileInfo; |
141 processed.add(src); | |
142 tasks.add(_parseDartFile(src).then(_addDartFile)); | |
143 } | |
144 | 143 |
145 // Load .dart files being referenced in components. | 144 _processImports(fileInfo); |
146 for (var component in fileInfo.declaredComponents) { | 145 |
147 var src = component.externalFile; | 146 // Load component files referenced by [file]. |
148 if (src != null && !processed.contains(src)) { | 147 for (var href in fileInfo.componentLinks) { |
149 processed.add(src); | 148 if (!_processed.contains(href)) { |
150 tasks.add(_parseDartFile(src).then(_addDartFile)); | 149 _processed.add(href); |
151 } | 150 _tasks.add(_parseHtmlFile(href).then(_processHtmlFile)); |
152 } | 151 } |
153 } | 152 } |
154 | 153 |
155 processed.add(inputFile); | 154 // Load .dart files being referenced in the page. |
156 tasks.add(_parseHtmlFile(inputFile).then(processHtmlFile)); | 155 var src = fileInfo.externalFile; |
157 return tasks.future; | 156 if (src != null && !_processed.contains(src)) { |
| 157 _processed.add(src); |
| 158 _tasks.add(_parseDartFile(src).then(_processDartFile)); |
| 159 } |
| 160 |
| 161 // Load .dart files being referenced in components. |
| 162 for (var component in fileInfo.declaredComponents) { |
| 163 var src = component.externalFile; |
| 164 if (src != null && !_processed.contains(src)) { |
| 165 _processed.add(src); |
| 166 _tasks.add(_parseDartFile(src).then(_processDartFile)); |
| 167 } else if (component.userCode != null) { |
| 168 _processImports(component); |
| 169 } |
| 170 } |
158 } | 171 } |
159 | 172 |
160 /** Asynchronously parse [path] as an .html file. */ | 173 /** Asynchronously parse [path] as an .html file. */ |
161 Future<SourceFile> _parseHtmlFile(Path path) { | 174 Future<SourceFile> _parseHtmlFile(Path path) { |
162 return fileSystem.readTextOrBytes(path).then((source) { | 175 return fileSystem.readTextOrBytes(path).then((source) { |
163 var file = new SourceFile(path); | 176 var file = new SourceFile(path); |
164 file.document = _time('Parsed', path, | 177 file.document = _time('Parsed', path, |
165 () => parseHtml(source, path, _messages)); | 178 () => parseHtml(source, path, _messages)); |
166 return file; | 179 return file; |
167 }) | 180 }) |
168 .catchError((e) => _readError(e, path)); | 181 .catchError((e) => _readError(e, path)); |
169 } | 182 } |
170 | 183 |
171 /** Parse [filename] and treat it as a .dart file. */ | 184 /** Parse [filename] and treat it as a .dart file. */ |
172 Future<SourceFile> _parseDartFile(Path path) { | 185 Future<SourceFile> _parseDartFile(Path path) { |
173 return fileSystem.readText(path) | 186 return fileSystem.readText(path) |
174 .then((code) => new SourceFile(path, isDart: true)..code = code) | 187 .then((code) => new SourceFile(path, isDart: true)..code = code) |
175 .catchError((e) => _readError(e, path)); | 188 .catchError((e) => _readError(e, path)); |
176 } | 189 } |
177 | 190 |
178 SourceFile _readError(error, Path path) { | 191 SourceFile _readError(error, Path path) { |
179 _messages.error('exception while reading file, original message:\n $error', | 192 _messages.error('exception while reading file, original message:\n $error', |
180 null, file: path); | 193 null, file: path); |
181 | 194 |
182 return null; | 195 return null; |
183 } | 196 } |
184 | 197 |
185 void _addDartFile(SourceFile dartFile) { | 198 void _processDartFile(SourceFile dartFile) { |
186 if (dartFile == null) return; | 199 if (!_shouldProcessFile(dartFile)) return; |
187 if (!_pathInfo.checkInputPath(dartFile.path, _messages)) return; | |
188 | 200 |
189 files.add(dartFile); | 201 files.add(dartFile); |
190 | 202 |
191 var fileInfo = new FileInfo(dartFile.path); | 203 var fileInfo = new FileInfo(dartFile.path); |
192 info[dartFile.path] = fileInfo; | 204 info[dartFile.path] = fileInfo; |
193 fileInfo.inlinedCode = dartFile.code; | 205 fileInfo.inlinedCode = |
194 fileInfo.userCode = parseDartCode(fileInfo.inlinedCode, | 206 parseDartCode(fileInfo.path, dartFile.code, _messages); |
195 fileInfo.path, messages:_messages); | 207 |
196 if (fileInfo.userCode.partOf != null) { | 208 _processImports(fileInfo); |
197 _messages.error('expected a library, not a part.', null, | 209 } |
198 file: dartFile.path); | 210 |
| 211 void _processImports(LibraryInfo library) { |
| 212 if (library.userCode == null) return; |
| 213 |
| 214 for (var directive in library.userCode.directives) { |
| 215 var src = _getDirectivePath(library, directive); |
| 216 if (src == null) { |
| 217 var uri = getDirectiveUri(directive).value; |
| 218 if (uri.startsWith('package:web_ui/observe')) { |
| 219 _useObservers = true; |
| 220 } |
| 221 } else if (!_processed.contains(src)) { |
| 222 _processed.add(src); |
| 223 _tasks.add(_parseDartFile(src).then(_processDartFile)); |
| 224 } |
199 } | 225 } |
200 } | 226 } |
201 | 227 |
| 228 Path _getDirectivePath(LibraryInfo libInfo, Directive directive) { |
| 229 var uri = getDirectiveUri(directive).value; |
| 230 if (uri.startsWith('dart:')) return null; |
| 231 |
| 232 if (uri.startsWith('package:')) { |
| 233 // Don't process our own package -- we'll implement @observable manually. |
| 234 if (uri.startsWith('package:web_ui/')) return null; |
| 235 |
| 236 return _mainPath.directoryPath.join(new Path('packages')) |
| 237 .join(new Path(uri.substring(8))); |
| 238 } else { |
| 239 return libInfo.inputPath.directoryPath.join(new Path(uri)); |
| 240 } |
| 241 } |
| 242 |
| 243 /** |
| 244 * Transform Dart source code. |
| 245 * Currently, the only transformation is [transformObservables]. |
| 246 * Calls _emitModifiedDartFiles to write the transformed files. |
| 247 */ |
| 248 void _transformDart() { |
| 249 var libraries = _findAllDartLibraries(); |
| 250 |
| 251 var transformed = []; |
| 252 for (var library in libraries) { |
| 253 if (transformObservables(library.userCode, _messages)) { |
| 254 // TODO(jmesserly): what about ObservableList/Map/Set? |
| 255 _useObservers = true; |
| 256 transformed.add(library); |
| 257 } |
| 258 } |
| 259 |
| 260 _findModifiedDartFiles(libraries, transformed); |
| 261 |
| 262 libraries.forEach(_fixImports); |
| 263 |
| 264 _emitModifiedDartFiles(libraries); |
| 265 } |
| 266 |
| 267 /** |
| 268 * Finds all Dart code libraries. |
| 269 * Each library will have [LibraryInfo.inlinedCode] that is non-null. |
| 270 * Also each inlinedCode will be unique. |
| 271 */ |
| 272 List<LibraryInfo> _findAllDartLibraries() { |
| 273 var libs = <LibraryInfo>[]; |
| 274 void _addLibrary(LibraryInfo lib) { |
| 275 if (lib.inlinedCode != null) libs.add(lib); |
| 276 } |
| 277 |
| 278 for (var sourceFile in files) { |
| 279 var file = info[sourceFile.path]; |
| 280 _addLibrary(file); |
| 281 file.declaredComponents.forEach(_addLibrary); |
| 282 } |
| 283 |
| 284 // Assert that each file path is unique. |
| 285 assert(_uniquePaths(libs)); |
| 286 return libs; |
| 287 } |
| 288 |
| 289 bool _uniquePaths(List<LibraryInfo> libs) { |
| 290 var seen = new Set(); |
| 291 for (var lib in libs) { |
| 292 if (seen.contains(lib.inlinedCode)) { |
| 293 throw new StateError('internal error: ' |
| 294 'duplicate user code for ${lib.inputPath}. Files were: $files'); |
| 295 } |
| 296 seen.add(lib.inlinedCode); |
| 297 } |
| 298 return true; |
| 299 } |
| 300 |
| 301 /** |
| 302 * Queue modified Dart files to be written. |
| 303 * This will not write files that are handled by [WebComponentEmitter] and |
| 304 * [MainPageEmitter]. |
| 305 */ |
| 306 void _emitModifiedDartFiles(List<LibraryInfo> libraries) { |
| 307 for (var lib in libraries) { |
| 308 // Components will get emitted by WebComponentEmitter, and the |
| 309 // entry point will get emitted by MainPageEmitter. |
| 310 // So we only need to worry about other .dart files. |
| 311 if (lib.modified && lib is FileInfo && |
| 312 lib.htmlFile == null && !lib.isEntryPoint) { |
| 313 |
| 314 var printer = emitDartFile(lib, _pathInfo); |
| 315 _emitFile(lib, printer, lib.inputPath); |
| 316 } |
| 317 } |
| 318 } |
| 319 |
| 320 /** |
| 321 * This method computes which Dart files have been modified, starting |
| 322 * from [transformed] and marking recursively through all files that import |
| 323 * the modified files. |
| 324 */ |
| 325 void _findModifiedDartFiles(List<LibraryInfo> libraries, |
| 326 List<FileInfo> transformed) { |
| 327 |
| 328 if (transformed.length == 0) return; |
| 329 |
| 330 // Compute files that reference each file, then use this information to |
| 331 // flip the modified bit transitively. This is a lot simpler than trying |
| 332 // to compute it the other way because of circular references. |
| 333 for (var library in libraries) { |
| 334 for (var directive in library.userCode.directives) { |
| 335 var importPath = _getDirectivePath(library, directive); |
| 336 if (importPath == null) continue; |
| 337 |
| 338 var importInfo = info[importPath]; |
| 339 if (importInfo != null) { |
| 340 importInfo.referencedBy.add(library); |
| 341 } |
| 342 } |
| 343 } |
| 344 |
| 345 // Propegate the modified bit to anything that references a modified file. |
| 346 void setModified(LibraryInfo library) { |
| 347 if (library.modified) return; |
| 348 library.modified = true; |
| 349 library.referencedBy.forEach(setModified); |
| 350 } |
| 351 transformed.forEach(setModified); |
| 352 |
| 353 for (var library in libraries) { |
| 354 // We don't need this anymore, so free it. |
| 355 library.referencedBy = null; |
| 356 } |
| 357 } |
| 358 |
| 359 void _fixImports(LibraryInfo library) { |
| 360 var fileOutputPath = _pathInfo.outputLibraryPath(library); |
| 361 |
| 362 // Fix imports. Modified files must use the generated path, otherwise |
| 363 // we need to make the path relative to the input. |
| 364 for (var directive in library.userCode.directives) { |
| 365 var importPath = _getDirectivePath(library, directive); |
| 366 if (importPath == null) continue; |
| 367 var importInfo = info[importPath]; |
| 368 if (importInfo == null) continue; |
| 369 |
| 370 String newUri; |
| 371 if (importInfo.modified) { |
| 372 // Use the generated URI for this file. |
| 373 newUri = _pathInfo.relativePath(library, importInfo).toString(); |
| 374 } else { |
| 375 // Get the relative path to the input file. |
| 376 newUri = _pathInfo.transformUrl(library.inputPath, |
| 377 getDirectiveUri(directive).value); |
| 378 } |
| 379 setDirectiveUri(directive, createStringLiteral(newUri)); |
| 380 } |
| 381 } |
| 382 |
202 /** Run the analyzer on every input html file. */ | 383 /** Run the analyzer on every input html file. */ |
203 void _analyze() { | 384 void _analyze() { |
204 var uniqueIds = new IntIterator(); | 385 var uniqueIds = new IntIterator(); |
205 for (var file in files) { | 386 for (var file in files) { |
206 if (file.isDart) continue; | 387 if (file.isDart) continue; |
207 _time('Analyzed contents', file.path, | 388 _time('Analyzed contents', file.path, |
208 () => analyzeFile(file, info, uniqueIds, _messages, | 389 () => analyzeFile(file, info, uniqueIds, _messages, |
209 cssPolyfill: options.processCss)); | 390 cssPolyfill: options.processCss)); |
210 } | 391 } |
211 } | 392 } |
(...skipping 25 matching lines...) Expand all Loading... |
237 } | 418 } |
238 | 419 |
239 /** Generate an html file with the (trimmed down) main html page. */ | 420 /** Generate an html file with the (trimmed down) main html page. */ |
240 void _emitMainHtml(SourceFile file) { | 421 void _emitMainHtml(SourceFile file) { |
241 var fileInfo = info[file.path]; | 422 var fileInfo = info[file.path]; |
242 | 423 |
243 var bootstrapName = '${file.path.filename}_bootstrap.dart'; | 424 var bootstrapName = '${file.path.filename}_bootstrap.dart'; |
244 var bootstrapPath = file.path.directoryPath.append(bootstrapName); | 425 var bootstrapPath = file.path.directoryPath.append(bootstrapName); |
245 var bootstrapOutPath = _pathInfo.outputPath(bootstrapPath, ''); | 426 var bootstrapOutPath = _pathInfo.outputPath(bootstrapPath, ''); |
246 output.add(new OutputFile(bootstrapOutPath, codegen.bootstrapCode( | 427 output.add(new OutputFile(bootstrapOutPath, codegen.bootstrapCode( |
247 _pathInfo.relativePath(new FileInfo(bootstrapPath), fileInfo)))); | 428 _pathInfo.relativePath(new FileInfo(bootstrapPath), fileInfo), |
| 429 _useObservers))); |
248 | 430 |
249 var document = file.document; | 431 var document = file.document; |
250 bool dartLoaderFound = false; | 432 bool dartLoaderFound = false; |
251 for (var script in document.queryAll('script')) { | 433 for (var script in document.queryAll('script')) { |
252 var src = script.attributes['src']; | 434 var src = script.attributes['src']; |
253 if (src != null && src.split('/').last == 'dart.js') { | 435 if (src != null && src.split('/').last == 'dart.js') { |
254 dartLoaderFound = true; | 436 dartLoaderFound = true; |
255 break; | 437 break; |
256 } | 438 } |
257 } | 439 } |
(...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
364 print('\nComponent: ${info.tagName}'); | 546 print('\nComponent: ${info.tagName}'); |
365 print('==========\n'); | 547 print('==========\n'); |
366 print(treeToDebugString(info.styleSheet)); | 548 print(treeToDebugString(info.styleSheet)); |
367 print(emitStyleSheet(info.styleSheet)); | 549 print(emitStyleSheet(info.styleSheet)); |
368 } | 550 } |
369 } | 551 } |
370 | 552 |
371 super.visitComponentInfo(info); | 553 super.visitComponentInfo(info); |
372 } | 554 } |
373 } | 555 } |
OLD | NEW |