OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2015, 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 /// Command line tool to run the checker on a Dart program. |
| 6 library dev_compiler.src.compiler; |
| 7 |
| 8 import 'dart:async'; |
| 9 import 'dart:collection'; |
| 10 import 'dart:math' as math; |
| 11 import 'dart:io'; |
| 12 |
| 13 import 'package:analyzer/src/generated/element.dart'; |
| 14 import 'package:analyzer/src/generated/engine.dart' |
| 15 show AnalysisEngine, AnalysisContext, ChangeSet, ParseDartTask; |
| 16 import 'package:analyzer/src/generated/error.dart' |
| 17 show AnalysisError, ErrorSeverity, ErrorType; |
| 18 import 'package:analyzer/src/generated/error.dart'; |
| 19 import 'package:analyzer/src/generated/resolver.dart' show TypeProvider; |
| 20 import 'package:analyzer/src/generated/source.dart' show Source; |
| 21 import 'package:analyzer/src/task/html.dart'; |
| 22 import 'package:html/dom.dart' as html; |
| 23 import 'package:html/parser.dart' as html; |
| 24 import 'package:logging/logging.dart' show Level, Logger, LogRecord; |
| 25 import 'package:path/path.dart' as path; |
| 26 |
| 27 import 'package:dev_compiler/strong_mode.dart' show StrongModeOptions; |
| 28 |
| 29 import 'analysis_context.dart'; |
| 30 import 'checker/checker.dart'; |
| 31 import 'checker/rules.dart'; |
| 32 import 'codegen/html_codegen.dart' as html_codegen; |
| 33 import 'codegen/js_codegen.dart'; |
| 34 import 'info.dart' |
| 35 show AnalyzerMessage, CheckerResults, LibraryInfo, LibraryUnit; |
| 36 import 'options.dart'; |
| 37 import 'report.dart'; |
| 38 |
| 39 /// Sets up the type checker logger to print a span that highlights error |
| 40 /// messages. |
| 41 StreamSubscription setupLogger(Level level, printFn) { |
| 42 Logger.root.level = level; |
| 43 return Logger.root.onRecord.listen((LogRecord rec) { |
| 44 printFn('${rec.level.name.toLowerCase()}: ${rec.message}'); |
| 45 }); |
| 46 } |
| 47 |
| 48 class BatchCompiler extends AbstractCompiler { |
| 49 JSGenerator _jsGen; |
| 50 |
| 51 /// Already compiled sources, so we don't compile them again. |
| 52 final _compiled = new HashSet<LibraryElement>(); |
| 53 |
| 54 bool _failure = false; |
| 55 bool get failure => _failure; |
| 56 |
| 57 BatchCompiler(AnalysisContext context, CompilerOptions options, |
| 58 {AnalysisErrorListener reporter}) |
| 59 : super(context, options, reporter) { |
| 60 if (outputDir != null) { |
| 61 _jsGen = new JSGenerator(this); |
| 62 } |
| 63 } |
| 64 |
| 65 void reset() { |
| 66 _compiled.clear(); |
| 67 } |
| 68 |
| 69 /// Compiles every file in [options.inputs]. |
| 70 /// Returns true on successful compile. |
| 71 bool run() { |
| 72 var clock = new Stopwatch()..start(); |
| 73 options.inputs.forEach(compileFromUriString); |
| 74 clock.stop(); |
| 75 var time = (clock.elapsedMilliseconds / 1000).toStringAsFixed(2); |
| 76 _log.fine('Compiled ${_compiled.length} libraries in ${time} s\n'); |
| 77 |
| 78 return !_failure; |
| 79 } |
| 80 |
| 81 void compileFromUriString(String uriString) { |
| 82 compileFromUri(stringToUri(uriString)); |
| 83 } |
| 84 |
| 85 void compileFromUri(Uri uri) { |
| 86 var source = context.sourceFactory.forUri(Uri.encodeFull('$uri')); |
| 87 if (source == null) throw new ArgumentError.value( |
| 88 uri.toString(), 'uri', 'could not find source for'); |
| 89 compileSource(source); |
| 90 } |
| 91 |
| 92 void compileSource(Source source) { |
| 93 if (AnalysisEngine.isHtmlFileName(source.uri.path)) { |
| 94 compileHtml(source); |
| 95 return; |
| 96 } |
| 97 |
| 98 compileLibrary(context.computeLibraryElement(source)); |
| 99 } |
| 100 |
| 101 void compileLibrary(LibraryElement library) { |
| 102 if (!_compiled.add(library)) return; |
| 103 if (!options.checkSdk && library.source.uri.scheme == 'dart') return; |
| 104 |
| 105 // TODO(jmesserly): in incremental mode, we can skip the transitive |
| 106 // compile of imports/exports. |
| 107 library.importedLibraries.forEach(compileLibrary); |
| 108 library.exportedLibraries.forEach(compileLibrary); |
| 109 |
| 110 var unitElements = [library.definingCompilationUnit]..addAll(library.parts); |
| 111 var units = []; |
| 112 |
| 113 bool failureInLib = false; |
| 114 for (var element in unitElements) { |
| 115 var unit = context.resolveCompilationUnit(element.source, library); |
| 116 units.add(unit); |
| 117 failureInLib = logErrors(element.source) || failureInLib; |
| 118 checker.visitCompilationUnit(unit); |
| 119 if (checker.failure) failureInLib = true; |
| 120 } |
| 121 |
| 122 if (failureInLib) { |
| 123 _failure = true; |
| 124 if (!options.codegenOptions.forceCompile) return; |
| 125 } |
| 126 |
| 127 if (_jsGen != null) { |
| 128 _jsGen.generateLibrary( |
| 129 new LibraryUnit(units.first, units.skip(1).toList())); |
| 130 } |
| 131 } |
| 132 |
| 133 void compileHtml(Source source) { |
| 134 // TODO(jmesserly): reuse DartScriptsTask instead of copy/paste. |
| 135 var contents = context.getContents(source); |
| 136 var document = html.parse(contents.data, generateSpans: true); |
| 137 var scripts = document.querySelectorAll('script[type="application/dart"]'); |
| 138 |
| 139 var loadedLibs = new LinkedHashSet<Uri>(); |
| 140 |
| 141 for (var script in scripts) { |
| 142 Source scriptSource = null; |
| 143 var srcAttr = script.attributes['src']; |
| 144 if (srcAttr == null) { |
| 145 if (script.hasContent()) { |
| 146 var fragments = <ScriptFragment>[]; |
| 147 for (var node in script.nodes) { |
| 148 if (node is html.Text) { |
| 149 var start = node.sourceSpan.start; |
| 150 fragments.add(new ScriptFragment( |
| 151 start.offset, start.line, start.column, node.data)); |
| 152 } |
| 153 } |
| 154 scriptSource = new DartScript(source, fragments); |
| 155 } |
| 156 } else if (AnalysisEngine.isDartFileName(srcAttr)) { |
| 157 scriptSource = context.sourceFactory.resolveUri(source, srcAttr); |
| 158 } |
| 159 |
| 160 if (scriptSource != null) { |
| 161 var lib = context.computeLibraryElement(scriptSource); |
| 162 compileLibrary(lib); |
| 163 script.replaceWith(_linkLibraries(lib, loadedLibs)); |
| 164 } |
| 165 } |
| 166 |
| 167 // TODO(jmesserly): we need to clean this up so we aren't treating these |
| 168 // as a special case. |
| 169 for (var file in defaultRuntimeFiles) { |
| 170 var input = path.join(options.runtimeDir, file); |
| 171 var output = path.join(outputDir, runtimeFileOutput(file)); |
| 172 new Directory(path.dirname(output)).createSync(recursive: true); |
| 173 new File(input).copySync(output); |
| 174 } |
| 175 |
| 176 new File(getOutputPath(source.uri)).openSync(mode: FileMode.WRITE) |
| 177 ..writeStringSync(document.outerHtml) |
| 178 ..writeStringSync('\n') |
| 179 ..closeSync(); |
| 180 } |
| 181 |
| 182 html.DocumentFragment _linkLibraries( |
| 183 LibraryElement mainLib, LinkedHashSet<Uri> loaded) { |
| 184 var alreadyLoaded = loaded.length; |
| 185 _collectLibraries(mainLib, loaded); |
| 186 |
| 187 var newLibs = loaded.skip(alreadyLoaded); |
| 188 var df = new html.DocumentFragment(); |
| 189 for (var path in defaultRuntimeFiles) { |
| 190 df.append(html_codegen.libraryInclude(runtimeFileOutput(path))); |
| 191 } |
| 192 for (var uri in newLibs) { |
| 193 if (uri.scheme == 'dart') continue; |
| 194 df.append(html_codegen.libraryInclude(getModulePath(uri))); |
| 195 } |
| 196 df.append(html_codegen.invokeMain(getModuleName(mainLib.source.uri))); |
| 197 return df; |
| 198 } |
| 199 |
| 200 void _collectLibraries(LibraryElement lib, LinkedHashSet<Uri> loaded) { |
| 201 var uri = lib.source.uri; |
| 202 if (!loaded.add(uri)) return; |
| 203 for (var l in lib.importedLibraries) _collectLibraries(l, loaded); |
| 204 for (var l in lib.exportedLibraries) _collectLibraries(l, loaded); |
| 205 // Move the item to the end of the list. |
| 206 loaded.remove(uri); |
| 207 loaded.add(uri); |
| 208 } |
| 209 |
| 210 String runtimeFileOutput(String file) => |
| 211 path.join('dev_compiler', 'runtime', file); |
| 212 } |
| 213 |
| 214 abstract class AbstractCompiler { |
| 215 final CompilerOptions options; |
| 216 final AnalysisContext context; |
| 217 final CodeChecker checker; |
| 218 |
| 219 AbstractCompiler(AnalysisContext context, CompilerOptions options, |
| 220 [AnalysisErrorListener reporter]) |
| 221 : context = context, |
| 222 options = options, |
| 223 checker = createChecker(context.typeProvider, options.strongOptions, |
| 224 reporter == null ? AnalysisErrorListener.NULL_LISTENER : reporter) { |
| 225 enableDevCompilerInference(context, options.strongOptions); |
| 226 } |
| 227 |
| 228 static CodeChecker createChecker(TypeProvider typeProvider, |
| 229 StrongModeOptions options, AnalysisErrorListener reporter) { |
| 230 return new CodeChecker( |
| 231 new RestrictedRules(typeProvider, options: options), reporter, options); |
| 232 } |
| 233 |
| 234 String get outputDir => options.codegenOptions.outputDir; |
| 235 TypeRules get rules => checker.rules; |
| 236 AnalysisErrorListener get reporter => checker.reporter; |
| 237 |
| 238 Uri stringToUri(String uriString) { |
| 239 var uri = uriString.startsWith('dart:') || uriString.startsWith('package:') |
| 240 ? Uri.parse(uriString) |
| 241 : new Uri.file(uriString); |
| 242 return uri; |
| 243 } |
| 244 |
| 245 /// Directory presumed to be the common prefix for all input file:// URIs. |
| 246 /// Used when computing output paths. |
| 247 /// |
| 248 /// For example: |
| 249 /// dartdevc -o out foo/a.dart bar/b.dart |
| 250 /// |
| 251 /// Will produce: |
| 252 /// out/foo/a.dart |
| 253 /// out/bar/b.dart |
| 254 /// |
| 255 /// This is only used if at least one of [options.codegenOptions.inputs] is |
| 256 /// a file URI. |
| 257 // TODO(jmesserly): do we need an option for this? |
| 258 // Other ideas: we could look up and see what package the file is in, treat |
| 259 // that as a base path. We could also use the current working directory as |
| 260 // the base. |
| 261 String get inputBaseDir { |
| 262 if (_fileUriCommonBaseDir == null) { |
| 263 List<String> common = null; |
| 264 for (var uri in options.inputs.map(stringToUri)) { |
| 265 if (uri.scheme != 'file') continue; |
| 266 |
| 267 var segments = path.split(path.dirname(uri.path)); |
| 268 if (common == null) { |
| 269 common = segments; |
| 270 } else { |
| 271 int len = math.min(common.length, segments.length); |
| 272 while (len > 0 && common[len - 1] != segments[len - 1]) { |
| 273 len--; |
| 274 } |
| 275 common.length = len; |
| 276 } |
| 277 } |
| 278 _fileUriCommonBaseDir = common == null ? '' : path.joinAll(common); |
| 279 } |
| 280 return _fileUriCommonBaseDir; |
| 281 } |
| 282 String _fileUriCommonBaseDir; |
| 283 |
| 284 String getOutputPath(Uri uri) => path.join(outputDir, getModulePath(uri)); |
| 285 |
| 286 /// Like [getModuleName] but includes the file extension, either .js or .html. |
| 287 String getModulePath(Uri uri) { |
| 288 var ext = path.extension(uri.path); |
| 289 if (ext == '.dart' || ext == '' && uri.scheme == 'dart') ext = '.js'; |
| 290 return getModuleName(uri) + ext; |
| 291 } |
| 292 |
| 293 /// Gets the module name, without extension. For example: |
| 294 /// |
| 295 /// * dart:core -> dart/core |
| 296 /// * file:foo/bar/baz.dart -> foo/bar/baz |
| 297 /// * package:qux/qux.dart -> qux/qux |
| 298 /// |
| 299 /// For file: URLs this will also make them relative to [inputBaseDir]. |
| 300 String getModuleName(Uri uri) { |
| 301 var filepath = path.withoutExtension(uri.path); |
| 302 if (uri.scheme == 'dart') { |
| 303 filepath = 'dart/$filepath'; |
| 304 } else if (uri.scheme == 'file') { |
| 305 filepath = path.relative(filepath, from: inputBaseDir); |
| 306 } else { |
| 307 assert(uri.scheme == 'package'); |
| 308 // filepath is good here, we want the output to start with a directory |
| 309 // matching the package name. |
| 310 } |
| 311 return filepath; |
| 312 } |
| 313 |
| 314 /// Log any errors encountered when resolving [source] and return whether any |
| 315 /// errors were found. |
| 316 bool logErrors(Source source) { |
| 317 List<AnalysisError> errors = context.computeErrors(source); |
| 318 bool failure = false; |
| 319 if (errors.isNotEmpty) { |
| 320 for (var error in errors) { |
| 321 // Always skip TODOs. |
| 322 if (error.errorCode.type == ErrorType.TODO) continue; |
| 323 |
| 324 // Skip hints for now. In the future these could be turned on via flags. |
| 325 if (error.errorCode.errorSeverity.ordinal < |
| 326 ErrorSeverity.WARNING.ordinal) { |
| 327 continue; |
| 328 } |
| 329 |
| 330 // All analyzer warnings or errors are errors for DDC. |
| 331 failure = true; |
| 332 reporter.onError(error); |
| 333 } |
| 334 } |
| 335 return failure; |
| 336 } |
| 337 } |
| 338 |
| 339 AnalysisErrorListener createErrorReporter( |
| 340 AnalysisContext context, CompilerOptions options) { |
| 341 return options.dumpInfo |
| 342 ? new SummaryReporter(context, options.logLevel) |
| 343 : new LogReporter(context, useColors: options.useColors); |
| 344 } |
| 345 |
| 346 // TODO(jmesserly): find a better home for these. |
| 347 /// Curated order to minimize lazy classes needed by dart:core and its |
| 348 /// transitive SDK imports. |
| 349 const corelibOrder = const [ |
| 350 'dart.core', |
| 351 'dart.collection', |
| 352 'dart._internal', |
| 353 'dart.math', |
| 354 'dart._interceptors', |
| 355 'dart.async', |
| 356 'dart._foreign_helper', |
| 357 'dart._js_embedded_names', |
| 358 'dart._js_helper', |
| 359 'dart.isolate', |
| 360 'dart.typed_data', |
| 361 'dart._native_typed_data', |
| 362 'dart._isolate_helper', |
| 363 'dart._js_primitives', |
| 364 'dart.convert', |
| 365 'dart.mirrors', |
| 366 'dart._js_mirrors', |
| 367 'dart.js' |
| 368 // _foreign_helper is not included, as it only defines the JS builtin that |
| 369 // the compiler handles at compile time. |
| 370 ]; |
| 371 |
| 372 /// Runtime files added to all applications when running the compiler in the |
| 373 /// command line. |
| 374 final defaultRuntimeFiles = () { |
| 375 var files = [ |
| 376 'harmony_feature_check.js', |
| 377 'dart_utils.js', |
| 378 'dart_library.js', |
| 379 '_errors.js', |
| 380 '_types.js', |
| 381 '_rtti.js', |
| 382 '_classes.js', |
| 383 '_operations.js', |
| 384 'dart_runtime.js', |
| 385 ]; |
| 386 files.addAll(corelibOrder.map((l) => l.replaceAll('.', '/') + '.js')); |
| 387 return files; |
| 388 }(); |
| 389 |
| 390 final _log = new Logger('dev_compiler.src.compiler'); |
OLD | NEW |