Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(497)

Side by Side Diff: lib/src/dependency_graph.dart

Issue 988483006: Add widget to display errors, and report dependency_graph errors correctly (Closed) Base URL: git@github.com:dart-lang/dev_compiler.git@master
Patch Set: Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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 Logger; 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 28
28 import 'info.dart'; 29 import 'info.dart';
29 import 'options.dart'; 30 import 'report.dart';
30 import 'utils.dart'; 31 import 'utils.dart';
31 32
32 /// Holds references to all source nodes in the import graph. This is mainly 33 /// Holds references to all source nodes in the import graph. This is mainly
33 /// used as a level of indirection to ensure that each source has a canonical 34 /// used as a level of indirection to ensure that each source has a canonical
34 /// representation. 35 /// representation.
35 class SourceGraph { 36 class SourceGraph {
36 /// All nodes in the source graph. Used to get a canonical representation for 37 /// All nodes in the source graph. Used to get a canonical representation for
37 /// any node. 38 /// any node.
38 final Map<Uri, SourceNode> nodes = {}; 39 final Map<Uri, SourceNode> nodes = {};
39 40
40 /// Analyzer used to resolve source files. 41 /// Analyzer used to resolve source files.
41 final AnalysisContext _context; 42 final AnalysisContext _context;
42 final CompilerOptions _options; 43 final CheckerReporter _reporter;
43 44
44 SourceGraph(this._context, this._options); 45 SourceGraph(this._context, this._reporter);
45 46
46 /// Node associated with a resolved [uri]. 47 /// Node associated with a resolved [uri].
47 SourceNode nodeFromUri(Uri uri) { 48 SourceNode nodeFromUri(Uri uri) {
48 var uriString = Uri.encodeFull('$uri'); 49 var uriString = Uri.encodeFull('$uri');
49 return nodes.putIfAbsent(uri, () { 50 return nodes.putIfAbsent(uri, () {
50 var source = _context.sourceFactory.forUri(uriString); 51 var source = _context.sourceFactory.forUri(uriString);
51 var extension = path.extension(uriString); 52 var extension = path.extension(uriString);
52 if (extension == '.html') { 53 if (extension == '.html') {
53 return new HtmlSourceNode(uri, source, this); 54 return new HtmlSourceNode(uri, source, this);
54 } else if (extension == '.dart' || uriString.startsWith('dart:')) { 55 } else if (extension == '.dart' || uriString.startsWith('dart:')) {
55 return new DartSourceNode(uri, source); 56 return new DartSourceNode(uri, source);
56 } else if (extension == '.js') { 57 } else if (extension == '.js' || extension == '.css') {
57 return new JavaScriptSourceNode(uri, source); 58 return new ResourceSourceNode(uri, source);
58 } else { 59 } else {
59 assert(false); // unreachable 60 assert(false); // unreachable
60 } 61 }
61 }); 62 });
62 } 63 }
63 } 64 }
64 65
65 /// A node in the import graph representing a source file. 66 /// A node in the import graph representing a source file.
66 abstract class SourceNode { 67 abstract class SourceNode {
67 /// Resolved URI for this node. 68 /// Resolved URI for this node.
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
103 } 104 }
104 105
105 String toString() { 106 String toString() {
106 var simpleUri = uri.scheme == 'file' ? path.relative(uri.path) : "$uri"; 107 var simpleUri = uri.scheme == 'file' ? path.relative(uri.path) : "$uri";
107 return '[$runtimeType: $simpleUri]'; 108 return '[$runtimeType: $simpleUri]';
108 } 109 }
109 } 110 }
110 111
111 /// A node representing an entry HTML source file. 112 /// A node representing an entry HTML source file.
112 class HtmlSourceNode extends SourceNode { 113 class HtmlSourceNode extends SourceNode {
113 /// Javascript dependencies, included by default on any application. 114 /// Resources included by default on any application.
114 Set<JavaScriptSourceNode> runtimeDeps = new Set<JavaScriptSourceNode>(); 115 Set<ResourceSourceNode> runtimeDeps = new Set<ResourceSourceNode>();
115 116
116 /// Libraries referred to via script tags. 117 /// Libraries referred to via script tags.
117 Set<DartSourceNode> scripts = new Set<DartSourceNode>(); 118 Set<DartSourceNode> scripts = new Set<DartSourceNode>();
118 119
119 @override 120 @override
120 Iterable<SourceNode> get allDeps => [scripts, runtimeDeps].expand((e) => e); 121 Iterable<SourceNode> get allDeps => [scripts, runtimeDeps].expand((e) => e);
121 122
122 @override 123 @override
123 Iterable<SourceNode> get depsWithoutParts => allDeps; 124 Iterable<SourceNode> get depsWithoutParts => allDeps;
124 125
125 /// Parsed document, updated whenever [update] is invoked. 126 /// Parsed document, updated whenever [update] is invoked.
126 Document document; 127 Document document;
127 128
128 HtmlSourceNode(uri, source, graph) : super(uri, source) { 129 HtmlSourceNode(uri, source, graph) : super(uri, source) {
129 var prefix = 'package:dev_compiler/runtime'; 130 var prefix = 'package:dev_compiler/runtime';
130 runtimeDeps 131 runtimeDeps
131 ..add(graph.nodeFromUri(Uri.parse('$prefix/dart_runtime.js'))) 132 ..add(graph.nodeFromUri(Uri.parse('$prefix/dart_runtime.js')))
132 ..add(graph.nodeFromUri(Uri.parse('$prefix/harmony_feature_check.js'))); 133 ..add(graph.nodeFromUri(Uri.parse('$prefix/harmony_feature_check.js')))
134 ..add(graph.nodeFromUri(Uri.parse('$prefix/messages_widget.js')))
135 ..add(graph.nodeFromUri(Uri.parse('$prefix/messages.css')));
133 } 136 }
134 137
135 void update(SourceGraph graph) { 138 void update(SourceGraph graph) {
136 super.update(graph); 139 super.update(graph);
137 if (needsRebuild) { 140 if (needsRebuild) {
141 graph._reporter.clearHtml(uri);
138 document = html.parse(source.contents.data, generateSpans: true); 142 document = html.parse(source.contents.data, generateSpans: true);
139 var newScripts = new Set<DartSourceNode>(); 143 var newScripts = new Set<DartSourceNode>();
140 var tags = document.querySelectorAll('script[type="application/dart"]'); 144 var tags = document.querySelectorAll('script[type="application/dart"]');
141 for (var script in tags) { 145 for (var script in tags) {
142 var src = script.attributes['src']; 146 var src = script.attributes['src'];
143 if (src == null) { 147 if (src == null) {
144 // TODO(sigmund): expose these as compile-time failures 148 graph._reporter.enterHtml(source.uri);
145 _log.severe(script.sourceSpan.message( 149 graph._reporter.log(new DependencyGraphError(
146 'inlined script tags not supported at this time ' 150 'inlined script tags not supported at this time '
147 '(see https://github.com/dart-lang/dart-dev-compiler/issues/54).', 151 '(see https://github.com/dart-lang/dart-dev-compiler/issues/54).',
148 color: graph._options.useColors ? colorOf('error') : false)); 152 script.sourceSpan));
153 graph._reporter.leaveHtml();
149 continue; 154 continue;
150 } 155 }
151 var node = graph.nodeFromUri(uri.resolve(src)); 156 var node = graph.nodeFromUri(uri.resolve(src));
152 if (!node.source.exists()) { 157 if (node == null || !node.source.exists()) {
153 _log.severe(script.sourceSpan.message('Script file $src not found', 158 graph._reporter.enterHtml(source.uri);
154 color: graph._options.useColors ? colorOf('error') : false)); 159 graph._reporter.log(new DependencyGraphError(
160 'Script file $src not found', script.sourceSpan));
161 graph._reporter.leaveHtml();
155 } 162 }
156 newScripts.add(node); 163 if (node != null) newScripts.add(node);
157 } 164 }
158 165
159 if (!_same(newScripts, scripts)) { 166 if (!_same(newScripts, scripts)) {
160 structureChanged = true; 167 structureChanged = true;
161 scripts = newScripts; 168 scripts = newScripts;
162 } 169 }
163 } 170 }
164 } 171 }
165 } 172 }
166 173
(...skipping 20 matching lines...) Expand all
187 @override 194 @override
188 Iterable<SourceNode> get depsWithoutParts => 195 Iterable<SourceNode> get depsWithoutParts =>
189 [imports, exports].expand((e) => e); 196 [imports, exports].expand((e) => e);
190 197
191 LibraryInfo info; 198 LibraryInfo info;
192 199
193 void update(SourceGraph graph) { 200 void update(SourceGraph graph) {
194 super.update(graph); 201 super.update(graph);
195 202
196 if (needsRebuild && source.contents.data != null) { 203 if (needsRebuild && source.contents.data != null) {
204 graph._reporter.clearLibrary(uri);
197 // If the defining compilation-unit changed, the structure might have 205 // If the defining compilation-unit changed, the structure might have
198 // changed. 206 // changed.
199 var unit = parseDirectives(source.contents.data, name: source.fullName); 207 var unit = parseDirectives(source.contents.data, name: source.fullName);
200 var newImports = new Set<DartSourceNode>(); 208 var newImports = new Set<DartSourceNode>();
201 var newExports = new Set<DartSourceNode>(); 209 var newExports = new Set<DartSourceNode>();
202 var newParts = new Set<DartSourceNode>(); 210 var newParts = new Set<DartSourceNode>();
203 for (var d in unit.directives) { 211 for (var d in unit.directives) {
204 // Nothing to do for parts. 212 // Nothing to do for parts.
205 if (d is PartOfDirective) return; 213 if (d is PartOfDirective) return;
206 if (d is LibraryDirective) continue; 214 if (d is LibraryDirective) continue;
207 var target = 215 var target =
208 ParseDartTask.resolveDirective(graph._context, source, d, null); 216 ParseDartTask.resolveDirective(graph._context, source, d, null);
209 var uri = target.uri; 217 var uri = target.uri;
210 var node = 218 var node =
211 graph.nodes.putIfAbsent(uri, () => new DartSourceNode(uri, target)); 219 graph.nodes.putIfAbsent(uri, () => new DartSourceNode(uri, target));
212 if (!node.source.exists()) { 220 if (!node.source.exists()) {
213 _log.severe(spanForNode(unit, source, d).message( 221 graph._reporter.enterLibrary(source.uri);
214 'File $uri not found', 222 graph._reporter.log(new DependencyGraphError(
215 color: graph._options.useColors ? colorOf('error') : false)); 223 'File $uri not found', spanForNode(unit, source, d)));
224 graph._reporter.leaveLibrary();
216 } 225 }
217 226
218 if (d is ImportDirective) { 227 if (d is ImportDirective) {
219 newImports.add(node); 228 newImports.add(node);
220 } else if (d is ExportDirective) { 229 } else if (d is ExportDirective) {
221 newExports.add(node); 230 newExports.add(node);
222 } else if (d is PartDirective) { 231 } else if (d is PartDirective) {
223 newParts.add(node); 232 newParts.add(node);
224 } 233 }
225 } 234 }
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
260 // Technically for parts we don't need to look at the contents. If they 269 // Technically for parts we don't need to look at the contents. If they
261 // contain imports, exports, or parts, we'll ignore them in our crawling. 270 // contain imports, exports, or parts, we'll ignore them in our crawling.
262 // However we do a full update to make it easier to adjust when users 271 // However we do a full update to make it easier to adjust when users
263 // switch a file from a part to a library. 272 // switch a file from a part to a library.
264 p.update(graph); 273 p.update(graph);
265 if (p.needsRebuild) needsRebuild = true; 274 if (p.needsRebuild) needsRebuild = true;
266 } 275 }
267 } 276 }
268 } 277 }
269 278
270 /// Represents a Javascript runtime resource from our compiler that is needed to 279 /// Represents a runtime resource from our compiler that is needed to run an
271 /// run an application. 280 /// application.
272 class JavaScriptSourceNode extends SourceNode { 281 class ResourceSourceNode extends SourceNode {
273 JavaScriptSourceNode(uri, source) : super(uri, source); 282 ResourceSourceNode(uri, source) : super(uri, source);
274 } 283 }
275 284
276 /// Updates the structure and `needsRebuild` marks in nodes of [graph] reachable 285 /// Updates the structure and `needsRebuild` marks in nodes of [graph] reachable
277 /// from [start]. 286 /// from [start].
278 /// 287 ///
279 /// That is, staring from [start], we update the graph by detecting file changes 288 /// That is, staring from [start], we update the graph by detecting file changes
280 /// and rebuilding the structure of the graph wherever it changed (an import was 289 /// and rebuilding the structure of the graph wherever it changed (an import was
281 /// added or removed, etc). 290 /// added or removed, etc).
282 /// 291 ///
283 /// After calling this function a node is marked with `needsRebuild` only if it 292 /// After calling this function a node is marked with `needsRebuild` only if it
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
329 // cases anways require using summaries to understand what parts of the public 338 // cases anways require using summaries to understand what parts of the public
330 // API may be affected by transitive changes. The re-export case is just one 339 // API may be affected by transitive changes. The re-export case is just one
331 // of those transitive cases, but is not sufficient. See 340 // of those transitive cases, but is not sufficient. See
332 // https://github.com/dart-lang/dev_compiler/issues/76 341 // https://github.com/dart-lang/dev_compiler/issues/76
333 var apiChangeDetected = new HashSet<SourceNode>(); 342 var apiChangeDetected = new HashSet<SourceNode>();
334 bool structureHasChanged = false; 343 bool structureHasChanged = false;
335 344
336 bool shouldBuildNode(SourceNode n) { 345 bool shouldBuildNode(SourceNode n) {
337 if (n.needsRebuild) return true; 346 if (n.needsRebuild) return true;
338 if (n is HtmlSourceNode) return structureHasChanged; 347 if (n is HtmlSourceNode) return structureHasChanged;
339 if (n is JavaScriptSourceNode) return false; 348 if (n is ResourceSourceNode) return false;
340 return (n as DartSourceNode).imports 349 return (n as DartSourceNode).imports
341 .any((i) => apiChangeDetected.contains(i)); 350 .any((i) => apiChangeDetected.contains(i));
342 } 351 }
343 352
344 visitInPostOrder(start, (n) { 353 visitInPostOrder(start, (n) {
345 if (n.structureChanged) structureHasChanged = true; 354 if (n.structureChanged) structureHasChanged = true;
346 if (shouldBuildNode(n)) { 355 if (shouldBuildNode(n)) {
347 if (build(n)) apiChangeDetected.add(n); 356 if (build(n)) apiChangeDetected.add(n);
348 } else if (n is DartSourceNode && 357 } else if (n is DartSourceNode &&
349 n.exports.any((e) => apiChangeDetected.contains(e))) { 358 n.exports.any((e) => apiChangeDetected.contains(e))) {
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
383 helper(SourceNode node) { 392 helper(SourceNode node) {
384 if (!seen.add(node)) return; 393 if (!seen.add(node)) return;
385 var deps = includeParts ? node.allDeps : node.depsWithoutParts; 394 var deps = includeParts ? node.allDeps : node.depsWithoutParts;
386 deps.forEach(helper); 395 deps.forEach(helper);
387 action(node); 396 action(node);
388 } 397 }
389 helper(start); 398 helper(start);
390 } 399 }
391 400
392 bool _same(Set a, Set b) => a.length == b.length && a.containsAll(b); 401 bool _same(Set a, Set b) => a.length == b.length && a.containsAll(b);
393 final _log = new Logger('dev_compiler.graph'); 402
403 /// An error message discovered while parsing the dependencies between files.
404 class DependencyGraphError extends MessageWithSpan {
405 const DependencyGraphError(String message, SourceSpan span)
406 : super(message, Level.SEVERE, span);
407 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698