| OLD | NEW | 
|---|
|  | (Empty) | 
| 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 |  | 
| 3 // BSD-style license that can be found in the LICENSE file. |  | 
| 4 |  | 
| 5 library compiler; |  | 
| 6 |  | 
| 7 import 'dart:async'; |  | 
| 8 import 'dart:collection' show SplayTreeMap; |  | 
| 9 |  | 
| 10 import 'package:csslib/visitor.dart' show StyleSheet, treeToDebugString; |  | 
| 11 import 'package:html5lib/dom.dart'; |  | 
| 12 import 'package:html5lib/parser.dart'; |  | 
| 13 |  | 
| 14 import 'analyzer.dart'; |  | 
| 15 import 'css_analyzer.dart' show analyzeCss, findUrlsImported, |  | 
| 16        findImportsInStyleSheet, parseCss; |  | 
| 17 import 'css_emitters.dart' show rewriteCssUris, |  | 
| 18        emitComponentStyleSheet, emitOriginalCss, emitStyleSheet; |  | 
| 19 import 'file_system.dart'; |  | 
| 20 import 'files.dart'; |  | 
| 21 import 'info.dart'; |  | 
| 22 import 'messages.dart'; |  | 
| 23 import 'compiler_options.dart'; |  | 
| 24 import 'utils.dart'; |  | 
| 25 |  | 
| 26 /** |  | 
| 27  * 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 |  | 
| 29  * [FileSystem], or it will be a [List<int>] if running on the command line. |  | 
| 30  * |  | 
| 31  * Adds emitted error/warning to [messages], if [messages] is supplied. |  | 
| 32  */ |  | 
| 33 Document parseHtml(contents, String sourcePath, Messages messages, |  | 
| 34     bool checkDocType) { |  | 
| 35   var parser = new HtmlParser(contents, generateSpans: true, |  | 
| 36       sourceUrl: sourcePath); |  | 
| 37   var document = parser.parse(); |  | 
| 38 |  | 
| 39   // Note: errors aren't fatal in HTML (unless strict mode is on). |  | 
| 40   // So just print them as warnings. |  | 
| 41   for (var e in parser.errors) { |  | 
| 42     if (checkDocType || e.errorCode != 'expected-doctype-but-got-start-tag') { |  | 
| 43       messages.warning(e.message, e.span); |  | 
| 44     } |  | 
| 45   } |  | 
| 46   return document; |  | 
| 47 } |  | 
| 48 |  | 
| 49 /** Compiles an application written with Dart web components. */ |  | 
| 50 class Compiler { |  | 
| 51   final FileSystem fileSystem; |  | 
| 52   final CompilerOptions options; |  | 
| 53   final List<SourceFile> files = <SourceFile>[]; |  | 
| 54 |  | 
| 55   String _mainPath; |  | 
| 56   String _packageRoot; |  | 
| 57   String _resetCssFile; |  | 
| 58   StyleSheet _cssResetStyleSheet; |  | 
| 59   Messages _messages; |  | 
| 60 |  | 
| 61   FutureGroup _tasks; |  | 
| 62   Set _processed; |  | 
| 63 |  | 
| 64   /** Information about source [files] given their href. */ |  | 
| 65   final Map<String, FileInfo> info = new SplayTreeMap<String, FileInfo>(); |  | 
| 66 |  | 
| 67   final GlobalInfo global = new GlobalInfo(); |  | 
| 68 |  | 
| 69   /** Creates a compiler with [options] using [fileSystem]. */ |  | 
| 70   Compiler(this.fileSystem, this.options, this._messages) { |  | 
| 71     _mainPath = options.inputFile; |  | 
| 72     var mainDir = path.dirname(_mainPath); |  | 
| 73     var baseDir = options.baseDir != null ? options.baseDir : mainDir; |  | 
| 74     _packageRoot = options.packageRoot != null ? options.packageRoot |  | 
| 75         : path.join(path.dirname(_mainPath), 'packages'); |  | 
| 76 |  | 
| 77     if (options.resetCssFile != null) { |  | 
| 78       _resetCssFile = options.resetCssFile; |  | 
| 79       if (path.isRelative(_resetCssFile)) { |  | 
| 80         // If CSS reset file path is relative from our current path. |  | 
| 81         _resetCssFile = path.resolve(_resetCssFile); |  | 
| 82       } |  | 
| 83     } |  | 
| 84 |  | 
| 85     // Normalize paths - all should be relative or absolute paths. |  | 
| 86     if (path.isAbsolute(_mainPath) || path.isAbsolute(baseDir) |  | 
| 87         || path.isAbsolute(_packageRoot)) { |  | 
| 88       if (path.isRelative(_mainPath)) _mainPath = path.resolve(_mainPath); |  | 
| 89       if (path.isRelative(baseDir)) baseDir = path.resolve(baseDir); |  | 
| 90       if (path.isRelative(_packageRoot)) { |  | 
| 91         _packageRoot = path.resolve(_packageRoot); |  | 
| 92       } |  | 
| 93     } |  | 
| 94   } |  | 
| 95 |  | 
| 96   /** Compile the application starting from the given input file. */ |  | 
| 97   Future run() { |  | 
| 98     if (path.basename(_mainPath).endsWith('.dart')) { |  | 
| 99       _messages.error("Please provide an HTML file as your entry point.", |  | 
| 100           null); |  | 
| 101       return new Future.value(null); |  | 
| 102     } |  | 
| 103     return _parseAndDiscover(_mainPath).then((_) { |  | 
| 104       _analyze(); |  | 
| 105 |  | 
| 106       // Analyze all CSS files. |  | 
| 107       _time('Analyzed Style Sheets', '', () => |  | 
| 108           analyzeCss(_packageRoot, files, info, |  | 
| 109               global.pseudoElements, _messages, |  | 
| 110               warningsAsErrors: options.warningsAsErrors)); |  | 
| 111     }); |  | 
| 112   } |  | 
| 113 |  | 
| 114   /** |  | 
| 115    * Asynchronously parse [inputFile] and transitively discover web components |  | 
| 116    * to load and parse. Returns a future that completes when all files are |  | 
| 117    * processed. |  | 
| 118    */ |  | 
| 119   Future _parseAndDiscover(String inputFile) { |  | 
| 120     _tasks = new FutureGroup(); |  | 
| 121     _processed = new Set(); |  | 
| 122     _processed.add(inputFile); |  | 
| 123     _tasks.add(_parseHtmlFile(new UrlInfo(inputFile, inputFile, null), true)); |  | 
| 124     return _tasks.future; |  | 
| 125   } |  | 
| 126 |  | 
| 127   void _processHtmlFile(UrlInfo inputUrl, SourceFile file) { |  | 
| 128     if (file == null) return; |  | 
| 129 |  | 
| 130     bool isEntryPoint = _processed.length == 1; |  | 
| 131 |  | 
| 132     files.add(file); |  | 
| 133 |  | 
| 134     var fileInfo = _time('Analyzed definitions', inputUrl.url, () { |  | 
| 135       return analyzeDefinitions(global, inputUrl, file.document, _packageRoot, |  | 
| 136         _messages); |  | 
| 137     }); |  | 
| 138     info[inputUrl.resolvedPath] = fileInfo; |  | 
| 139 |  | 
| 140     if (isEntryPoint && _resetCssFile != null) { |  | 
| 141       _processed.add(_resetCssFile); |  | 
| 142       _tasks.add(_parseCssFile(new UrlInfo(_resetCssFile, _resetCssFile, |  | 
| 143           null))); |  | 
| 144     } |  | 
| 145 |  | 
| 146     // Load component files referenced by [file]. |  | 
| 147     for (var link in fileInfo.componentLinks) { |  | 
| 148       _loadFile(link, _parseHtmlFile); |  | 
| 149     } |  | 
| 150 |  | 
| 151     // Load stylesheet files referenced by [file]. |  | 
| 152     for (var link in fileInfo.styleSheetHrefs) { |  | 
| 153       _loadFile(link, _parseCssFile); |  | 
| 154     } |  | 
| 155 |  | 
| 156     // Process any @imports inside of a <style> tag. |  | 
| 157     var urlInfos = findUrlsImported(fileInfo, fileInfo.inputUrl, _packageRoot, |  | 
| 158         file.document, _messages, options); |  | 
| 159     for (var urlInfo in urlInfos) { |  | 
| 160       _loadFile(urlInfo, _parseCssFile); |  | 
| 161     } |  | 
| 162 |  | 
| 163     // Load .dart files being referenced in components. |  | 
| 164     for (var component in fileInfo.declaredComponents) { |  | 
| 165       // Process any @imports inside of the <style> tag in a component. |  | 
| 166       var urlInfos = findUrlsImported(component, fileInfo.inputUrl, |  | 
| 167           _packageRoot, component.element, _messages, options); |  | 
| 168       for (var urlInfo in urlInfos) { |  | 
| 169         _loadFile(urlInfo, _parseCssFile); |  | 
| 170       } |  | 
| 171     } |  | 
| 172   } |  | 
| 173 |  | 
| 174   /** |  | 
| 175    * Helper function to load [urlInfo] and parse it using [loadAndParse] if it |  | 
| 176    * hasn't been loaded before. |  | 
| 177    */ |  | 
| 178   void _loadFile(UrlInfo urlInfo, Future loadAndParse(UrlInfo inputUrl)) { |  | 
| 179     if (urlInfo == null) return; |  | 
| 180     var resolvedPath = urlInfo.resolvedPath; |  | 
| 181     if (!_processed.contains(resolvedPath)) { |  | 
| 182       _processed.add(resolvedPath); |  | 
| 183       _tasks.add(loadAndParse(urlInfo)); |  | 
| 184     } |  | 
| 185   } |  | 
| 186 |  | 
| 187   /** Parse an HTML file. */ |  | 
| 188   Future _parseHtmlFile(UrlInfo inputUrl, [bool checkDocType = false]) { |  | 
| 189     var filePath = inputUrl.resolvedPath; |  | 
| 190     return fileSystem.readTextOrBytes(filePath) |  | 
| 191         .catchError((e) => _readError(e, inputUrl)) |  | 
| 192         .then((source) { |  | 
| 193           if (source == null) return; |  | 
| 194           var file = new SourceFile(filePath); |  | 
| 195           file.document = _time('Parsed', filePath, |  | 
| 196               () => parseHtml(source, filePath, _messages, checkDocType)); |  | 
| 197           _processHtmlFile(inputUrl, file); |  | 
| 198         }); |  | 
| 199   } |  | 
| 200 |  | 
| 201   /** Parse a stylesheet file. */ |  | 
| 202   Future _parseCssFile(UrlInfo inputUrl) { |  | 
| 203     if (!options.emulateScopedCss) { |  | 
| 204       return new Future<SourceFile>.value(null); |  | 
| 205     } |  | 
| 206     var filePath = inputUrl.resolvedPath; |  | 
| 207     return fileSystem.readText(filePath) |  | 
| 208         .catchError((e) => _readError(e, inputUrl, isWarning: true)) |  | 
| 209         .then((code) { |  | 
| 210           if (code == null) return; |  | 
| 211           var file = new SourceFile(filePath, type: SourceFile.STYLESHEET); |  | 
| 212           file.code = code; |  | 
| 213           _processCssFile(inputUrl, file); |  | 
| 214         }); |  | 
| 215   } |  | 
| 216 |  | 
| 217 |  | 
| 218   SourceFile _readError(error, UrlInfo inputUrl, {isWarning: false}) { |  | 
| 219     var message = 'unable to open file "${inputUrl.resolvedPath}"'; |  | 
| 220     if (options.verbose) { |  | 
| 221       message = '$message. original message:\n $error'; |  | 
| 222     } |  | 
| 223     if (isWarning) { |  | 
| 224       _messages.warning(message, inputUrl.sourceSpan); |  | 
| 225     } else { |  | 
| 226       _messages.error(message, inputUrl.sourceSpan); |  | 
| 227     } |  | 
| 228     return null; |  | 
| 229   } |  | 
| 230 |  | 
| 231   void _processCssFile(UrlInfo inputUrl, SourceFile cssFile) { |  | 
| 232     if (cssFile == null) return; |  | 
| 233 |  | 
| 234     files.add(cssFile); |  | 
| 235 |  | 
| 236     var fileInfo = new FileInfo(inputUrl); |  | 
| 237     info[inputUrl.resolvedPath] = fileInfo; |  | 
| 238 |  | 
| 239     var styleSheet = parseCss(cssFile.code, _messages, options); |  | 
| 240     if (inputUrl.url == _resetCssFile) { |  | 
| 241       _cssResetStyleSheet = styleSheet; |  | 
| 242     } else if (styleSheet != null) { |  | 
| 243       _resolveStyleSheetImports(inputUrl, cssFile.path, styleSheet); |  | 
| 244       fileInfo.styleSheets.add(styleSheet); |  | 
| 245     } |  | 
| 246   } |  | 
| 247 |  | 
| 248   /** Load and parse all style sheets referenced with an @imports. */ |  | 
| 249   void _resolveStyleSheetImports(UrlInfo inputUrl, String processingFile, |  | 
| 250       StyleSheet styleSheet) { |  | 
| 251     var urlInfos = _time('CSS imports', processingFile, () => |  | 
| 252         findImportsInStyleSheet(styleSheet, _packageRoot, inputUrl, _messages)); |  | 
| 253 |  | 
| 254     for (var urlInfo in urlInfos) { |  | 
| 255       if (urlInfo == null) break; |  | 
| 256       // Load any @imported stylesheet files referenced in this style sheet. |  | 
| 257       _loadFile(urlInfo, _parseCssFile); |  | 
| 258     } |  | 
| 259   } |  | 
| 260 |  | 
| 261   /** Run the analyzer on every input html file. */ |  | 
| 262   void _analyze() { |  | 
| 263     var uniqueIds = new IntIterator(); |  | 
| 264     for (var file in files) { |  | 
| 265       if (file.isHtml) { |  | 
| 266         _time('Analyzed contents', file.path, () => |  | 
| 267             analyzeFile(file, info, uniqueIds, global, _messages, |  | 
| 268               options.emulateScopedCss)); |  | 
| 269       } |  | 
| 270     } |  | 
| 271   } |  | 
| 272 |  | 
| 273   // TODO(jmesserly): refactor this and other CSS related transforms out of |  | 
| 274   // Compiler. |  | 
| 275   /** |  | 
| 276    * Generate an CSS file for all style sheets (main and components). |  | 
| 277    * Returns true if a file was generated, otherwise false. |  | 
| 278    */ |  | 
| 279   bool _emitAllCss() { |  | 
| 280     if (!options.emulateScopedCss) return false; |  | 
| 281 |  | 
| 282     var buff = new StringBuffer(); |  | 
| 283 |  | 
| 284     // Emit all linked style sheet files first. |  | 
| 285     for (var file in files) { |  | 
| 286       var css = new StringBuffer(); |  | 
| 287       var fileInfo = info[file.path]; |  | 
| 288       if (file.isStyleSheet) { |  | 
| 289         for (var styleSheet in fileInfo.styleSheets) { |  | 
| 290           css.write( |  | 
| 291               '/* Auto-generated from style sheet href = ${file.path} */\n' |  | 
| 292               '/* DO NOT EDIT. */\n\n'); |  | 
| 293           css.write(emitStyleSheet(styleSheet, fileInfo)); |  | 
| 294           css.write('\n\n'); |  | 
| 295         } |  | 
| 296       } |  | 
| 297     } |  | 
| 298 |  | 
| 299     // Emit all CSS for each component (style scoped). |  | 
| 300     for (var file in files) { |  | 
| 301       if (file.isHtml) { |  | 
| 302         var fileInfo = info[file.path]; |  | 
| 303         for (var component in fileInfo.declaredComponents) { |  | 
| 304           for (var styleSheet in component.styleSheets) { |  | 
| 305             // Translate any URIs in CSS. |  | 
| 306             if (buff.isEmpty) { |  | 
| 307               buff.write( |  | 
| 308                   '/* Auto-generated from components style tags. */\n' |  | 
| 309                   '/* DO NOT EDIT. */\n\n'); |  | 
| 310             } |  | 
| 311             buff.write( |  | 
| 312                 '/* ==================================================== \n' |  | 
| 313                 '   Component ${component.tagName} stylesheet \n' |  | 
| 314                 '   ==================================================== */\n'); |  | 
| 315 |  | 
| 316             var tagName = component.tagName; |  | 
| 317             if (!component.hasAuthorStyles) { |  | 
| 318               if (_cssResetStyleSheet != null) { |  | 
| 319                 // If component doesn't have apply-author-styles then we need to |  | 
| 320                 // reset the CSS the styles for the component (if css-reset file |  | 
| 321                 // option was passed). |  | 
| 322                 buff.write('\n/* Start CSS Reset */\n'); |  | 
| 323                 var style; |  | 
| 324                 if (options.emulateScopedCss) { |  | 
| 325                   style = emitComponentStyleSheet(_cssResetStyleSheet, tagName); |  | 
| 326                 } else { |  | 
| 327                   style = emitOriginalCss(_cssResetStyleSheet); |  | 
| 328                 } |  | 
| 329                 buff.write(style); |  | 
| 330                 buff.write('/* End CSS Reset */\n\n'); |  | 
| 331               } |  | 
| 332             } |  | 
| 333             if (options.emulateScopedCss) { |  | 
| 334               buff.write(emitComponentStyleSheet(styleSheet, tagName)); |  | 
| 335             } else { |  | 
| 336               buff.write(emitOriginalCss(styleSheet)); |  | 
| 337             } |  | 
| 338             buff.write('\n\n'); |  | 
| 339           } |  | 
| 340         } |  | 
| 341       } |  | 
| 342     } |  | 
| 343 |  | 
| 344     if (buff.isEmpty) return false; |  | 
| 345     return true; |  | 
| 346   } |  | 
| 347 |  | 
| 348   _time(String logMessage, String filePath, callback(), |  | 
| 349       {bool printTime: false}) { |  | 
| 350     var message = new StringBuffer(); |  | 
| 351     message.write(logMessage); |  | 
| 352     var filename = path.basename(filePath); |  | 
| 353     for (int i = (60 - logMessage.length - filename.length); i > 0 ; i--) { |  | 
| 354       message.write(' '); |  | 
| 355     } |  | 
| 356     message.write(filename); |  | 
| 357     return time(message.toString(), callback, |  | 
| 358         printTime: options.verbose || printTime); |  | 
| 359   } |  | 
| 360 } |  | 
| OLD | NEW | 
|---|