@@ -14,6 +14,7 @@ import 'package:analyzer/src/generated/ast.dart'
14
14
ImportDirective,
15
15
ExportDirective,
16
16
PartDirective,
17
+ PartOfDirective,
17
18
CompilationUnit,
18
19
Identifier;
19
20
import 'package:analyzer/src/generated/engine.dart'
@@ -43,26 +44,15 @@ class SourceGraph {
43
44
SourceGraph (this ._context, this ._options);
44
45
45
46
/// Node associated with a resolved [uri] .
46
- SourceNode nodeFromUri (Uri uri, [ bool isPart = false ] ) {
47
+ SourceNode nodeFromUri (Uri uri) {
47
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
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
53
return new HtmlSourceNode (uri, source);
62
- } else if (kind == SourceKind .LIBRARY ) {
63
- return new LibrarySourceNode (uri, source);
64
- } else if (kind == SourceKind .PART ) {
65
- return new PartSourceNode (uri, source);
54
+ } else if (extension == '.dart' || uriString.startsWith ('dart:' )) {
55
+ return new DartSourceNode (uri, source);
66
56
} else {
67
57
assert (false ); // unreachable
68
58
}
@@ -89,9 +79,14 @@ abstract class SourceNode {
89
79
/// exports, or parts) changed after we reparsed its contents.
90
80
bool structureChanged = false ;
91
81
92
- /// Direct dependencies (script tags for HtmlSourceNodes; imports, exports and
93
- /// parts for LibrarySourceNodes).
94
- Iterable <SourceNode > get directDeps;
82
+ /// Direct dependencies in the [SourceGraph] . These include script tags for
83
+ /// [HtmlSourceNode] s; and imports, exports and parts for [DartSourceNode] s.
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
91
SourceNode (this .uri, this .source);
97
92
@@ -114,10 +109,13 @@ abstract class SourceNode {
114
109
/// A node representing an entry HTML source file.
115
110
class HtmlSourceNode extends SourceNode {
116
111
/// Libraries referred to via script tags.
117
- Set <LibrarySourceNode > scripts = new Set <LibrarySourceNode >();
112
+ Set <DartSourceNode > scripts = new Set <DartSourceNode >();
113
+
114
+ @override
115
+ Iterable <SourceNode > get allDeps => scripts;
118
116
119
117
@override
120
- Iterable <SourceNode > get directDeps => scripts;
118
+ Iterable <SourceNode > get depsWithoutParts => scripts;
121
119
122
120
/// Parsed document, updated whenever [update] is invoked.
123
121
Document document;
@@ -128,7 +126,7 @@ class HtmlSourceNode extends SourceNode {
128
126
super .update (graph);
129
127
if (needsRebuild) {
130
128
document = html.parse (source.contents.data, generateSpans: true );
131
- var newScripts = new Set <LibrarySourceNode >();
129
+ var newScripts = new Set <DartSourceNode >();
132
130
var tags = document.querySelectorAll ('script[type="application/dart"]' );
133
131
for (var script in tags) {
134
132
var src = script.attributes['src' ];
@@ -156,41 +154,51 @@ class HtmlSourceNode extends SourceNode {
156
154
}
157
155
}
158
156
159
- /// A node representing a Dart part.
160
- class PartSourceNode extends SourceNode {
161
- final Iterable <SourceNode > directDeps = const [];
162
- PartSourceNode (uri, source) : super (uri, source);
163
- }
157
+ /// A node representing a Dart library or part.
158
+ class DartSourceNode extends SourceNode {
159
+ /// Set of imported libraries (empty for part files).
160
+ Set <DartSourceNode > imports = new Set <DartSourceNode >();
161
+
162
+ /// Set of exported libraries (empty for part files).
163
+ Set <DartSourceNode > exports = new Set <DartSourceNode >();
164
+
165
+ /// Parts of this library (empty for part files).
166
+ Set <DartSourceNode > parts = new Set <DartSourceNode >();
164
167
165
- /// A node representing a Dart library.
166
- class LibrarySourceNode extends SourceNode {
167
- LibrarySourceNode (uri, source) : super (uri, source);
168
+ /// How many times this file is included as a part.
169
+ int includedAsPart = 0 ;
168
170
169
- Set <LibrarySourceNode > imports = new Set <LibrarySourceNode >();
170
- Set <LibrarySourceNode > exports = new Set <LibrarySourceNode >();
171
- Set <PartSourceNode > parts = new Set <PartSourceNode >();
171
+ DartSourceNode (uri, source) : super (uri, source);
172
172
173
- Iterable <SourceNode > get directDeps =>
173
+ @override
174
+ Iterable <SourceNode > get allDeps =>
174
175
[imports, exports, parts].expand ((e) => e);
175
176
177
+ @override
178
+ Iterable <SourceNode > get depsWithoutParts =>
179
+ [imports, exports].expand ((e) => e);
180
+
176
181
LibraryInfo info;
177
182
178
183
void update (SourceGraph graph) {
179
184
super .update (graph);
185
+
180
186
if (needsRebuild && source.contents.data != null ) {
181
187
// If the defining compilation-unit changed, the structure might have
182
188
// changed.
183
189
var unit = parseDirectives (source.contents.data, name: source.fullName);
184
- var newImports = new Set <LibrarySourceNode >();
185
- var newExports = new Set <LibrarySourceNode >();
186
- var newParts = new Set <PartSourceNode >();
190
+ var newImports = new Set <DartSourceNode >();
191
+ var newExports = new Set <DartSourceNode >();
192
+ var newParts = new Set <DartSourceNode >();
187
193
for (var d in unit.directives) {
194
+ // Nothing to do for parts.
195
+ if (d is PartOfDirective ) return ;
188
196
if (d is LibraryDirective ) continue ;
189
197
var target =
190
198
ParseDartTask .resolveDirective (graph._context, source, d, null );
191
199
var uri = target.uri;
192
- var node = graph. nodeFor (uri, target,
193
- d is PartDirective ? SourceKind . PART : SourceKind . LIBRARY );
200
+ var node =
201
+ graph.nodes. putIfAbsent (uri, () => new DartSourceNode (uri, target) );
194
202
if (! node.source.exists ()) {
195
203
_log.severe (spanForNode (unit, source, d).message (
196
204
'File $uri not found' ,
@@ -218,13 +226,31 @@ class LibrarySourceNode extends SourceNode {
218
226
219
227
if (! _same (newParts, parts)) {
220
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
243
parts = newParts;
222
244
}
223
245
}
224
246
225
247
// The library should be marked as needing rebuild if a part changed
226
248
// internally:
227
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
254
p.update (graph);
229
255
if (p.needsRebuild) needsRebuild = true ;
230
256
}
@@ -243,13 +269,14 @@ class LibrarySourceNode extends SourceNode {
243
269
/// changes (e.g. when the API of a dependency changed) are handled later in
244
270
/// [rebuild] .
245
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
275
/// Clears all the `needsRebuild` and `structureChanged` marks in nodes
250
276
/// reachable from [start] .
251
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
282
/// Traverses from [start] with the purpose of building any source that needs to
@@ -259,11 +286,11 @@ void clearMarks(SourceNode start) {
259
286
/// reachable nodes. There are four rules used to decide when to rebuild a node
260
287
/// (call [build] on a node):
261
288
///
262
- /// * Only rebuild Dart libraries ([LibrarySourceNode ] ) or HTML files
263
- /// ([HtmlSourceNode]), but never part files ([PartSourceNode]) . That is
264
- /// because those are built as part of some library.
289
+ /// * Only rebuild Dart libraries ([DartSourceNode ] ) or HTML files
290
+ /// ([HtmlSourceNode]), but skip part files. That is because those are
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
294
/// changes or changes in a part of the library. Internally this function
268
295
/// calls [refreshStructureAndMarks] to ensure that the graph structure is
269
296
/// up-to-date and that these nodes with local changes contain the
@@ -273,7 +300,7 @@ void clearMarks(SourceNode start) {
273
300
/// down its reachable subgraph. This is done because HTML files embed the
274
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
304
/// whose API may have changed. The result of [build] is used to determine
278
305
/// whether other nodes need to be rebuilt. The function [build] is expected
279
306
/// to return `true` on a node `n` if it detemines other nodes that import
@@ -291,42 +318,58 @@ rebuild(SourceNode start, SourceGraph graph, bool build(SourceNode node)) {
291
318
bool structureHasChanged = false ;
292
319
293
320
bool shouldBuildNode (SourceNode n) {
294
- if (n is PartSourceNode ) return false ;
295
321
if (n.needsRebuild) return true ;
296
322
if (n is HtmlSourceNode ) return structureHasChanged;
297
- return (n as LibrarySourceNode ).imports
323
+ return (n as DartSourceNode ).imports
298
324
.any ((i) => apiChangeDetected.contains (i));
299
325
}
300
326
301
327
visitInPostOrder (start, (n) {
302
328
if (n.structureChanged) structureHasChanged = true ;
303
329
if (shouldBuildNode (n)) {
304
330
if (build (n)) apiChangeDetected.add (n);
305
- } else if (n is LibrarySourceNode &&
331
+ } else if (n is DartSourceNode &&
306
332
n.exports.any ((e) => apiChangeDetected.contains (e))) {
307
333
apiChangeDetected.add (n);
308
334
}
309
335
n.needsRebuild = false ;
310
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.
315
- visitInPreOrder (SourceNode node, void action (SourceNode node),
316
- {Set <SourceNode > seen}) {
317
- if (seen == null ) seen = new HashSet <SourceNode >();
318
- if (! seen.add (node)) return ;
319
- action (node);
320
- node.directDeps.forEach ((d) => visitInPreOrder (d, action, seen: seen));
349
+ /// Helper that runs [action] on nodes reachable from [start] in pre-order.
350
+ visitInPreOrder (SourceNode start, void action (SourceNode node),
351
+ {bool includeParts: false }) {
352
+ var seen = new HashSet <SourceNode >();
353
+ helper (SourceNode node) {
354
+ if (! seen.add (node)) return ;
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.
324
- visitInPostOrder (SourceNode node, void action (SourceNode node),
325
- {Set <SourceNode > seen}) {
326
- if (seen == null ) seen = new HashSet <SourceNode >();
327
- if (! seen.add (node)) return ;
328
- node.directDeps.forEach ((d) => visitInPostOrder (d, action, seen: seen));
329
- action (node);
362
+ /// Helper that runs [action] on nodes reachable from [start] in post-order.
363
+ visitInPostOrder (SourceNode start, void action (SourceNode node),
364
+ {bool includeParts: false }) {
365
+ var seen = new HashSet <SourceNode >();
366
+ helper (SourceNode node) {
367
+ if (! seen.add (node)) return ;
368
+ var deps = includeParts ? node.allDeps : node.depsWithoutParts;
369
+ deps.forEach (helper);
370
+ action (node);
371
+ }
372
+ helper (start);
330
373
}
331
374
332
375
bool _same (Set a, Set b) => a.length == b.length && a.containsAll (b);
0 commit comments