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 CompilationUnit, | 18 CompilationUnit, |
18 Identifier; | 19 Identifier; |
19 import 'package:analyzer/src/generated/engine.dart' | 20 import 'package:analyzer/src/generated/engine.dart' |
20 show ParseDartTask, AnalysisContext; | 21 show ParseDartTask, AnalysisContext; |
21 import 'package:analyzer/src/generated/source.dart' show Source, SourceKind; | 22 import 'package:analyzer/src/generated/source.dart' show Source, SourceKind; |
22 import 'package:html5lib/dom.dart' show Document; | 23 import 'package:html5lib/dom.dart' show Document; |
23 import 'package:html5lib/parser.dart' as html; | 24 import 'package:html5lib/parser.dart' as html; |
24 import 'package:logging/logging.dart' show Logger; | 25 import 'package:logging/logging.dart' show Logger; |
25 import 'package:path/path.dart' as path; | 26 import 'package:path/path.dart' as path; |
26 | 27 |
27 import 'info.dart'; | 28 import 'info.dart'; |
28 import 'options.dart'; | 29 import 'options.dart'; |
29 import 'utils.dart'; | 30 import 'utils.dart'; |
30 | 31 |
31 /// Holds references to all source nodes in the import graph. This is mainly | 32 /// Holds references to all source nodes in the import graph. This is mainly |
32 /// used as a level of indirection to ensure that each source has a canonical | 33 /// used as a level of indirection to ensure that each source has a canonical |
33 /// representation. | 34 /// representation. |
34 class SourceGraph { | 35 class SourceGraph { |
35 /// All nodes in the source graph. Used to get a canonical representation for | 36 /// All nodes in the source graph. Used to get a canonical representation for |
36 /// any node. | 37 /// any node. |
37 final Map<Uri, SourceNode> nodes = {}; | 38 final Map<Uri, SourceNode> nodes = {}; |
38 | 39 |
39 /// Analyzer used to resolve source files. | 40 /// Analyzer used to resolve source files. |
40 final AnalysisContext _context; | 41 final AnalysisContext _context; |
41 final CompilerOptions _options; | 42 final CompilerOptions _options; |
42 | 43 |
43 SourceGraph(this._context, this._options); | 44 SourceGraph(this._context, this._options); |
44 | 45 |
45 /// Node associated with a resolved [uri]. | 46 /// Node associated with a resolved [uri]. |
46 SourceNode nodeFromUri(Uri uri, [bool isPart = false]) { | 47 SourceNode nodeFromUri(Uri uri) { |
47 var uriString = Uri.encodeFull('$uri'); | 48 var uriString = Uri.encodeFull('$uri'); |
48 var kind = uriString.endsWith('.html') | |
49 ? SourceKind.HTML | |
50 : isPart ? SourceKind.PART : SourceKind.LIBRARY; | |
51 return nodeFor(uri, _context.sourceFactory.forUri(uriString), kind); | |
52 } | |
53 | |
54 /// Construct the node of the given [kind] with the given [uri] and [source]. | |
55 SourceNode nodeFor(Uri uri, Source source, SourceKind kind) { | |
56 // TODO(sigmund): validate canonicalization? | |
57 // TODO(sigmund): add support for changing a file from one kind to another | |
58 // (e.g. converting a file from a part to a library). | |
59 return nodes.putIfAbsent(uri, () { | 49 return nodes.putIfAbsent(uri, () { |
60 if (kind == SourceKind.HTML) { | 50 var source = _context.sourceFactory.forUri(uriString); |
| 51 var extension = path.extension(uriString); |
| 52 if (extension == '.html') { |
61 return new HtmlSourceNode(uri, source); | 53 return new HtmlSourceNode(uri, source); |
62 } else if (kind == SourceKind.LIBRARY) { | 54 } else if (extension == '.dart' || uriString.startsWith('dart:')) { |
63 return new LibrarySourceNode(uri, source); | 55 return new DartSourceNode(uri, source); |
64 } else if (kind == SourceKind.PART) { | |
65 return new PartSourceNode(uri, source); | |
66 } else { | 56 } else { |
67 assert(false); // unreachable | 57 assert(false); // unreachable |
68 } | 58 } |
69 }); | 59 }); |
70 } | 60 } |
71 } | 61 } |
72 | 62 |
73 /// A node in the import graph representing a source file. | 63 /// A node in the import graph representing a source file. |
74 abstract class SourceNode { | 64 abstract class SourceNode { |
75 /// Resolved URI for this node. | 65 /// Resolved URI for this node. |
76 final Uri uri; | 66 final Uri uri; |
77 | 67 |
78 /// Resolved source from the analyzer. We let the analyzer internally track | 68 /// Resolved source from the analyzer. We let the analyzer internally track |
79 /// for modifications to the source files. | 69 /// for modifications to the source files. |
80 final Source source; | 70 final Source source; |
81 | 71 |
82 /// Last stamp read from `source.modificationStamp`. | 72 /// Last stamp read from `source.modificationStamp`. |
83 int _lastStamp = 0; | 73 int _lastStamp = 0; |
84 | 74 |
85 /// Whether we need to rebuild this source file. | 75 /// Whether we need to rebuild this source file. |
86 bool needsRebuild = false; | 76 bool needsRebuild = false; |
87 | 77 |
88 /// Whether the structure of dependencies from this node (scripts, imports, | 78 /// Whether the structure of dependencies from this node (scripts, imports, |
89 /// exports, or parts) changed after we reparsed its contents. | 79 /// exports, or parts) changed after we reparsed its contents. |
90 bool structureChanged = false; | 80 bool structureChanged = false; |
91 | 81 |
92 /// Direct dependencies (script tags for HtmlSourceNodes; imports, exports and | 82 /// Direct dependencies in the [SourceGraph]. These include script tags for |
93 /// parts for LibrarySourceNodes). | 83 /// [HtmlSourceNode]s; and imports, exports and parts for [DartSourceNode]s. |
94 Iterable<SourceNode> get directDeps; | 84 Iterable<SourceNode> get allDeps; |
| 85 |
| 86 /// Like [allDeps] but excludes parts for [DartSourceNode]s. For many |
| 87 /// operations we mainly care about dependencies at the library level, so |
| 88 /// parts are excluded from this list. |
| 89 Iterable<SourceNode> get depsWithoutParts; |
95 | 90 |
96 SourceNode(this.uri, this.source); | 91 SourceNode(this.uri, this.source); |
97 | 92 |
98 /// Check for whether the file has changed and, if so, mark [needsRebuild] and | 93 /// Check for whether the file has changed and, if so, mark [needsRebuild] and |
99 /// [structureChanged] as necessary. | 94 /// [structureChanged] as necessary. |
100 void update(SourceGraph graph) { | 95 void update(SourceGraph graph) { |
101 int newStamp = source.modificationStamp; | 96 int newStamp = source.modificationStamp; |
102 if (newStamp > _lastStamp) { | 97 if (newStamp > _lastStamp) { |
103 _lastStamp = newStamp; | 98 _lastStamp = newStamp; |
104 needsRebuild = true; | 99 needsRebuild = true; |
105 } | 100 } |
106 } | 101 } |
107 | 102 |
108 String toString() { | 103 String toString() { |
109 var simpleUri = uri.scheme == 'file' ? path.relative(uri.path) : "$uri"; | 104 var simpleUri = uri.scheme == 'file' ? path.relative(uri.path) : "$uri"; |
110 return '[$runtimeType: $simpleUri]'; | 105 return '[$runtimeType: $simpleUri]'; |
111 } | 106 } |
112 } | 107 } |
113 | 108 |
114 /// A node representing an entry HTML source file. | 109 /// A node representing an entry HTML source file. |
115 class HtmlSourceNode extends SourceNode { | 110 class HtmlSourceNode extends SourceNode { |
116 /// Libraries referred to via script tags. | 111 /// Libraries referred to via script tags. |
117 Set<LibrarySourceNode> scripts = new Set<LibrarySourceNode>(); | 112 Set<DartSourceNode> scripts = new Set<DartSourceNode>(); |
118 | 113 |
119 @override | 114 @override |
120 Iterable<SourceNode> get directDeps => scripts; | 115 Iterable<SourceNode> get allDeps => scripts; |
| 116 |
| 117 @override |
| 118 Iterable<SourceNode> get depsWithoutParts => scripts; |
121 | 119 |
122 /// Parsed document, updated whenever [update] is invoked. | 120 /// Parsed document, updated whenever [update] is invoked. |
123 Document document; | 121 Document document; |
124 | 122 |
125 HtmlSourceNode(uri, source) : super(uri, source); | 123 HtmlSourceNode(uri, source) : super(uri, source); |
126 | 124 |
127 void update(SourceGraph graph) { | 125 void update(SourceGraph graph) { |
128 super.update(graph); | 126 super.update(graph); |
129 if (needsRebuild) { | 127 if (needsRebuild) { |
130 document = html.parse(source.contents.data, generateSpans: true); | 128 document = html.parse(source.contents.data, generateSpans: true); |
131 var newScripts = new Set<LibrarySourceNode>(); | 129 var newScripts = new Set<DartSourceNode>(); |
132 var tags = document.querySelectorAll('script[type="application/dart"]'); | 130 var tags = document.querySelectorAll('script[type="application/dart"]'); |
133 for (var script in tags) { | 131 for (var script in tags) { |
134 var src = script.attributes['src']; | 132 var src = script.attributes['src']; |
135 if (src == null) { | 133 if (src == null) { |
136 // TODO(sigmund): expose these as compile-time failures | 134 // TODO(sigmund): expose these as compile-time failures |
137 _log.severe(script.sourceSpan.message( | 135 _log.severe(script.sourceSpan.message( |
138 'inlined script tags not supported at this time ' | 136 'inlined script tags not supported at this time ' |
139 '(see https://github.com/dart-lang/dart-dev-compiler/issues/54).', | 137 '(see https://github.com/dart-lang/dart-dev-compiler/issues/54).', |
140 color: graph._options.useColors ? colorOf('error') : false)); | 138 color: graph._options.useColors ? colorOf('error') : false)); |
141 continue; | 139 continue; |
142 } | 140 } |
143 var node = graph.nodeFromUri(uri.resolve(src)); | 141 var node = graph.nodeFromUri(uri.resolve(src)); |
144 if (!node.source.exists()) { | 142 if (!node.source.exists()) { |
145 _log.severe(script.sourceSpan.message('Script file $src not found', | 143 _log.severe(script.sourceSpan.message('Script file $src not found', |
146 color: graph._options.useColors ? colorOf('error') : false)); | 144 color: graph._options.useColors ? colorOf('error') : false)); |
147 } | 145 } |
148 newScripts.add(node); | 146 newScripts.add(node); |
149 } | 147 } |
150 | 148 |
151 if (!_same(newScripts, scripts)) { | 149 if (!_same(newScripts, scripts)) { |
152 structureChanged = true; | 150 structureChanged = true; |
153 scripts = newScripts; | 151 scripts = newScripts; |
154 } | 152 } |
155 } | 153 } |
156 } | 154 } |
157 } | 155 } |
158 | 156 |
159 /// A node representing a Dart part. | 157 /// A node representing a Dart library or part. |
160 class PartSourceNode extends SourceNode { | 158 class DartSourceNode extends SourceNode { |
161 final Iterable<SourceNode> directDeps = const []; | 159 /// Set of imported libraries (empty for part files). |
162 PartSourceNode(uri, source) : super(uri, source); | 160 Set<DartSourceNode> imports = new Set<DartSourceNode>(); |
163 } | |
164 | 161 |
165 /// A node representing a Dart library. | 162 /// Set of exported libraries (empty for part files). |
166 class LibrarySourceNode extends SourceNode { | 163 Set<DartSourceNode> exports = new Set<DartSourceNode>(); |
167 LibrarySourceNode(uri, source) : super(uri, source); | |
168 | 164 |
169 Set<LibrarySourceNode> imports = new Set<LibrarySourceNode>(); | 165 /// Parts of this library (empty for part files). |
170 Set<LibrarySourceNode> exports = new Set<LibrarySourceNode>(); | 166 Set<DartSourceNode> parts = new Set<DartSourceNode>(); |
171 Set<PartSourceNode> parts = new Set<PartSourceNode>(); | |
172 | 167 |
173 Iterable<SourceNode> get directDeps => | 168 /// How many times this file is included as a part. |
| 169 int includedAsPart = 0; |
| 170 |
| 171 DartSourceNode(uri, source) : super(uri, source); |
| 172 |
| 173 @override |
| 174 Iterable<SourceNode> get allDeps => |
174 [imports, exports, parts].expand((e) => e); | 175 [imports, exports, parts].expand((e) => e); |
175 | 176 |
| 177 @override |
| 178 Iterable<SourceNode> get depsWithoutParts => |
| 179 [imports, exports].expand((e) => e); |
| 180 |
176 LibraryInfo info; | 181 LibraryInfo info; |
177 | 182 |
178 void update(SourceGraph graph) { | 183 void update(SourceGraph graph) { |
179 super.update(graph); | 184 super.update(graph); |
| 185 |
180 if (needsRebuild && source.contents.data != null) { | 186 if (needsRebuild && source.contents.data != null) { |
181 // If the defining compilation-unit changed, the structure might have | 187 // If the defining compilation-unit changed, the structure might have |
182 // changed. | 188 // changed. |
183 var unit = parseDirectives(source.contents.data, name: source.fullName); | 189 var unit = parseDirectives(source.contents.data, name: source.fullName); |
184 var newImports = new Set<LibrarySourceNode>(); | 190 var newImports = new Set<DartSourceNode>(); |
185 var newExports = new Set<LibrarySourceNode>(); | 191 var newExports = new Set<DartSourceNode>(); |
186 var newParts = new Set<PartSourceNode>(); | 192 var newParts = new Set<DartSourceNode>(); |
187 for (var d in unit.directives) { | 193 for (var d in unit.directives) { |
| 194 // Nothing to do for parts. |
| 195 if (d is PartOfDirective) return; |
188 if (d is LibraryDirective) continue; | 196 if (d is LibraryDirective) continue; |
189 var target = | 197 var target = |
190 ParseDartTask.resolveDirective(graph._context, source, d, null); | 198 ParseDartTask.resolveDirective(graph._context, source, d, null); |
191 var uri = target.uri; | 199 var uri = target.uri; |
192 var node = graph.nodeFor(uri, target, | 200 var node = |
193 d is PartDirective ? SourceKind.PART : SourceKind.LIBRARY); | 201 graph.nodes.putIfAbsent(uri, () => new DartSourceNode(uri, target)); |
194 if (!node.source.exists()) { | 202 if (!node.source.exists()) { |
195 _log.severe(spanForNode(unit, source, d).message( | 203 _log.severe(spanForNode(unit, source, d).message( |
196 'File $uri not found', | 204 'File $uri not found', |
197 color: graph._options.useColors ? colorOf('error') : false)); | 205 color: graph._options.useColors ? colorOf('error') : false)); |
198 } | 206 } |
199 | 207 |
200 if (d is ImportDirective) { | 208 if (d is ImportDirective) { |
201 newImports.add(node); | 209 newImports.add(node); |
202 } else if (d is ExportDirective) { | 210 } else if (d is ExportDirective) { |
203 newExports.add(node); | 211 newExports.add(node); |
204 } else if (d is PartDirective) { | 212 } else if (d is PartDirective) { |
205 newParts.add(node); | 213 newParts.add(node); |
206 } | 214 } |
207 } | 215 } |
208 | 216 |
209 if (!_same(newImports, imports)) { | 217 if (!_same(newImports, imports)) { |
210 structureChanged = true; | 218 structureChanged = true; |
211 imports = newImports; | 219 imports = newImports; |
212 } | 220 } |
213 | 221 |
214 if (!_same(newExports, exports)) { | 222 if (!_same(newExports, exports)) { |
215 structureChanged = true; | 223 structureChanged = true; |
216 exports = newExports; | 224 exports = newExports; |
217 } | 225 } |
218 | 226 |
219 if (!_same(newParts, parts)) { | 227 if (!_same(newParts, parts)) { |
220 structureChanged = true; | 228 structureChanged = true; |
| 229 |
| 230 // When parts are removed, it's possible they were updated to be |
| 231 // imported as a library |
| 232 for (var p in parts) { |
| 233 if (newParts.contains(p)) continue; |
| 234 if (--p.includedAsPart == 0) { |
| 235 p.needsRebuild = true; |
| 236 } |
| 237 } |
| 238 |
| 239 for (var p in newParts) { |
| 240 if (parts.contains(p)) continue; |
| 241 p.includedAsPart++; |
| 242 } |
221 parts = newParts; | 243 parts = newParts; |
222 } | 244 } |
223 } | 245 } |
224 | 246 |
225 // The library should be marked as needing rebuild if a part changed | 247 // The library should be marked as needing rebuild if a part changed |
226 // internally: | 248 // internally: |
227 for (var p in parts) { | 249 for (var p in parts) { |
| 250 // Technically for parts we don't need to look at the contents. If they |
| 251 // contain imports, exports, or parts, we'll ignore them in our crawling. |
| 252 // However we do a full update to make it easier to adjust when users |
| 253 // switch a file from a part to a library. |
228 p.update(graph); | 254 p.update(graph); |
229 if (p.needsRebuild) needsRebuild = true; | 255 if (p.needsRebuild) needsRebuild = true; |
230 } | 256 } |
231 } | 257 } |
232 } | 258 } |
233 | 259 |
234 /// Updates the structure and `needsRebuild` marks in nodes of [graph] reachable | 260 /// Updates the structure and `needsRebuild` marks in nodes of [graph] reachable |
235 /// from [start]. | 261 /// from [start]. |
236 /// | 262 /// |
237 /// That is, staring from [start], we update the graph by detecting file changes | 263 /// That is, staring from [start], we update the graph by detecting file changes |
238 /// and rebuilding the structure of the graph wherever it changed (an import was | 264 /// and rebuilding the structure of the graph wherever it changed (an import was |
239 /// added or removed, etc). | 265 /// added or removed, etc). |
240 /// | 266 /// |
241 /// After calling this function a node is marked with `needsRebuild` only if it | 267 /// After calling this function a node is marked with `needsRebuild` only if it |
242 /// contained local changes. Rebuild decisions that derive from transitive | 268 /// contained local changes. Rebuild decisions that derive from transitive |
243 /// changes (e.g. when the API of a dependency changed) are handled later in | 269 /// changes (e.g. when the API of a dependency changed) are handled later in |
244 /// [rebuild]. | 270 /// [rebuild]. |
245 void refreshStructureAndMarks(SourceNode start, SourceGraph graph) { | 271 void refreshStructureAndMarks(SourceNode start, SourceGraph graph) { |
246 visitInPreOrder(start, (n) => n.update(graph)); | 272 visitInPreOrder(start, (n) => n.update(graph), includeParts: false); |
247 } | 273 } |
248 | 274 |
249 /// Clears all the `needsRebuild` and `structureChanged` marks in nodes | 275 /// Clears all the `needsRebuild` and `structureChanged` marks in nodes |
250 /// reachable from [start]. | 276 /// reachable from [start]. |
251 void clearMarks(SourceNode start) { | 277 void clearMarks(SourceNode start) { |
252 visitInPreOrder(start, (n) => n.needsRebuild = n.structureChanged = false); | 278 visitInPreOrder(start, (n) => n.needsRebuild = n.structureChanged = false, |
| 279 includeParts: true); |
253 } | 280 } |
254 | 281 |
255 /// Traverses from [start] with the purpose of building any source that needs to | 282 /// Traverses from [start] with the purpose of building any source that needs to |
256 /// be rebuilt. | 283 /// be rebuilt. |
257 /// | 284 /// |
258 /// This function will call [build] in a post-order fashion, on a subset of the | 285 /// This function will call [build] in a post-order fashion, on a subset of the |
259 /// reachable nodes. There are four rules used to decide when to rebuild a node | 286 /// reachable nodes. There are four rules used to decide when to rebuild a node |
260 /// (call [build] on a node): | 287 /// (call [build] on a node): |
261 /// | 288 /// |
262 /// * Only rebuild Dart libraries ([LibrarySourceNode]) or HTML files | 289 /// * Only rebuild Dart libraries ([DartSourceNode]) or HTML files |
263 /// ([HtmlSourceNode]), but never part files ([PartSourceNode]). That is | 290 /// ([HtmlSourceNode]), but skip part files. That is because those are |
264 /// because those are built as part of some library. | 291 /// built as part of some library. |
265 /// | 292 /// |
266 /// * Always rebuild [LibrarySourceNode]s and [HtmlSourceNode]s with local | 293 /// * Always rebuild [DartSourceNode]s and [HtmlSourceNode]s with local |
267 /// changes or changes in a part of the library. Internally this function | 294 /// changes or changes in a part of the library. Internally this function |
268 /// calls [refreshStructureAndMarks] to ensure that the graph structure is | 295 /// calls [refreshStructureAndMarks] to ensure that the graph structure is |
269 /// up-to-date and that these nodes with local changes contain the | 296 /// up-to-date and that these nodes with local changes contain the |
270 /// `needsRebuild` bit. | 297 /// `needsRebuild` bit. |
271 /// | 298 /// |
272 /// * Rebuild [HtmlSourceNode]s if there were structural changes somewhere | 299 /// * Rebuild [HtmlSourceNode]s if there were structural changes somewhere |
273 /// down its reachable subgraph. This is done because HTML files embed the | 300 /// down its reachable subgraph. This is done because HTML files embed the |
274 /// transitive closure of the import graph in their output. | 301 /// transitive closure of the import graph in their output. |
275 /// | 302 /// |
276 /// * Rebuild [LibrarySourceNode]s that depend on other [LibrarySourceNode]s | 303 /// * Rebuild [DartSourceNode]s that depend on other [DartSourceNode]s |
277 /// whose API may have changed. The result of [build] is used to determine | 304 /// whose API may have changed. The result of [build] is used to determine |
278 /// whether other nodes need to be rebuilt. The function [build] is expected | 305 /// whether other nodes need to be rebuilt. The function [build] is expected |
279 /// to return `true` on a node `n` if it detemines other nodes that import | 306 /// to return `true` on a node `n` if it detemines other nodes that import |
280 /// `n` may need to be rebuilt as well. | 307 /// `n` may need to be rebuilt as well. |
281 rebuild(SourceNode start, SourceGraph graph, bool build(SourceNode node)) { | 308 rebuild(SourceNode start, SourceGraph graph, bool build(SourceNode node)) { |
282 refreshStructureAndMarks(start, graph); | 309 refreshStructureAndMarks(start, graph); |
283 // Hold which source nodes may have changed their public API, this includes | 310 // Hold which source nodes may have changed their public API, this includes |
284 // libraries that were modified or libraries that export other modified APIs. | 311 // libraries that were modified or libraries that export other modified APIs. |
285 // TODO(sigmund): consider removing this special support for exports? Many | 312 // TODO(sigmund): consider removing this special support for exports? Many |
286 // cases anways require using summaries to understand what parts of the public | 313 // cases anways require using summaries to understand what parts of the public |
287 // API may be affected by transitive changes. The re-export case is just one | 314 // API may be affected by transitive changes. The re-export case is just one |
288 // of those transitive cases, but is not sufficient. See | 315 // of those transitive cases, but is not sufficient. See |
289 // https://github.com/dart-lang/dev_compiler/issues/76 | 316 // https://github.com/dart-lang/dev_compiler/issues/76 |
290 var apiChangeDetected = new HashSet<SourceNode>(); | 317 var apiChangeDetected = new HashSet<SourceNode>(); |
291 bool structureHasChanged = false; | 318 bool structureHasChanged = false; |
292 | 319 |
293 bool shouldBuildNode(SourceNode n) { | 320 bool shouldBuildNode(SourceNode n) { |
294 if (n is PartSourceNode) return false; | |
295 if (n.needsRebuild) return true; | 321 if (n.needsRebuild) return true; |
296 if (n is HtmlSourceNode) return structureHasChanged; | 322 if (n is HtmlSourceNode) return structureHasChanged; |
297 return (n as LibrarySourceNode).imports | 323 return (n as DartSourceNode).imports |
298 .any((i) => apiChangeDetected.contains(i)); | 324 .any((i) => apiChangeDetected.contains(i)); |
299 } | 325 } |
300 | 326 |
301 visitInPostOrder(start, (n) { | 327 visitInPostOrder(start, (n) { |
302 if (n.structureChanged) structureHasChanged = true; | 328 if (n.structureChanged) structureHasChanged = true; |
303 if (shouldBuildNode(n)) { | 329 if (shouldBuildNode(n)) { |
304 if (build(n)) apiChangeDetected.add(n); | 330 if (build(n)) apiChangeDetected.add(n); |
305 } else if (n is LibrarySourceNode && | 331 } else if (n is DartSourceNode && |
306 n.exports.any((e) => apiChangeDetected.contains(e))) { | 332 n.exports.any((e) => apiChangeDetected.contains(e))) { |
307 apiChangeDetected.add(n); | 333 apiChangeDetected.add(n); |
308 } | 334 } |
309 n.needsRebuild = false; | 335 n.needsRebuild = false; |
310 n.structureChanged = false; | 336 n.structureChanged = false; |
311 }); | 337 if (n is DartSourceNode) { |
| 338 // Note: clearing out flags in the parts could be a problem if someone |
| 339 // tries to use a file both as a part and a library at the same time. |
| 340 // In that case, we might not correctly propagate changes in the places |
| 341 // where it is used as a library. Technically it's not allowed to have a |
| 342 // file as a part and a library at once, and the analyzer should report an |
| 343 // error in that case. |
| 344 n.parts.forEach((p) => p.needsRebuild = p.structureChanged = false); |
| 345 } |
| 346 }, includeParts: false); |
312 } | 347 } |
313 | 348 |
314 /// Helper that runs [action] on nodes reachable from [node] in pre-order. | 349 /// Helper that runs [action] on nodes reachable from [start] in pre-order. |
315 visitInPreOrder(SourceNode node, void action(SourceNode node), | 350 visitInPreOrder(SourceNode start, void action(SourceNode node), |
316 {Set<SourceNode> seen}) { | 351 {bool includeParts: false}) { |
317 if (seen == null) seen = new HashSet<SourceNode>(); | 352 var seen = new HashSet<SourceNode>(); |
318 if (!seen.add(node)) return; | 353 helper(SourceNode node) { |
319 action(node); | 354 if (!seen.add(node)) return; |
320 node.directDeps.forEach((d) => visitInPreOrder(d, action, seen: seen)); | 355 action(node); |
| 356 var deps = includeParts ? node.allDeps : node.depsWithoutParts; |
| 357 deps.forEach(helper); |
| 358 } |
| 359 helper(start); |
321 } | 360 } |
322 | 361 |
323 /// Helper that runs [action] on nodes reachable from [node] in post-order. | 362 /// Helper that runs [action] on nodes reachable from [start] in post-order. |
324 visitInPostOrder(SourceNode node, void action(SourceNode node), | 363 visitInPostOrder(SourceNode start, void action(SourceNode node), |
325 {Set<SourceNode> seen}) { | 364 {bool includeParts: false}) { |
326 if (seen == null) seen = new HashSet<SourceNode>(); | 365 var seen = new HashSet<SourceNode>(); |
327 if (!seen.add(node)) return; | 366 helper(SourceNode node) { |
328 node.directDeps.forEach((d) => visitInPostOrder(d, action, seen: seen)); | 367 if (!seen.add(node)) return; |
329 action(node); | 368 var deps = includeParts ? node.allDeps : node.depsWithoutParts; |
| 369 deps.forEach(helper); |
| 370 action(node); |
| 371 } |
| 372 helper(start); |
330 } | 373 } |
331 | 374 |
332 bool _same(Set a, Set b) => a.length == b.length && a.containsAll(b); | 375 bool _same(Set a, Set b) => a.length == b.length && a.containsAll(b); |
333 final _log = new Logger('dev_compiler.graph'); | 376 final _log = new Logger('dev_compiler.graph'); |
OLD | NEW |