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 |