| 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 AstNode, |
| 14 CompilationUnit, |
| 15 ExportDirective, |
| 16 Identifier, |
| 17 ImportDirective, |
| 13 LibraryDirective, | 18 LibraryDirective, |
| 14 ImportDirective, | |
| 15 ExportDirective, | |
| 16 PartDirective, | 19 PartDirective, |
| 17 PartOfDirective, | 20 PartOfDirective; |
| 18 CompilationUnit, | |
| 19 Identifier; | |
| 20 import 'package:analyzer/src/generated/engine.dart' | 21 import 'package:analyzer/src/generated/engine.dart' |
| 21 show ParseDartTask, AnalysisContext; | 22 show ParseDartTask, AnalysisContext; |
| 22 import 'package:analyzer/src/generated/source.dart' show Source, SourceKind; | 23 import 'package:analyzer/src/generated/source.dart' show Source, SourceKind; |
| 23 import 'package:html5lib/dom.dart' show Document; | 24 import 'package:html5lib/dom.dart' show Document, Node; |
| 24 import 'package:html5lib/parser.dart' as html; | 25 import 'package:html5lib/parser.dart' as html; |
| 25 import 'package:logging/logging.dart' show Level; | 26 import 'package:logging/logging.dart' show Level; |
| 26 import 'package:path/path.dart' as path; | 27 import 'package:path/path.dart' as path; |
| 27 import 'package:source_span/source_span.dart' show SourceSpan; | 28 import 'package:source_span/source_span.dart' show SourceSpan; |
| 28 | 29 |
| 29 import 'info.dart'; | 30 import 'info.dart'; |
| 30 import 'options.dart'; | 31 import 'options.dart'; |
| 31 import 'report.dart'; | 32 import 'report.dart'; |
| 32 import 'utils.dart'; | 33 import 'utils.dart'; |
| 33 | 34 |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 65 } | 66 } |
| 66 } | 67 } |
| 67 | 68 |
| 68 /// A node in the import graph representing a source file. | 69 /// A node in the import graph representing a source file. |
| 69 abstract class SourceNode { | 70 abstract class SourceNode { |
| 70 /// Resolved URI for this node. | 71 /// Resolved URI for this node. |
| 71 final Uri uri; | 72 final Uri uri; |
| 72 | 73 |
| 73 /// Resolved source from the analyzer. We let the analyzer internally track | 74 /// Resolved source from the analyzer. We let the analyzer internally track |
| 74 /// for modifications to the source files. | 75 /// for modifications to the source files. |
| 75 final Source source; | 76 Source _source; |
| 77 Source get source => _source; |
| 76 | 78 |
| 77 /// Last stamp read from `source.modificationStamp`. | 79 /// Last stamp read from `source.modificationStamp`. |
| 78 int _lastStamp = 0; | 80 int _lastStamp = 0; |
| 79 | 81 |
| 80 /// A hash used to help browsers cache the output that would be produced from | 82 /// A hash used to help browsers cache the output that would be produced from |
| 81 /// building this node. | 83 /// building this node. |
| 82 String cachingHash; | 84 String cachingHash; |
| 83 | 85 |
| 84 /// Whether we need to rebuild this source file. | 86 /// Whether we need to rebuild this source file. |
| 85 bool needsRebuild = false; | 87 bool needsRebuild = false; |
| 86 | 88 |
| 87 /// Whether the structure of dependencies from this node (scripts, imports, | 89 /// Whether the structure of dependencies from this node (scripts, imports, |
| 88 /// exports, or parts) changed after we reparsed its contents. | 90 /// exports, or parts) changed after we reparsed its contents. |
| 89 bool structureChanged = false; | 91 bool structureChanged = false; |
| 90 | 92 |
| 91 /// Direct dependencies in the [SourceGraph]. These include script tags for | 93 /// Direct dependencies in the [SourceGraph]. These include script tags for |
| 92 /// [HtmlSourceNode]s; and imports, exports and parts for [DartSourceNode]s. | 94 /// [HtmlSourceNode]s; and imports, exports and parts for [DartSourceNode]s. |
| 93 Iterable<SourceNode> get allDeps => const []; | 95 Iterable<SourceNode> get allDeps => const []; |
| 94 | 96 |
| 95 /// Like [allDeps] but excludes parts for [DartSourceNode]s. For many | 97 /// Like [allDeps] but excludes parts for [DartSourceNode]s. For many |
| 96 /// operations we mainly care about dependencies at the library level, so | 98 /// operations we mainly care about dependencies at the library level, so |
| 97 /// parts are excluded from this list. | 99 /// parts are excluded from this list. |
| 98 Iterable<SourceNode> get depsWithoutParts => const []; | 100 Iterable<SourceNode> get depsWithoutParts => const []; |
| 99 | 101 |
| 100 SourceNode(this.uri, this.source); | 102 SourceNode(this.uri, this._source); |
| 101 | 103 |
| 102 /// Check for whether the file has changed and, if so, mark [needsRebuild] and | 104 /// Check for whether the file has changed and, if so, mark [needsRebuild] and |
| 103 /// [structureChanged] as necessary. | 105 /// [structureChanged] as necessary. |
| 104 void update(SourceGraph graph) { | 106 void update(SourceGraph graph) { |
| 105 int newStamp = source.modificationStamp; | 107 if (_source == null) { |
| 108 _source = graph._context.sourceFactory.forUri(Uri.encodeFull('$uri')); |
| 109 if (_source == null) return; |
| 110 } |
| 111 int newStamp = _source.modificationStamp; |
| 106 if (newStamp > _lastStamp) { | 112 if (newStamp > _lastStamp) { |
| 107 _lastStamp = newStamp; | 113 _lastStamp = newStamp; |
| 108 needsRebuild = true; | 114 needsRebuild = true; |
| 109 } | 115 } |
| 110 } | 116 } |
| 111 | 117 |
| 112 String toString() { | 118 String toString() { |
| 113 var simpleUri = uri.scheme == 'file' ? path.relative(uri.path) : "$uri"; | 119 var simpleUri = uri.scheme == 'file' ? path.relative(uri.path) : "$uri"; |
| 114 return '[$runtimeType: $simpleUri]'; | 120 return '[$runtimeType: $simpleUri]'; |
| 115 } | 121 } |
| (...skipping 30 matching lines...) Expand all Loading... |
| 146 void update(SourceGraph graph) { | 152 void update(SourceGraph graph) { |
| 147 super.update(graph); | 153 super.update(graph); |
| 148 if (needsRebuild) { | 154 if (needsRebuild) { |
| 149 graph._reporter.clearHtml(uri); | 155 graph._reporter.clearHtml(uri); |
| 150 document = html.parse(source.contents.data, generateSpans: true); | 156 document = html.parse(source.contents.data, generateSpans: true); |
| 151 var newScripts = new Set<DartSourceNode>(); | 157 var newScripts = new Set<DartSourceNode>(); |
| 152 var tags = document.querySelectorAll('script[type="application/dart"]'); | 158 var tags = document.querySelectorAll('script[type="application/dart"]'); |
| 153 for (var script in tags) { | 159 for (var script in tags) { |
| 154 var src = script.attributes['src']; | 160 var src = script.attributes['src']; |
| 155 if (src == null) { | 161 if (src == null) { |
| 156 graph._reporter.enterHtml(source.uri); | 162 _reportError(graph, 'inlined script tags not supported at this time ' |
| 157 graph._reporter.log(new DependencyGraphError( | |
| 158 'inlined script tags not supported at this time ' | |
| 159 '(see https://github.com/dart-lang/dart-dev-compiler/issues/54).', | 163 '(see https://github.com/dart-lang/dart-dev-compiler/issues/54).', |
| 160 script.sourceSpan)); | 164 script); |
| 161 graph._reporter.leaveHtml(); | |
| 162 continue; | 165 continue; |
| 163 } | 166 } |
| 164 var node = graph.nodeFromUri(uri.resolve(src)); | 167 var node = graph.nodeFromUri(uri.resolve(src)); |
| 165 if (node == null || !node.source.exists()) { | 168 if (node == null || !node.source.exists()) { |
| 166 graph._reporter.enterHtml(source.uri); | 169 _reportError(graph, 'Script file $src not found', script); |
| 167 graph._reporter.log(new DependencyGraphError( | |
| 168 'Script file $src not found', script.sourceSpan)); | |
| 169 graph._reporter.leaveHtml(); | |
| 170 } | 170 } |
| 171 if (node != null) newScripts.add(node); | 171 if (node != null) newScripts.add(node); |
| 172 } | 172 } |
| 173 | 173 |
| 174 if (!_same(newScripts, scripts)) { | 174 if (!_same(newScripts, scripts)) { |
| 175 structureChanged = true; | 175 structureChanged = true; |
| 176 scripts = newScripts; | 176 scripts = newScripts; |
| 177 } | 177 } |
| 178 } | 178 } |
| 179 } | 179 } |
| 180 |
| 181 void _reportError(SourceGraph graph, String message, Node node) { |
| 182 graph._reporter.enterHtml(source.uri); |
| 183 graph._reporter.log(new DependencyGraphError(message, node.sourceSpan)); |
| 184 graph._reporter.leaveHtml(); |
| 185 } |
| 180 } | 186 } |
| 181 | 187 |
| 182 /// A node representing a Dart library or part. | 188 /// A node representing a Dart library or part. |
| 183 class DartSourceNode extends SourceNode { | 189 class DartSourceNode extends SourceNode { |
| 184 /// Set of imported libraries (empty for part files). | 190 /// Set of imported libraries (empty for part files). |
| 185 Set<DartSourceNode> imports = new Set<DartSourceNode>(); | 191 Set<DartSourceNode> imports = new Set<DartSourceNode>(); |
| 186 | 192 |
| 187 /// Set of exported libraries (empty for part files). | 193 /// Set of exported libraries (empty for part files). |
| 188 Set<DartSourceNode> exports = new Set<DartSourceNode>(); | 194 Set<DartSourceNode> exports = new Set<DartSourceNode>(); |
| 189 | 195 |
| (...skipping 23 matching lines...) Expand all Loading... |
| 213 // If the defining compilation-unit changed, the structure might have | 219 // If the defining compilation-unit changed, the structure might have |
| 214 // changed. | 220 // changed. |
| 215 var unit = parseDirectives(source.contents.data, name: source.fullName); | 221 var unit = parseDirectives(source.contents.data, name: source.fullName); |
| 216 var newImports = new Set<DartSourceNode>(); | 222 var newImports = new Set<DartSourceNode>(); |
| 217 var newExports = new Set<DartSourceNode>(); | 223 var newExports = new Set<DartSourceNode>(); |
| 218 var newParts = new Set<DartSourceNode>(); | 224 var newParts = new Set<DartSourceNode>(); |
| 219 for (var d in unit.directives) { | 225 for (var d in unit.directives) { |
| 220 // Nothing to do for parts. | 226 // Nothing to do for parts. |
| 221 if (d is PartOfDirective) return; | 227 if (d is PartOfDirective) return; |
| 222 if (d is LibraryDirective) continue; | 228 if (d is LibraryDirective) continue; |
| 229 |
| 230 // `dart:core` and other similar URLs only contain a name, but it is |
| 231 // meant to be a folder when resolving relative paths from it. |
| 232 var targetUri = uri.scheme == 'dart' && uri.pathSegments.length == 1 |
| 233 ? Uri.parse('$uri/').resolve(d.uri.stringValue) |
| 234 : uri.resolve(d.uri.stringValue); |
| 223 var target = | 235 var target = |
| 224 ParseDartTask.resolveDirective(graph._context, source, d, null); | 236 ParseDartTask.resolveDirective(graph._context, source, d, null); |
| 225 var uri = target.uri; | 237 if (target != null) { |
| 226 var node = | 238 if (targetUri != target.uri) print(">> ${target.uri} $targetUri"); |
| 227 graph.nodes.putIfAbsent(uri, () => new DartSourceNode(uri, target)); | 239 } |
| 228 if (!node.source.exists()) { | 240 var node = graph.nodes.putIfAbsent( |
| 229 graph._reporter.enterLibrary(source.uri); | 241 targetUri, () => new DartSourceNode(targetUri, target)); |
| 230 graph._reporter.log(new DependencyGraphError( | 242 //var node = graph.nodeFromUri(targetUri); |
| 231 'File $uri not found', spanForNode(unit, source, d))); | 243 if (node.source == null || !node.source.exists()) { |
| 232 graph._reporter.leaveLibrary(); | 244 _reportError(graph, 'File $targetUri not found', unit, d); |
| 233 } | 245 } |
| 234 | 246 |
| 235 if (d is ImportDirective) { | 247 if (d is ImportDirective) { |
| 236 newImports.add(node); | 248 newImports.add(node); |
| 237 } else if (d is ExportDirective) { | 249 } else if (d is ExportDirective) { |
| 238 newExports.add(node); | 250 newExports.add(node); |
| 239 } else if (d is PartDirective) { | 251 } else if (d is PartDirective) { |
| 240 newParts.add(node); | 252 newParts.add(node); |
| 241 } | 253 } |
| 242 } | 254 } |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 275 // internally: | 287 // internally: |
| 276 for (var p in parts) { | 288 for (var p in parts) { |
| 277 // Technically for parts we don't need to look at the contents. If they | 289 // Technically for parts we don't need to look at the contents. If they |
| 278 // contain imports, exports, or parts, we'll ignore them in our crawling. | 290 // contain imports, exports, or parts, we'll ignore them in our crawling. |
| 279 // However we do a full update to make it easier to adjust when users | 291 // However we do a full update to make it easier to adjust when users |
| 280 // switch a file from a part to a library. | 292 // switch a file from a part to a library. |
| 281 p.update(graph); | 293 p.update(graph); |
| 282 if (p.needsRebuild) needsRebuild = true; | 294 if (p.needsRebuild) needsRebuild = true; |
| 283 } | 295 } |
| 284 } | 296 } |
| 297 |
| 298 void _reportError( |
| 299 SourceGraph graph, String message, CompilationUnit unit, AstNode node) { |
| 300 graph._reporter.enterLibrary(source.uri); |
| 301 graph._reporter.log( |
| 302 new DependencyGraphError(message, spanForNode(unit, source, node))); |
| 303 graph._reporter.leaveLibrary(); |
| 304 } |
| 285 } | 305 } |
| 286 | 306 |
| 287 /// Represents a runtime resource from our compiler that is needed to run an | 307 /// Represents a runtime resource from our compiler that is needed to run an |
| 288 /// application. | 308 /// application. |
| 289 class ResourceSourceNode extends SourceNode { | 309 class ResourceSourceNode extends SourceNode { |
| 290 ResourceSourceNode(uri, source) : super(uri, source); | 310 ResourceSourceNode(uri, source) : super(uri, source); |
| 291 } | 311 } |
| 292 | 312 |
| 293 /// Updates the structure and `needsRebuild` marks in nodes of [graph] reachable | 313 /// Updates the structure and `needsRebuild` marks in nodes of [graph] reachable |
| 294 /// from [start]. | 314 /// from [start]. |
| (...skipping 113 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 408 helper(start); | 428 helper(start); |
| 409 } | 429 } |
| 410 | 430 |
| 411 bool _same(Set a, Set b) => a.length == b.length && a.containsAll(b); | 431 bool _same(Set a, Set b) => a.length == b.length && a.containsAll(b); |
| 412 | 432 |
| 413 /// An error message discovered while parsing the dependencies between files. | 433 /// An error message discovered while parsing the dependencies between files. |
| 414 class DependencyGraphError extends MessageWithSpan { | 434 class DependencyGraphError extends MessageWithSpan { |
| 415 const DependencyGraphError(String message, SourceSpan span) | 435 const DependencyGraphError(String message, SourceSpan span) |
| 416 : super(message, Level.SEVERE, span); | 436 : super(message, Level.SEVERE, span); |
| 417 } | 437 } |
| OLD | NEW |