| OLD | NEW |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 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 | 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 /// Tracks the shape of the import/export graph and dependencies between files. | 5 /// Tracks the shape of the import/export graph and dependencies between files. |
| 6 library dev_compiler.src.dependency_graph; | 6 library dev_compiler.src.dependency_graph; |
| 7 | 7 |
| 8 import 'dart:collection' show HashSet; | 8 import 'dart:collection' show HashSet; |
| 9 | 9 |
| 10 import 'package:analyzer/analyzer.dart' show parseDirectives; | 10 import 'package:analyzer/analyzer.dart' show parseDirectives; |
| 11 import 'package:analyzer/src/generated/ast.dart' | 11 import 'package:analyzer/src/generated/ast.dart' |
| 12 show | 12 show |
| 13 LibraryDirective, | 13 LibraryDirective, |
| 14 ImportDirective, | 14 ImportDirective, |
| 15 ExportDirective, | 15 ExportDirective, |
| 16 PartDirective, | 16 PartDirective, |
| 17 PartOfDirective, | 17 PartOfDirective, |
| 18 CompilationUnit, | 18 CompilationUnit, |
| 19 Identifier; | 19 Identifier; |
| 20 import 'package:analyzer/src/generated/engine.dart' | 20 import 'package:analyzer/src/generated/engine.dart' |
| 21 show ParseDartTask, AnalysisContext; | 21 show ParseDartTask, AnalysisContext; |
| 22 import 'package:analyzer/src/generated/source.dart' show Source, SourceKind; | 22 import 'package:analyzer/src/generated/source.dart' show Source, SourceKind; |
| 23 import 'package:html5lib/dom.dart' show Document; | 23 import 'package:html5lib/dom.dart' show Document; |
| 24 import 'package:html5lib/parser.dart' as html; | 24 import 'package:html5lib/parser.dart' as html; |
| 25 import 'package:logging/logging.dart' show Logger; | 25 import 'package:logging/logging.dart' show Level; |
| 26 import 'package:path/path.dart' as path; | 26 import 'package:path/path.dart' as path; |
| 27 import 'package:source_span/source_span.dart' show SourceSpan; |
| 27 | 28 |
| 28 import 'info.dart'; | 29 import 'info.dart'; |
| 29 import 'options.dart'; | 30 import 'report.dart'; |
| 30 import 'utils.dart'; | 31 import 'utils.dart'; |
| 31 | 32 |
| 32 /// Holds references to all source nodes in the import graph. This is mainly | 33 /// Holds references to all source nodes in the import graph. This is mainly |
| 33 /// used as a level of indirection to ensure that each source has a canonical | 34 /// used as a level of indirection to ensure that each source has a canonical |
| 34 /// representation. | 35 /// representation. |
| 35 class SourceGraph { | 36 class SourceGraph { |
| 36 /// All nodes in the source graph. Used to get a canonical representation for | 37 /// All nodes in the source graph. Used to get a canonical representation for |
| 37 /// any node. | 38 /// any node. |
| 38 final Map<Uri, SourceNode> nodes = {}; | 39 final Map<Uri, SourceNode> nodes = {}; |
| 39 | 40 |
| 40 /// Analyzer used to resolve source files. | 41 /// Analyzer used to resolve source files. |
| 41 final AnalysisContext _context; | 42 final AnalysisContext _context; |
| 42 final CompilerOptions _options; | 43 final CheckerReporter _reporter; |
| 43 | 44 |
| 44 SourceGraph(this._context, this._options); | 45 SourceGraph(this._context, this._reporter); |
| 45 | 46 |
| 46 /// Node associated with a resolved [uri]. | 47 /// Node associated with a resolved [uri]. |
| 47 SourceNode nodeFromUri(Uri uri) { | 48 SourceNode nodeFromUri(Uri uri) { |
| 48 var uriString = Uri.encodeFull('$uri'); | 49 var uriString = Uri.encodeFull('$uri'); |
| 49 return nodes.putIfAbsent(uri, () { | 50 return nodes.putIfAbsent(uri, () { |
| 50 var source = _context.sourceFactory.forUri(uriString); | 51 var source = _context.sourceFactory.forUri(uriString); |
| 51 var extension = path.extension(uriString); | 52 var extension = path.extension(uriString); |
| 52 if (extension == '.html') { | 53 if (extension == '.html') { |
| 53 return new HtmlSourceNode(uri, source, this); | 54 return new HtmlSourceNode(uri, source, this); |
| 54 } else if (extension == '.dart' || uriString.startsWith('dart:')) { | 55 } else if (extension == '.dart' || uriString.startsWith('dart:')) { |
| 55 return new DartSourceNode(uri, source); | 56 return new DartSourceNode(uri, source); |
| 56 } else if (extension == '.js') { | 57 } else if (extension == '.js' || extension == '.css') { |
| 57 return new JavaScriptSourceNode(uri, source); | 58 return new ResourceSourceNode(uri, source); |
| 58 } else { | 59 } else { |
| 59 assert(false); // unreachable | 60 assert(false); // unreachable |
| 60 } | 61 } |
| 61 }); | 62 }); |
| 62 } | 63 } |
| 63 } | 64 } |
| 64 | 65 |
| 65 /// A node in the import graph representing a source file. | 66 /// A node in the import graph representing a source file. |
| 66 abstract class SourceNode { | 67 abstract class SourceNode { |
| 67 /// Resolved URI for this node. | 68 /// Resolved URI for this node. |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 103 } | 104 } |
| 104 | 105 |
| 105 String toString() { | 106 String toString() { |
| 106 var simpleUri = uri.scheme == 'file' ? path.relative(uri.path) : "$uri"; | 107 var simpleUri = uri.scheme == 'file' ? path.relative(uri.path) : "$uri"; |
| 107 return '[$runtimeType: $simpleUri]'; | 108 return '[$runtimeType: $simpleUri]'; |
| 108 } | 109 } |
| 109 } | 110 } |
| 110 | 111 |
| 111 /// A node representing an entry HTML source file. | 112 /// A node representing an entry HTML source file. |
| 112 class HtmlSourceNode extends SourceNode { | 113 class HtmlSourceNode extends SourceNode { |
| 113 /// Javascript dependencies, included by default on any application. | 114 /// Resources included by default on any application. |
| 114 Set<JavaScriptSourceNode> runtimeDeps = new Set<JavaScriptSourceNode>(); | 115 Set<ResourceSourceNode> runtimeDeps = new Set<ResourceSourceNode>(); |
| 115 | 116 |
| 116 /// Libraries referred to via script tags. | 117 /// Libraries referred to via script tags. |
| 117 Set<DartSourceNode> scripts = new Set<DartSourceNode>(); | 118 Set<DartSourceNode> scripts = new Set<DartSourceNode>(); |
| 118 | 119 |
| 119 @override | 120 @override |
| 120 Iterable<SourceNode> get allDeps => [scripts, runtimeDeps].expand((e) => e); | 121 Iterable<SourceNode> get allDeps => [scripts, runtimeDeps].expand((e) => e); |
| 121 | 122 |
| 122 @override | 123 @override |
| 123 Iterable<SourceNode> get depsWithoutParts => allDeps; | 124 Iterable<SourceNode> get depsWithoutParts => allDeps; |
| 124 | 125 |
| 125 /// Parsed document, updated whenever [update] is invoked. | 126 /// Parsed document, updated whenever [update] is invoked. |
| 126 Document document; | 127 Document document; |
| 127 | 128 |
| 128 HtmlSourceNode(uri, source, graph) : super(uri, source) { | 129 HtmlSourceNode(uri, source, graph) : super(uri, source) { |
| 129 var prefix = 'package:dev_compiler/runtime'; | 130 var prefix = 'package:dev_compiler/runtime'; |
| 130 runtimeDeps | 131 runtimeDeps |
| 131 ..add(graph.nodeFromUri(Uri.parse('$prefix/dart_runtime.js'))) | 132 ..add(graph.nodeFromUri(Uri.parse('$prefix/dart_runtime.js'))) |
| 132 ..add(graph.nodeFromUri(Uri.parse('$prefix/harmony_feature_check.js'))); | 133 ..add(graph.nodeFromUri(Uri.parse('$prefix/harmony_feature_check.js'))) |
| 134 ..add(graph.nodeFromUri(Uri.parse('$prefix/messages_widget.js'))) |
| 135 ..add(graph.nodeFromUri(Uri.parse('$prefix/messages.css'))); |
| 133 } | 136 } |
| 134 | 137 |
| 135 void update(SourceGraph graph) { | 138 void update(SourceGraph graph) { |
| 136 super.update(graph); | 139 super.update(graph); |
| 137 if (needsRebuild) { | 140 if (needsRebuild) { |
| 141 graph._reporter.clearHtml(uri); |
| 138 document = html.parse(source.contents.data, generateSpans: true); | 142 document = html.parse(source.contents.data, generateSpans: true); |
| 139 var newScripts = new Set<DartSourceNode>(); | 143 var newScripts = new Set<DartSourceNode>(); |
| 140 var tags = document.querySelectorAll('script[type="application/dart"]'); | 144 var tags = document.querySelectorAll('script[type="application/dart"]'); |
| 141 for (var script in tags) { | 145 for (var script in tags) { |
| 142 var src = script.attributes['src']; | 146 var src = script.attributes['src']; |
| 143 if (src == null) { | 147 if (src == null) { |
| 144 // TODO(sigmund): expose these as compile-time failures | 148 graph._reporter.enterHtml(source.uri); |
| 145 _log.severe(script.sourceSpan.message( | 149 graph._reporter.log(new DependencyGraphError( |
| 146 'inlined script tags not supported at this time ' | 150 'inlined script tags not supported at this time ' |
| 147 '(see https://github.com/dart-lang/dart-dev-compiler/issues/54).', | 151 '(see https://github.com/dart-lang/dart-dev-compiler/issues/54).', |
| 148 color: graph._options.useColors ? colorOf('error') : false)); | 152 script.sourceSpan)); |
| 153 graph._reporter.leaveHtml(); |
| 149 continue; | 154 continue; |
| 150 } | 155 } |
| 151 var node = graph.nodeFromUri(uri.resolve(src)); | 156 var node = graph.nodeFromUri(uri.resolve(src)); |
| 152 if (!node.source.exists()) { | 157 if (node == null || !node.source.exists()) { |
| 153 _log.severe(script.sourceSpan.message('Script file $src not found', | 158 graph._reporter.enterHtml(source.uri); |
| 154 color: graph._options.useColors ? colorOf('error') : false)); | 159 graph._reporter.log(new DependencyGraphError( |
| 160 'Script file $src not found', script.sourceSpan)); |
| 161 graph._reporter.leaveHtml(); |
| 155 } | 162 } |
| 156 newScripts.add(node); | 163 if (node != null) newScripts.add(node); |
| 157 } | 164 } |
| 158 | 165 |
| 159 if (!_same(newScripts, scripts)) { | 166 if (!_same(newScripts, scripts)) { |
| 160 structureChanged = true; | 167 structureChanged = true; |
| 161 scripts = newScripts; | 168 scripts = newScripts; |
| 162 } | 169 } |
| 163 } | 170 } |
| 164 } | 171 } |
| 165 } | 172 } |
| 166 | 173 |
| (...skipping 20 matching lines...) Expand all Loading... |
| 187 @override | 194 @override |
| 188 Iterable<SourceNode> get depsWithoutParts => | 195 Iterable<SourceNode> get depsWithoutParts => |
| 189 [imports, exports].expand((e) => e); | 196 [imports, exports].expand((e) => e); |
| 190 | 197 |
| 191 LibraryInfo info; | 198 LibraryInfo info; |
| 192 | 199 |
| 193 void update(SourceGraph graph) { | 200 void update(SourceGraph graph) { |
| 194 super.update(graph); | 201 super.update(graph); |
| 195 | 202 |
| 196 if (needsRebuild && source.contents.data != null) { | 203 if (needsRebuild && source.contents.data != null) { |
| 204 graph._reporter.clearLibrary(uri); |
| 197 // If the defining compilation-unit changed, the structure might have | 205 // If the defining compilation-unit changed, the structure might have |
| 198 // changed. | 206 // changed. |
| 199 var unit = parseDirectives(source.contents.data, name: source.fullName); | 207 var unit = parseDirectives(source.contents.data, name: source.fullName); |
| 200 var newImports = new Set<DartSourceNode>(); | 208 var newImports = new Set<DartSourceNode>(); |
| 201 var newExports = new Set<DartSourceNode>(); | 209 var newExports = new Set<DartSourceNode>(); |
| 202 var newParts = new Set<DartSourceNode>(); | 210 var newParts = new Set<DartSourceNode>(); |
| 203 for (var d in unit.directives) { | 211 for (var d in unit.directives) { |
| 204 // Nothing to do for parts. | 212 // Nothing to do for parts. |
| 205 if (d is PartOfDirective) return; | 213 if (d is PartOfDirective) return; |
| 206 if (d is LibraryDirective) continue; | 214 if (d is LibraryDirective) continue; |
| 207 var target = | 215 var target = |
| 208 ParseDartTask.resolveDirective(graph._context, source, d, null); | 216 ParseDartTask.resolveDirective(graph._context, source, d, null); |
| 209 var uri = target.uri; | 217 var uri = target.uri; |
| 210 var node = | 218 var node = |
| 211 graph.nodes.putIfAbsent(uri, () => new DartSourceNode(uri, target)); | 219 graph.nodes.putIfAbsent(uri, () => new DartSourceNode(uri, target)); |
| 212 if (!node.source.exists()) { | 220 if (!node.source.exists()) { |
| 213 _log.severe(spanForNode(unit, source, d).message( | 221 graph._reporter.enterLibrary(source.uri); |
| 214 'File $uri not found', | 222 graph._reporter.log(new DependencyGraphError( |
| 215 color: graph._options.useColors ? colorOf('error') : false)); | 223 'File $uri not found', spanForNode(unit, source, d))); |
| 224 graph._reporter.leaveLibrary(); |
| 216 } | 225 } |
| 217 | 226 |
| 218 if (d is ImportDirective) { | 227 if (d is ImportDirective) { |
| 219 newImports.add(node); | 228 newImports.add(node); |
| 220 } else if (d is ExportDirective) { | 229 } else if (d is ExportDirective) { |
| 221 newExports.add(node); | 230 newExports.add(node); |
| 222 } else if (d is PartDirective) { | 231 } else if (d is PartDirective) { |
| 223 newParts.add(node); | 232 newParts.add(node); |
| 224 } | 233 } |
| 225 } | 234 } |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 260 // Technically for parts we don't need to look at the contents. If they | 269 // Technically for parts we don't need to look at the contents. If they |
| 261 // contain imports, exports, or parts, we'll ignore them in our crawling. | 270 // contain imports, exports, or parts, we'll ignore them in our crawling. |
| 262 // However we do a full update to make it easier to adjust when users | 271 // However we do a full update to make it easier to adjust when users |
| 263 // switch a file from a part to a library. | 272 // switch a file from a part to a library. |
| 264 p.update(graph); | 273 p.update(graph); |
| 265 if (p.needsRebuild) needsRebuild = true; | 274 if (p.needsRebuild) needsRebuild = true; |
| 266 } | 275 } |
| 267 } | 276 } |
| 268 } | 277 } |
| 269 | 278 |
| 270 /// Represents a Javascript runtime resource from our compiler that is needed to | 279 /// Represents a runtime resource from our compiler that is needed to run an |
| 271 /// run an application. | 280 /// application. |
| 272 class JavaScriptSourceNode extends SourceNode { | 281 class ResourceSourceNode extends SourceNode { |
| 273 JavaScriptSourceNode(uri, source) : super(uri, source); | 282 ResourceSourceNode(uri, source) : super(uri, source); |
| 274 } | 283 } |
| 275 | 284 |
| 276 /// Updates the structure and `needsRebuild` marks in nodes of [graph] reachable | 285 /// Updates the structure and `needsRebuild` marks in nodes of [graph] reachable |
| 277 /// from [start]. | 286 /// from [start]. |
| 278 /// | 287 /// |
| 279 /// That is, staring from [start], we update the graph by detecting file changes | 288 /// That is, staring from [start], we update the graph by detecting file changes |
| 280 /// and rebuilding the structure of the graph wherever it changed (an import was | 289 /// and rebuilding the structure of the graph wherever it changed (an import was |
| 281 /// added or removed, etc). | 290 /// added or removed, etc). |
| 282 /// | 291 /// |
| 283 /// After calling this function a node is marked with `needsRebuild` only if it | 292 /// After calling this function a node is marked with `needsRebuild` only if it |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 329 // cases anways require using summaries to understand what parts of the public | 338 // cases anways require using summaries to understand what parts of the public |
| 330 // API may be affected by transitive changes. The re-export case is just one | 339 // API may be affected by transitive changes. The re-export case is just one |
| 331 // of those transitive cases, but is not sufficient. See | 340 // of those transitive cases, but is not sufficient. See |
| 332 // https://github.com/dart-lang/dev_compiler/issues/76 | 341 // https://github.com/dart-lang/dev_compiler/issues/76 |
| 333 var apiChangeDetected = new HashSet<SourceNode>(); | 342 var apiChangeDetected = new HashSet<SourceNode>(); |
| 334 bool structureHasChanged = false; | 343 bool structureHasChanged = false; |
| 335 | 344 |
| 336 bool shouldBuildNode(SourceNode n) { | 345 bool shouldBuildNode(SourceNode n) { |
| 337 if (n.needsRebuild) return true; | 346 if (n.needsRebuild) return true; |
| 338 if (n is HtmlSourceNode) return structureHasChanged; | 347 if (n is HtmlSourceNode) return structureHasChanged; |
| 339 if (n is JavaScriptSourceNode) return false; | 348 if (n is ResourceSourceNode) return false; |
| 340 return (n as DartSourceNode).imports | 349 return (n as DartSourceNode).imports |
| 341 .any((i) => apiChangeDetected.contains(i)); | 350 .any((i) => apiChangeDetected.contains(i)); |
| 342 } | 351 } |
| 343 | 352 |
| 344 visitInPostOrder(start, (n) { | 353 visitInPostOrder(start, (n) { |
| 345 if (n.structureChanged) structureHasChanged = true; | 354 if (n.structureChanged) structureHasChanged = true; |
| 346 if (shouldBuildNode(n)) { | 355 if (shouldBuildNode(n)) { |
| 347 if (build(n)) apiChangeDetected.add(n); | 356 if (build(n)) apiChangeDetected.add(n); |
| 348 } else if (n is DartSourceNode && | 357 } else if (n is DartSourceNode && |
| 349 n.exports.any((e) => apiChangeDetected.contains(e))) { | 358 n.exports.any((e) => apiChangeDetected.contains(e))) { |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 383 helper(SourceNode node) { | 392 helper(SourceNode node) { |
| 384 if (!seen.add(node)) return; | 393 if (!seen.add(node)) return; |
| 385 var deps = includeParts ? node.allDeps : node.depsWithoutParts; | 394 var deps = includeParts ? node.allDeps : node.depsWithoutParts; |
| 386 deps.forEach(helper); | 395 deps.forEach(helper); |
| 387 action(node); | 396 action(node); |
| 388 } | 397 } |
| 389 helper(start); | 398 helper(start); |
| 390 } | 399 } |
| 391 | 400 |
| 392 bool _same(Set a, Set b) => a.length == b.length && a.containsAll(b); | 401 bool _same(Set a, Set b) => a.length == b.length && a.containsAll(b); |
| 393 final _log = new Logger('dev_compiler.graph'); | 402 |
| 403 /// An error message discovered while parsing the dependencies between files. |
| 404 class DependencyGraphError extends MessageWithSpan { |
| 405 const DependencyGraphError(String message, SourceSpan span) |
| 406 : super(message, Level.SEVERE, span); |
| 407 } |
| OLD | NEW |