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 Level; | 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 import 'package:source_span/source_span.dart' show SourceSpan; |
28 | 28 |
29 import 'info.dart'; | 29 import 'info.dart'; |
| 30 import 'options.dart'; |
30 import 'report.dart'; | 31 import 'report.dart'; |
31 import 'utils.dart'; | 32 import 'utils.dart'; |
32 | 33 |
33 /// Holds references to all source nodes in the import graph. This is mainly | 34 /// Holds references to all source nodes in the import graph. This is mainly |
34 /// used as a level of indirection to ensure that each source has a canonical | 35 /// used as a level of indirection to ensure that each source has a canonical |
35 /// representation. | 36 /// representation. |
36 class SourceGraph { | 37 class SourceGraph { |
37 /// All nodes in the source graph. Used to get a canonical representation for | 38 /// All nodes in the source graph. Used to get a canonical representation for |
38 /// any node. | 39 /// any node. |
39 final Map<Uri, SourceNode> nodes = {}; | 40 final Map<Uri, SourceNode> nodes = {}; |
40 | 41 |
41 /// Analyzer used to resolve source files. | 42 /// Analyzer used to resolve source files. |
42 final AnalysisContext _context; | 43 final AnalysisContext _context; |
43 final CheckerReporter _reporter; | 44 final CheckerReporter _reporter; |
| 45 final CompilerOptions _options; |
44 | 46 |
45 SourceGraph(this._context, this._reporter); | 47 SourceGraph(this._context, this._reporter, this._options); |
46 | 48 |
47 /// Node associated with a resolved [uri]. | 49 /// Node associated with a resolved [uri]. |
48 SourceNode nodeFromUri(Uri uri) { | 50 SourceNode nodeFromUri(Uri uri) { |
49 var uriString = Uri.encodeFull('$uri'); | 51 var uriString = Uri.encodeFull('$uri'); |
50 return nodes.putIfAbsent(uri, () { | 52 return nodes.putIfAbsent(uri, () { |
51 var source = _context.sourceFactory.forUri(uriString); | 53 var source = _context.sourceFactory.forUri(uriString); |
52 var extension = path.extension(uriString); | 54 var extension = path.extension(uriString); |
53 if (extension == '.html') { | 55 if (extension == '.html') { |
54 return new HtmlSourceNode(uri, source, this); | 56 return new HtmlSourceNode(uri, source, this); |
55 } else if (extension == '.dart' || uriString.startsWith('dart:')) { | 57 } else if (extension == '.dart' || uriString.startsWith('dart:')) { |
(...skipping 12 matching lines...) Expand all Loading... |
68 /// Resolved URI for this node. | 70 /// Resolved URI for this node. |
69 final Uri uri; | 71 final Uri uri; |
70 | 72 |
71 /// Resolved source from the analyzer. We let the analyzer internally track | 73 /// Resolved source from the analyzer. We let the analyzer internally track |
72 /// for modifications to the source files. | 74 /// for modifications to the source files. |
73 final Source source; | 75 final Source source; |
74 | 76 |
75 /// Last stamp read from `source.modificationStamp`. | 77 /// Last stamp read from `source.modificationStamp`. |
76 int _lastStamp = 0; | 78 int _lastStamp = 0; |
77 | 79 |
| 80 /// A hash used to help browsers cache the output that would be produced from |
| 81 /// building this node. |
| 82 String cachingHash; |
| 83 |
78 /// Whether we need to rebuild this source file. | 84 /// Whether we need to rebuild this source file. |
79 bool needsRebuild = false; | 85 bool needsRebuild = false; |
80 | 86 |
81 /// Whether the structure of dependencies from this node (scripts, imports, | 87 /// Whether the structure of dependencies from this node (scripts, imports, |
82 /// exports, or parts) changed after we reparsed its contents. | 88 /// exports, or parts) changed after we reparsed its contents. |
83 bool structureChanged = false; | 89 bool structureChanged = false; |
84 | 90 |
85 /// Direct dependencies in the [SourceGraph]. These include script tags for | 91 /// Direct dependencies in the [SourceGraph]. These include script tags for |
86 /// [HtmlSourceNode]s; and imports, exports and parts for [DartSourceNode]s. | 92 /// [HtmlSourceNode]s; and imports, exports and parts for [DartSourceNode]s. |
87 Iterable<SourceNode> get allDeps => const []; | 93 Iterable<SourceNode> get allDeps => const []; |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
121 Iterable<SourceNode> get allDeps => [scripts, runtimeDeps].expand((e) => e); | 127 Iterable<SourceNode> get allDeps => [scripts, runtimeDeps].expand((e) => e); |
122 | 128 |
123 @override | 129 @override |
124 Iterable<SourceNode> get depsWithoutParts => allDeps; | 130 Iterable<SourceNode> get depsWithoutParts => allDeps; |
125 | 131 |
126 /// Parsed document, updated whenever [update] is invoked. | 132 /// Parsed document, updated whenever [update] is invoked. |
127 Document document; | 133 Document document; |
128 | 134 |
129 HtmlSourceNode(uri, source, graph) : super(uri, source) { | 135 HtmlSourceNode(uri, source, graph) : super(uri, source) { |
130 var prefix = 'package:dev_compiler/runtime'; | 136 var prefix = 'package:dev_compiler/runtime'; |
131 runtimeDeps | 137 var files = ['harmony_feature_check.js', 'dart_runtime.js']; |
132 ..add(graph.nodeFromUri(Uri.parse('$prefix/dart_runtime.js'))) | 138 if (graph._options.serverMode) { |
133 ..add(graph.nodeFromUri(Uri.parse('$prefix/harmony_feature_check.js'))) | 139 files.addAll(const ['messages_widget.js', 'messages.css']); |
134 ..add(graph.nodeFromUri(Uri.parse('$prefix/messages_widget.js'))) | 140 } |
135 ..add(graph.nodeFromUri(Uri.parse('$prefix/messages.css'))); | 141 files.forEach((file) { |
| 142 runtimeDeps.add(graph.nodeFromUri(Uri.parse('$prefix/$file'))); |
| 143 }); |
136 } | 144 } |
137 | 145 |
138 void update(SourceGraph graph) { | 146 void update(SourceGraph graph) { |
139 super.update(graph); | 147 super.update(graph); |
140 if (needsRebuild) { | 148 if (needsRebuild) { |
141 graph._reporter.clearHtml(uri); | 149 graph._reporter.clearHtml(uri); |
142 document = html.parse(source.contents.data, generateSpans: true); | 150 document = html.parse(source.contents.data, generateSpans: true); |
143 var newScripts = new Set<DartSourceNode>(); | 151 var newScripts = new Set<DartSourceNode>(); |
144 var tags = document.querySelectorAll('script[type="application/dart"]'); | 152 var tags = document.querySelectorAll('script[type="application/dart"]'); |
145 for (var script in tags) { | 153 for (var script in tags) { |
(...skipping 187 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
333 rebuild(SourceNode start, SourceGraph graph, bool build(SourceNode node)) { | 341 rebuild(SourceNode start, SourceGraph graph, bool build(SourceNode node)) { |
334 refreshStructureAndMarks(start, graph); | 342 refreshStructureAndMarks(start, graph); |
335 // Hold which source nodes may have changed their public API, this includes | 343 // Hold which source nodes may have changed their public API, this includes |
336 // libraries that were modified or libraries that export other modified APIs. | 344 // libraries that were modified or libraries that export other modified APIs. |
337 // TODO(sigmund): consider removing this special support for exports? Many | 345 // TODO(sigmund): consider removing this special support for exports? Many |
338 // cases anways require using summaries to understand what parts of the public | 346 // cases anways require using summaries to understand what parts of the public |
339 // API may be affected by transitive changes. The re-export case is just one | 347 // API may be affected by transitive changes. The re-export case is just one |
340 // of those transitive cases, but is not sufficient. See | 348 // of those transitive cases, but is not sufficient. See |
341 // https://github.com/dart-lang/dev_compiler/issues/76 | 349 // https://github.com/dart-lang/dev_compiler/issues/76 |
342 var apiChangeDetected = new HashSet<SourceNode>(); | 350 var apiChangeDetected = new HashSet<SourceNode>(); |
343 bool structureHasChanged = false; | 351 bool htmlNeedsRebuild = false; |
344 | 352 |
345 bool shouldBuildNode(SourceNode n) { | 353 bool shouldBuildNode(SourceNode n) { |
346 if (n.needsRebuild) return true; | 354 if (n.needsRebuild) return true; |
347 if (n is HtmlSourceNode) return structureHasChanged; | 355 if (n is HtmlSourceNode) return htmlNeedsRebuild; |
348 if (n is ResourceSourceNode) return false; | 356 if (n is ResourceSourceNode) return false; |
349 return (n as DartSourceNode).imports | 357 return (n as DartSourceNode).imports |
350 .any((i) => apiChangeDetected.contains(i)); | 358 .any((i) => apiChangeDetected.contains(i)); |
351 } | 359 } |
352 | 360 |
353 visitInPostOrder(start, (n) { | 361 visitInPostOrder(start, (n) { |
354 if (n.structureChanged) structureHasChanged = true; | 362 if (n.structureChanged) htmlNeedsRebuild = true; |
355 if (shouldBuildNode(n)) { | 363 if (shouldBuildNode(n)) { |
| 364 var oldHash = n.cachingHash; |
356 if (build(n)) apiChangeDetected.add(n); | 365 if (build(n)) apiChangeDetected.add(n); |
| 366 if (oldHash != n.cachingHash) htmlNeedsRebuild = true; |
357 } else if (n is DartSourceNode && | 367 } else if (n is DartSourceNode && |
358 n.exports.any((e) => apiChangeDetected.contains(e))) { | 368 n.exports.any((e) => apiChangeDetected.contains(e))) { |
359 apiChangeDetected.add(n); | 369 apiChangeDetected.add(n); |
360 } | 370 } |
361 n.needsRebuild = false; | 371 n.needsRebuild = false; |
362 n.structureChanged = false; | 372 n.structureChanged = false; |
363 if (n is DartSourceNode) { | 373 if (n is DartSourceNode) { |
364 // Note: clearing out flags in the parts could be a problem if someone | 374 // Note: clearing out flags in the parts could be a problem if someone |
365 // tries to use a file both as a part and a library at the same time. | 375 // tries to use a file both as a part and a library at the same time. |
366 // In that case, we might not correctly propagate changes in the places | 376 // In that case, we might not correctly propagate changes in the places |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
398 helper(start); | 408 helper(start); |
399 } | 409 } |
400 | 410 |
401 bool _same(Set a, Set b) => a.length == b.length && a.containsAll(b); | 411 bool _same(Set a, Set b) => a.length == b.length && a.containsAll(b); |
402 | 412 |
403 /// An error message discovered while parsing the dependencies between files. | 413 /// An error message discovered while parsing the dependencies between files. |
404 class DependencyGraphError extends MessageWithSpan { | 414 class DependencyGraphError extends MessageWithSpan { |
405 const DependencyGraphError(String message, SourceSpan span) | 415 const DependencyGraphError(String message, SourceSpan span) |
406 : super(message, Level.SEVERE, span); | 416 : super(message, Level.SEVERE, span); |
407 } | 417 } |
OLD | NEW |