Skip to content

Commit 0991bea

Browse files
committed
Allow changing a file from a part to a library, and viceversa.
BUG= [email protected] Review URL: https://codereview.chromium.org/983553002
1 parent 122a190 commit 0991bea

File tree

4 files changed

+454
-84
lines changed

4 files changed

+454
-84
lines changed

pkg/dev_compiler/lib/devc.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ class Compiler {
9797
bool _buildSource(SourceNode node) {
9898
if (node is HtmlSourceNode) {
9999
_buildHtmlFile(node);
100-
} else if (node is LibrarySourceNode) {
100+
} else if (node is DartSourceNode) {
101101
_buildDartLibrary(node);
102102
} else {
103103
assert(false); // should not get a build request on PartSourceNode
@@ -134,12 +134,12 @@ class Compiler {
134134
_devCompilerRuntimeCopied = true;
135135
}
136136

137-
bool _isEntry(LibrarySourceNode node) {
138-
if (_entryNode is LibrarySourceNode) return _entryNode == node;
137+
bool _isEntry(DartSourceNode node) {
138+
if (_entryNode is DartSourceNode) return _entryNode == node;
139139
return (_entryNode as HtmlSourceNode).scripts.contains(node);
140140
}
141141

142-
void _buildDartLibrary(LibrarySourceNode node) {
142+
void _buildDartLibrary(DartSourceNode node) {
143143
var source = node.source;
144144
// TODO(sigmund): find out from analyzer team if there is a better way
145145
_resolver.context.applyChanges(new ChangeSet()..changedSource(source));

pkg/dev_compiler/lib/src/codegen/html_codegen.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ String generateEntryHtml(HtmlSourceNode root, CompilerOptions options) {
4343

4444
var libraries = [];
4545
visitInPostOrder(root, (n) {
46-
if (n is LibrarySourceNode) libraries.add(n);
47-
});
46+
if (n is DartSourceNode) libraries.add(n);
47+
}, includeParts: false);
4848

4949
String mainLibraryName;
5050
var fragment = _loadRuntimeScripts();

pkg/dev_compiler/lib/src/dependency_graph.dart

Lines changed: 108 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import 'package:analyzer/src/generated/ast.dart'
1414
ImportDirective,
1515
ExportDirective,
1616
PartDirective,
17+
PartOfDirective,
1718
CompilationUnit,
1819
Identifier;
1920
import 'package:analyzer/src/generated/engine.dart'
@@ -43,26 +44,15 @@ class SourceGraph {
4344
SourceGraph(this._context, this._options);
4445

4546
/// Node associated with a resolved [uri].
46-
SourceNode nodeFromUri(Uri uri, [bool isPart = false]) {
47+
SourceNode nodeFromUri(Uri uri) {
4748
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).
5949
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') {
6153
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);
6656
} else {
6757
assert(false); // unreachable
6858
}
@@ -89,9 +79,14 @@ abstract class SourceNode {
8979
/// exports, or parts) changed after we reparsed its contents.
9080
bool structureChanged = false;
9181

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;
9590

9691
SourceNode(this.uri, this.source);
9792

@@ -114,10 +109,13 @@ abstract class SourceNode {
114109
/// A node representing an entry HTML source file.
115110
class HtmlSourceNode extends SourceNode {
116111
/// 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;
118116

119117
@override
120-
Iterable<SourceNode> get directDeps => scripts;
118+
Iterable<SourceNode> get depsWithoutParts => scripts;
121119

122120
/// Parsed document, updated whenever [update] is invoked.
123121
Document document;
@@ -128,7 +126,7 @@ class HtmlSourceNode extends SourceNode {
128126
super.update(graph);
129127
if (needsRebuild) {
130128
document = html.parse(source.contents.data, generateSpans: true);
131-
var newScripts = new Set<LibrarySourceNode>();
129+
var newScripts = new Set<DartSourceNode>();
132130
var tags = document.querySelectorAll('script[type="application/dart"]');
133131
for (var script in tags) {
134132
var src = script.attributes['src'];
@@ -156,41 +154,51 @@ class HtmlSourceNode extends SourceNode {
156154
}
157155
}
158156

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>();
164167

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;
168170

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);
172172

173-
Iterable<SourceNode> get directDeps =>
173+
@override
174+
Iterable<SourceNode> get allDeps =>
174175
[imports, exports, parts].expand((e) => e);
175176

177+
@override
178+
Iterable<SourceNode> get depsWithoutParts =>
179+
[imports, exports].expand((e) => e);
180+
176181
LibraryInfo info;
177182

178183
void update(SourceGraph graph) {
179184
super.update(graph);
185+
180186
if (needsRebuild && source.contents.data != null) {
181187
// If the defining compilation-unit changed, the structure might have
182188
// changed.
183189
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>();
187193
for (var d in unit.directives) {
194+
// Nothing to do for parts.
195+
if (d is PartOfDirective) return;
188196
if (d is LibraryDirective) continue;
189197
var target =
190198
ParseDartTask.resolveDirective(graph._context, source, d, null);
191199
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));
194202
if (!node.source.exists()) {
195203
_log.severe(spanForNode(unit, source, d).message(
196204
'File $uri not found',
@@ -218,13 +226,31 @@ class LibrarySourceNode extends SourceNode {
218226

219227
if (!_same(newParts, parts)) {
220228
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+
}
221243
parts = newParts;
222244
}
223245
}
224246

225247
// The library should be marked as needing rebuild if a part changed
226248
// internally:
227249
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.
228254
p.update(graph);
229255
if (p.needsRebuild) needsRebuild = true;
230256
}
@@ -243,13 +269,14 @@ class LibrarySourceNode extends SourceNode {
243269
/// changes (e.g. when the API of a dependency changed) are handled later in
244270
/// [rebuild].
245271
void refreshStructureAndMarks(SourceNode start, SourceGraph graph) {
246-
visitInPreOrder(start, (n) => n.update(graph));
272+
visitInPreOrder(start, (n) => n.update(graph), includeParts: false);
247273
}
248274

249275
/// Clears all the `needsRebuild` and `structureChanged` marks in nodes
250276
/// reachable from [start].
251277
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);
253280
}
254281

255282
/// Traverses from [start] with the purpose of building any source that needs to
@@ -259,11 +286,11 @@ void clearMarks(SourceNode start) {
259286
/// reachable nodes. There are four rules used to decide when to rebuild a node
260287
/// (call [build] on a node):
261288
///
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.
265292
///
266-
/// * Always rebuild [LibrarySourceNode]s and [HtmlSourceNode]s with local
293+
/// * Always rebuild [DartSourceNode]s and [HtmlSourceNode]s with local
267294
/// changes or changes in a part of the library. Internally this function
268295
/// calls [refreshStructureAndMarks] to ensure that the graph structure is
269296
/// up-to-date and that these nodes with local changes contain the
@@ -273,7 +300,7 @@ void clearMarks(SourceNode start) {
273300
/// down its reachable subgraph. This is done because HTML files embed the
274301
/// transitive closure of the import graph in their output.
275302
///
276-
/// * Rebuild [LibrarySourceNode]s that depend on other [LibrarySourceNode]s
303+
/// * Rebuild [DartSourceNode]s that depend on other [DartSourceNode]s
277304
/// whose API may have changed. The result of [build] is used to determine
278305
/// whether other nodes need to be rebuilt. The function [build] is expected
279306
/// 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)) {
291318
bool structureHasChanged = false;
292319

293320
bool shouldBuildNode(SourceNode n) {
294-
if (n is PartSourceNode) return false;
295321
if (n.needsRebuild) return true;
296322
if (n is HtmlSourceNode) return structureHasChanged;
297-
return (n as LibrarySourceNode).imports
323+
return (n as DartSourceNode).imports
298324
.any((i) => apiChangeDetected.contains(i));
299325
}
300326

301327
visitInPostOrder(start, (n) {
302328
if (n.structureChanged) structureHasChanged = true;
303329
if (shouldBuildNode(n)) {
304330
if (build(n)) apiChangeDetected.add(n);
305-
} else if (n is LibrarySourceNode &&
331+
} else if (n is DartSourceNode &&
306332
n.exports.any((e) => apiChangeDetected.contains(e))) {
307333
apiChangeDetected.add(n);
308334
}
309335
n.needsRebuild = false;
310336
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);
312347
}
313348

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);
321360
}
322361

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);
330373
}
331374

332375
bool _same(Set a, Set b) => a.length == b.length && a.containsAll(b);

0 commit comments

Comments
 (0)