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.userCode = parseDartCode(fileInfo.path, dartFile.code, _messages); |
194 fileInfo.userCode = parseDartCode(fileInfo.inlinedCode, | 206 |
195 fileInfo.path, messages:_messages); | 207 _processImports(fileInfo); |
196 if (fileInfo.userCode.partOf != null) { | 208 } |
197 _messages.error('expected a library, not a part.', null, | 209 |
198 file: dartFile.path); | 210 void _processImports(LibraryInfo library) { |
211 if (library.userCode == null) return; | |
212 | |
213 for (var directive in library.userCode.directives) { | |
214 var src = _getDirectivePath(library, directive); | |
215 if (src == null) continue; | |
216 if (!_processed.contains(src)) { | |
217 _processed.add(src); | |
218 _tasks.add(_parseDartFile(src).then(_processDartFile)); | |
219 } | |
199 } | 220 } |
200 } | 221 } |
201 | 222 |
223 Path _getDirectivePath(LibraryInfo libInfo, Directive directive) { | |
224 var uri = getDirectiveUri(directive).value; | |
225 if (uri.startsWith('dart:')) return null; | |
226 | |
227 if (uri.startsWith('package:')) { | |
228 // Don't process our own package -- we'll implement @observable manually. | |
229 if (uri.startsWith('package:web_ui/')) return null; | |
230 | |
231 return _mainPath.directoryPath.join(new Path('packages')) | |
232 .join(new Path(uri.substring(8))); | |
233 } else { | |
234 return libInfo.inputPath.directoryPath.join(new Path(uri)); | |
235 } | |
236 } | |
237 | |
238 /** | |
239 * Transform Dart source code. | |
240 * Currently, the only transformation is [transformObservables]. | |
241 * Calls _emitModifiedDartFiles to write the transformed files. | |
242 */ | |
243 void _transformDart() { | |
244 var libraries = _findAllDartLibraries(); | |
245 | |
246 var transformed = []; | |
247 for (var library in libraries) { | |
248 if (transformObservables(library.userCode, _messages)) { | |
249 // TODO(jmesserly): what about ObservableList/Map/Set? | |
Siggi Cherem (dart-lang)
2013/02/13 01:43:24
how about if you have an import to those libraries
Jennifer Messerly
2013/02/13 05:43:15
Yeah, the tricky part is they can be imported tran
| |
250 _useObservers = true; | |
251 transformed.add(library); | |
252 } | |
253 } | |
254 | |
255 _findModifiedDartFiles(libraries, transformed); | |
256 | |
257 libraries.forEach(_fixImports); | |
258 | |
259 _emitModifiedDartFiles(libraries); | |
260 } | |
261 | |
262 /** | |
263 * Finds all Dart code libraries. | |
264 * Each library will have [LibraryInfo.userCode] that is non-null. | |
265 * Also each userCode will be unique. | |
266 */ | |
267 List<LibraryInfo> _findAllDartLibraries() { | |
268 var libs = <LibraryInfo>[]; | |
269 void _addLibrary(LibraryInfo lib) { | |
270 if (lib.userCode != null && lib.externalCode == null) { | |
271 libs.add(lib); | |
272 } | |
273 } | |
274 | |
275 for (var sourceFile in files) { | |
276 var file = info[sourceFile.path]; | |
277 _addLibrary(file); | |
278 file.declaredComponents.forEach(_addLibrary); | |
279 } | |
280 | |
281 // Assert that each file path is unique. | |
282 assert(_uniquePaths(libs)); | |
283 return libs; | |
284 } | |
285 | |
286 bool _uniquePaths(List<LibraryInfo> libs) { | |
287 var seen = new Set(); | |
288 for (var lib in libs) { | |
289 if (seen.contains(lib.userCode)) { | |
290 throw new StateError('internal error: ' | |
291 'duplicate user code for ${lib.inputPath}. Files were: $files'); | |
292 } | |
293 seen.add(lib.userCode); | |
294 } | |
295 return true; | |
296 } | |
297 | |
298 /** | |
299 * Queue modified Dart files to be written. | |
300 * This will not write files that are handled by [WebComponentEmitter] and | |
301 * [MainPageEmitter]. | |
302 */ | |
303 void _emitModifiedDartFiles(List<LibraryInfo> libraries) { | |
304 for (var lib in libraries) { | |
305 // Components will get emitted by WebComponentEmitter, and the | |
306 // entry point will get emitted by MainPageEmitter. | |
307 // So we only need to worry about other .dart files. | |
308 if (lib.modified && lib is FileInfo && | |
309 lib.htmlFile == null && !lib.isEntryPoint) { | |
310 | |
311 var printer = emitDartFile(lib, _pathInfo); | |
312 _emitFile(lib, printer, lib.inputPath); | |
313 } | |
314 } | |
315 } | |
316 | |
317 /** | |
318 * This method computes which Dart files have been modified, starting | |
319 * from [transformed] and marking recursively through all files that import | |
320 * the modified files. | |
321 */ | |
322 void _findModifiedDartFiles(List<LibraryInfo> libraries, | |
323 List<FileInfo> transformed) { | |
324 | |
325 if (transformed.length == 0) return; | |
326 | |
327 // Compute files that reference each file, then use this information to | |
328 // flip the modified bit transitively. This is a lot simpler than trying | |
329 // to compute it the other way because of circular references. | |
330 for (var library in libraries) { | |
331 for (var directive in library.userCode.directives) { | |
332 var importPath = _getDirectivePath(library, directive); | |
333 if (importPath == null) continue; | |
334 | |
335 var importInfo = info[importPath]; | |
336 if (importInfo != null) { | |
337 importInfo.referencedBy.add(library); | |
338 } | |
339 } | |
340 } | |
341 | |
342 // Propegate the modified bit to anything that references a modified file. | |
343 void setModified(LibraryInfo library) { | |
344 if (library.modified) return; | |
345 library.modified = true; | |
346 library.referencedBy.forEach(setModified); | |
347 } | |
348 transformed.forEach(setModified); | |
349 | |
350 for (var library in libraries) { | |
351 // We don't need this anymore, so free it. | |
352 library.referencedBy = null; | |
353 } | |
354 } | |
355 | |
356 void _fixImports(LibraryInfo library) { | |
357 var fileOutputPath = _pathInfo.outputLibraryPath(library); | |
358 | |
359 // Fix imports. Modified files must use the generated path, otherwise | |
360 // we need to make the path relative to the input. | |
361 for (var directive in library.userCode.directives) { | |
362 var importPath = _getDirectivePath(library, directive); | |
363 if (importPath == null) continue; | |
364 var importInfo = info[importPath]; | |
365 if (importInfo == null) continue; | |
366 | |
367 String newUri; | |
368 if (importInfo.modified) { | |
369 // Use the generated URI for this file. | |
370 newUri = _pathInfo.outputLibraryPath(importInfo) | |
371 .relativeTo(fileOutputPath.directoryPath).toString(); | |
Siggi Cherem (dart-lang)
2013/02/13 01:43:24
newUri = _pathInfo.relativePath(library, importInf
Jennifer Messerly
2013/02/13 05:43:15
Done.
| |
372 } else { | |
373 // Get the relative path to the input file. | |
374 newUri = _pathInfo.transformUrl(library.inputPath, | |
375 getDirectiveUri(directive).value); | |
376 } | |
377 setDirectiveUri(directive, createStringLiteral(newUri)); | |
378 } | |
379 } | |
380 | |
202 /** Run the analyzer on every input html file. */ | 381 /** Run the analyzer on every input html file. */ |
203 void _analyze() { | 382 void _analyze() { |
204 var uniqueIds = new IntIterator(); | 383 var uniqueIds = new IntIterator(); |
205 for (var file in files) { | 384 for (var file in files) { |
206 if (file.isDart) continue; | 385 if (file.isDart) continue; |
207 _time('Analyzed contents', file.path, | 386 _time('Analyzed contents', file.path, |
208 () => analyzeFile(file, info, uniqueIds, _messages, | 387 () => analyzeFile(file, info, uniqueIds, _messages, |
209 cssPolyfill: options.processCss)); | 388 cssPolyfill: options.processCss)); |
210 } | 389 } |
211 } | 390 } |
(...skipping 25 matching lines...) Expand all Loading... | |
237 } | 416 } |
238 | 417 |
239 /** Generate an html file with the (trimmed down) main html page. */ | 418 /** Generate an html file with the (trimmed down) main html page. */ |
240 void _emitMainHtml(SourceFile file) { | 419 void _emitMainHtml(SourceFile file) { |
241 var fileInfo = info[file.path]; | 420 var fileInfo = info[file.path]; |
242 | 421 |
243 var bootstrapName = '${file.path.filename}_bootstrap.dart'; | 422 var bootstrapName = '${file.path.filename}_bootstrap.dart'; |
244 var bootstrapPath = file.path.directoryPath.append(bootstrapName); | 423 var bootstrapPath = file.path.directoryPath.append(bootstrapName); |
245 var bootstrapOutPath = _pathInfo.outputPath(bootstrapPath, ''); | 424 var bootstrapOutPath = _pathInfo.outputPath(bootstrapPath, ''); |
246 output.add(new OutputFile(bootstrapOutPath, codegen.bootstrapCode( | 425 output.add(new OutputFile(bootstrapOutPath, codegen.bootstrapCode( |
247 _pathInfo.relativePath(new FileInfo(bootstrapPath), fileInfo)))); | 426 _pathInfo.relativePath(new FileInfo(bootstrapPath), fileInfo), |
427 _useObservers))); | |
248 | 428 |
249 var document = file.document; | 429 var document = file.document; |
250 bool dartLoaderFound = false; | 430 bool dartLoaderFound = false; |
251 for (var script in document.queryAll('script')) { | 431 for (var script in document.queryAll('script')) { |
252 var src = script.attributes['src']; | 432 var src = script.attributes['src']; |
253 if (src != null && src.split('/').last == 'dart.js') { | 433 if (src != null && src.split('/').last == 'dart.js') { |
254 dartLoaderFound = true; | 434 dartLoaderFound = true; |
255 break; | 435 break; |
256 } | 436 } |
257 } | 437 } |
(...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
364 print('\nComponent: ${info.tagName}'); | 544 print('\nComponent: ${info.tagName}'); |
365 print('==========\n'); | 545 print('==========\n'); |
366 print(treeToDebugString(info.styleSheet)); | 546 print(treeToDebugString(info.styleSheet)); |
367 print(emitStyleSheet(info.styleSheet)); | 547 print(emitStyleSheet(info.styleSheet)); |
368 } | 548 } |
369 } | 549 } |
370 | 550 |
371 super.visitComponentInfo(info); | 551 super.visitComponentInfo(info); |
372 } | 552 } |
373 } | 553 } |
OLD | NEW |