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

Side by Side Diff: sdk/lib/_internal/dartdoc/lib/src/export_map.dart

Issue 14645015: Use LibraryDependencyMirror in dartdoc. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 7 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 | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2013, 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 /// This library uses the Dart analyzer to find the exports for a set of 5 /// This library uses the Dart analyzer to find the exports for a set of
6 /// libraries. It stores these exports in an [ExportMap]. This is used to 6 /// libraries. It stores these exports in an [ExportMap]. This is used to
7 /// display exported members as part of the exporting library, since dart2js 7 /// display exported members as part of the exporting library, since dart2js
8 /// doesn't provide this information itself. 8 /// doesn't provide this information itself.
9 library export_map; 9 library export_map;
10 10
11 import 'dart:io'; 11 import 'dart:io';
12 import 'dart:uri'; 12 import 'dart:uri';
13 13
14 import 'package:analyzer_experimental/src/generated/ast.dart'; 14 import '../../../compiler/implementation/mirrors/mirrors.dart';
15 import 'package:analyzer_experimental/src/generated/error.dart';
16 import 'package:analyzer_experimental/src/generated/parser.dart';
17 import 'package:analyzer_experimental/src/generated/scanner.dart';
18 import 'package:analyzer_experimental/src/generated/source.dart';
19 import 'package:pathos/path.dart' as pathos; 15 import 'package:pathos/path.dart' as pathos;
20 16
21 import 'dartdoc/utils.dart'; 17 import 'dartdoc/utils.dart';
22 18
23 /// A class that tracks which libraries export which other libraries.
24 class ExportMap {
25 /// A map from libraries to their [Export]s.
26 ///
27 /// Each key is the absolute path of a library on the filesystem, and each
28 /// value is a list of [Export]s for that library. There's guaranteed to be
29 /// only one [Export] of a given library in a given list.
30 final Map<String, List<Export>> exports;
31
32 /// A cache of the transitive exports for each library. The keys are paths to
33 /// libraries. The values are maps from the exported path to the [Export]
34 /// objects, to make it easier to merge multiple exports of the same library.
35 final _transitiveExportsByPath = <String, Map<String, Export>>{};
36
37 /// Parse an export map from a set of [libraries], which should be Dart import
38 /// [Uri]s. [packageRoot] should be the path to the `packages` directory to
39 /// use when resolving `package:` imports and libraries. Libraries that are
40 /// not available on the local machine will be ignored.
41 ///
42 /// In addition to parsing the exports in [libraries], this will parse the
43 /// exports in all libraries transitively reachable from [libraries] via
44 /// `import` or `export`.
45 factory ExportMap.parse(Iterable<Uri> libraries, String packageRoot) {
46 var exports = <String, List<Export>>{};
47
48 void traverse(String path) {
49 if (exports.containsKey(path)) return;
50
51 var importsAndExports;
52 try {
53 importsAndExports = _importsAndExportsForFile(path, packageRoot);
54 } on FileIOException catch (_) {
55 // Ignore unreadable/nonexistent files.
56 return;
57 }
58
59 var exportsForLibrary = <String, Export>{};
60 for (var export in importsAndExports.last) {
61 addOrMergeExport(exportsForLibrary, export.path, export);
62 }
63 exports[path] = new List.from(exportsForLibrary.values);
64 exports[path].map((directive) => directive.path).forEach(traverse);
65 importsAndExports.first.forEach(traverse);
66 }
67
68 for (var library in libraries) {
69 var path = importUriToPath(library, packageRoot: packageRoot);
70 if (path != null) traverse(path);
71 }
72
73 return new ExportMap._(exports);
74 }
75
76 ExportMap._(this.exports);
77
78 /// Returns a list of all the paths of exported libraries that [this] is aware
79 /// of.
80 List<String> get allExportedFiles => exports.values.expand((e) => e)
81 .map((directive) => directive.path).toList();
82
83 /// Returns a list of all exports that [library] transitively exports. This
84 /// means that if [library] exports another library that in turn exports a
85 /// third, the third library will be included in the returned list.
86 ///
87 /// This will automatically handle nested `hide` and `show` directives on the
88 /// exports, as well as merging multiple exports of the same library.
89 List<Export> transitiveExports(String library) {
90 Map<String, Export> _getTransitiveExportsByPath(String path) {
91 if (_transitiveExportsByPath.containsKey(path)) {
92 return _transitiveExportsByPath[path];
93 }
94
95 var exportsByPath = <String, Export>{};
96 _transitiveExportsByPath[path] = exportsByPath;
97 if (exports[path] == null) return exportsByPath;
98
99 for (var export in exports[path]) {
100 exportsByPath[export.path] = export;
101 }
102
103 for (var export in exports[path]) {
104 for (var subExport in _getTransitiveExportsByPath(export.path).values) {
105 subExport = export.compose(subExport);
106 if (exportsByPath.containsKey(subExport.path)) {
107 subExport = subExport.merge(exportsByPath[subExport.path]);
108 }
109 exportsByPath[subExport.path] = subExport;
110 }
111 }
112 return exportsByPath;
113 }
114
115 var path = pathos.normalize(pathos.absolute(library));
116 return _getTransitiveExportsByPath(path).values.toList();
117 }
118 }
119
120 /// A class that represents one library exporting another. 19 /// A class that represents one library exporting another.
121 class Export { 20 class Export {
122 /// The absolute path of the library that contains this export. 21 /// The library that contains this export.
123 final String exporter; 22 final LibraryMirror exporter;
124 23
125 /// The absolute path of the library being exported. 24 /// The library being exported.
126 final String path; 25 final LibraryMirror exported;
127 26
128 /// The set of identifiers that are explicitly being exported. If this is 27 /// The set of identifiers that are explicitly being exported. If this is
129 /// non-empty, no identifiers other than these will be visible. 28 /// non-empty, no identifiers other than these will be visible.
130 /// 29 ///
131 /// One or both of [show] and [hide] will always be empty. 30 /// One or both of [show] and [hide] will always be empty.
132 Set<String> get show => _show; 31 Set<String> get show => _show;
133 Set<String> _show; 32 Set<String> _show;
134 33
135 /// The set of identifiers that are not exported. 34 /// The set of identifiers that are not exported.
136 /// 35 ///
137 /// One or both of [show] and [hide] will always be empty. 36 /// One or both of [show] and [hide] will always be empty.
138 Set<String> get hide => _hide; 37 Set<String> get hide => _hide;
139 Set<String> _hide; 38 Set<String> _hide;
140 39
141 /// Whether or not members exported are hidden by default. 40 /// Whether or not members exported are hidden by default.
142 bool get _hideByDefault => !show.isEmpty; 41 bool get _hideByDefault => !show.isEmpty;
143 42
144 /// Creates a new export. 43 /// Creates a new export.
145 /// 44 ///
146 /// This will normalize [show] and [hide] so that if both are non-empty, only 45 /// This will normalize [show] and [hide] so that if both are non-empty, only
147 /// [show] will be set. 46 /// [show] will be set.
148 Export(this.exporter, this.path, {Iterable<String> show, 47 factory Export(LibraryDependencyMirror mirror) {
149 Iterable<String> hide}) { 48 List<String> show = <String>[];
49 List<String> hide = <String>[];
50 for (CombinatorMirror combinator in mirror.combinators) {
51 if (combinator.isShow) {
52 show.addAll(combinator.identifiers);
53 }
54 if (combinator.isHide) {
55 hide.addAll(combinator.identifiers);
56 }
57 }
58 return new Export._internal(
59 mirror.sourceLibrary, mirror.targetLibrary, show: show, hide: hide);
60 }
61
62 Export._internal(this.exporter, this.exported,
63 {Iterable<String> show, Iterable<String> hide}) {
150 _show = new Set<String>.from(show == null ? [] : show); 64 _show = new Set<String>.from(show == null ? [] : show);
151 _hide = new Set<String>.from(hide == null ? [] : hide); 65 _hide = new Set<String>.from(hide == null ? [] : hide);
152 66
153 if (!_show.isEmpty) { 67 if (!_show.isEmpty) {
154 _show.removeAll(_hide); 68 _show.removeAll(_hide);
155 _hide = new Set<String>(); 69 _hide = new Set<String>();
156 } 70 }
157 } 71 }
158 72
159 /// Returns a new [Export] that represents [this] composed with [nested], as 73 /// Returns a new [Export] that represents [this] composed with [nested], as
(...skipping 10 matching lines...) Expand all
170 show.removeAll(nested.hide); 84 show.removeAll(nested.hide);
171 } 85 }
172 } else if (nested._hideByDefault) { 86 } else if (nested._hideByDefault) {
173 show.addAll(nested.show); 87 show.addAll(nested.show);
174 show.removeAll(this.hide); 88 show.removeAll(this.hide);
175 } else { 89 } else {
176 hide.addAll(this.hide); 90 hide.addAll(this.hide);
177 hide.addAll(nested.hide); 91 hide.addAll(nested.hide);
178 } 92 }
179 93
180 return new Export(this.exporter, nested.path, show: show, hide: hide); 94 return new Export._internal(
95 this.exporter, nested.exported, show: show, hide: hide);
181 } 96 }
182 97
183 /// Returns a new [Export] that merges [this] with [nested], as though both 98 /// Returns a new [Export] that merges [this] with [nested], as though both
184 /// exports were included in the same library. 99 /// exports were included in the same library.
185 /// 100 ///
186 /// [this] and [other] must have the same values for [exporter] and [path]. 101 /// [this] and [other] must have the same values for [exporter] and [path].
187 Export merge(Export other) { 102 Export merge(Export other) {
188 if (this.path != other.path) { 103 if (this.exported != other.exported) {
189 throw new ArgumentError("Can't merge two Exports with different paths: " 104 throw new ArgumentError("Can't merge two Exports with different paths: "
190 "export '$path' from '$exporter' and export '${other.path}' from " 105 "export '$exported' from '$exporter' and export '${other.exported}' fr om "
191 "'${other.exporter}'."); 106 "'${other.exporter}'.");
192 } if (this.exporter != other.exporter) { 107 } if (this.exporter != other.exporter) {
193 throw new ArgumentError("Can't merge two Exports with different " 108 throw new ArgumentError("Can't merge two Exports with different "
194 "exporters: export '$path' from '$exporter' and export " 109 "exporters: export '$exported' from '$exporter' and export "
195 "'${other.path}' from '${other.exporter}'."); 110 "'${other.exported}' from '${other.exporter}'.");
196 } 111 }
197 112
198 var show = new Set<String>(); 113 var show = new Set<String>();
199 var hide = new Set<String>(); 114 var hide = new Set<String>();
200 115
201 if (this._hideByDefault) { 116 if (this._hideByDefault) {
202 if (other._hideByDefault) { 117 if (other._hideByDefault) {
203 show.addAll(this.show); 118 show.addAll(this.show);
204 show.addAll(other.show); 119 show.addAll(other.show);
205 } else { 120 } else {
206 hide.addAll(other.hide); 121 hide.addAll(other.hide);
207 hide.removeAll(this.show); 122 hide.removeAll(this.show);
208 } 123 }
209 } else { 124 } else {
210 hide.addAll(this.hide); 125 hide.addAll(this.hide);
211 if (other._hideByDefault) { 126 if (other._hideByDefault) {
212 hide.removeAll(other.show); 127 hide.removeAll(other.show);
213 } else { 128 } else {
214 hide.retainAll(other.hide); 129 hide.retainAll(other.hide);
215 } 130 }
216 } 131 }
217 132
218 return new Export(exporter, path, show: show, hide: hide); 133 return new Export._internal(exporter, exported, show: show, hide: hide);
219 } 134 }
220 135
221 /// Returns whether or not a member named [name] is visible through this 136 /// Returns whether or not a member named [name] is visible through this
222 /// import, as goverend by [show] and [hide]. 137 /// import, as goverend by [show] and [hide].
223 bool isMemberVisible(String name) => 138 bool isMemberVisible(String name) =>
224 _hideByDefault ? show.contains(name) : !hide.contains(name); 139 _hideByDefault ? show.contains(name) : !hide.contains(name);
225 140
226 bool operator==(other) => other is Export && other.exporter == exporter && 141 bool operator==(other) => other is Export && other.exporter == exporter &&
227 other.path == path && show.containsAll(other.show) && 142 other.exported == exported && show.containsAll(other.show) &&
228 other.show.containsAll(show) && hide.containsAll(other.hide) && 143 other.show.containsAll(show) && hide.containsAll(other.hide) &&
229 other.hide.containsAll(hide); 144 other.hide.containsAll(hide);
230 145
231 int get hashCode { 146 int get hashCode {
232 var hashCode = exporter.hashCode ^ path.hashCode; 147 var hashCode = exporter.hashCode ^ exported.hashCode;
233 hashCode = show.reduce(hashCode, (hash, name) => hash ^ name.hashCode); 148 hashCode = show.reduce(hashCode, (hash, name) => hash ^ name.hashCode);
234 return hide.reduce(hashCode, (hash, name) => hash ^ name.hashCode); 149 return hide.reduce(hashCode, (hash, name) => hash ^ name.hashCode);
235 } 150 }
236 151
237 String toString() { 152 String toString() {
238 var combinator = ''; 153 var combinator = '';
239 if (!show.isEmpty) { 154 if (!show.isEmpty) {
240 combinator = ' show ${show.join(', ')}'; 155 combinator = ' show ${show.join(', ')}';
241 } else if (!hide.isEmpty) { 156 } else if (!hide.isEmpty) {
242 combinator = ' hide ${hide.join(', ')}'; 157 combinator = ' hide ${hide.join(', ')}';
243 } 158 }
244 return "export '$path'$combinator (from $exporter)"; 159 return "export '${exported.displayName}'"
160 "$combinator (from ${exporter.displayName})";
245 } 161 }
246 } 162 }
247 163
248 /// Returns a list of imports and a list of exports for the dart library at
249 /// [file]. [packageRoot] is used to resolve `package:` URLs.
250 ///
251 /// The imports are a list of absolute paths, while the exports are [Export]
252 /// objects.
253 Pair<List<String>, List<Export>> _importsAndExportsForFile(String file,
254 String packageRoot) {
255 var collector = new _ImportExportCollector();
256 _parseFile(file).accept(collector);
257
258 var imports = collector.imports.map((import) {
259 return _pathForDirective(import, pathos.dirname(file), packageRoot);
260 }).where((import) => import != null).toList();
261
262 var exports = collector.exports.map((export) {
263 var path = _pathForDirective(export, pathos.dirname(file), packageRoot);
264 if (path == null) return null;
265
266 path = pathos.normalize(pathos.absolute(path));
267 var show = export.combinators
268 .where((combinator) => combinator is ShowCombinator)
269 .expand((combinator) => combinator.shownNames.map((name) => name.name));
270 var hide = export.combinators
271 .where((combinator) => combinator is HideCombinator)
272 .expand((combinator) =>
273 combinator.hiddenNames.map((name) => name.name));
274
275 return new Export(file, path, show: show, hide: hide);
276 }).where((export) => export != null).toList();
277
278 return new Pair<List<String>, List<Export>>(imports, exports);
279 }
280
281 /// Returns the absolute path to the library imported by [directive], or `null`
282 /// if it doesn't refer to a file on the local filesystem.
283 ///
284 /// [basePath] is the path from which relative imports should be resolved.
285 /// [packageRoot] is the path from which `package:` imports should be resolved.
286 String _pathForDirective(NamespaceDirective directive, String basePath,
287 String packageRoot) {
288 var uri = Uri.parse(_stringLiteralToString(directive.uri));
289 var path = importUriToPath(uri, basePath: basePath, packageRoot: packageRoot);
290 if (path == null) return null;
291 return pathos.normalize(pathos.absolute(path));
292 }
293
294 /// Parses a Dart file into an AST.
295 CompilationUnit _parseFile(String path) {
296 var contents = new File(path).readAsStringSync();
297 var errorCollector = new _ErrorCollector();
298 var scanner = new StringScanner(null, contents, errorCollector);
299 var token = scanner.tokenize();
300 var parser = new Parser(null, errorCollector);
301 var unit = parser.parseCompilationUnit(token);
302 unit.lineInfo = new LineInfo(scanner.lineStarts);
303
304 if (!errorCollector.errors.isEmpty) {
305 throw new FormatException(
306 errorCollector.errors.map((e) => e.toString()).join("\n"));
307 }
308
309 return unit;
310 }
311
312 /// A simple error listener that collects errors into a list.
313 class _ErrorCollector extends AnalysisErrorListener {
314 final errors = <AnalysisError>[];
315
316 _ErrorCollector();
317
318 void onError(AnalysisError error) => errors.add(error);
319 }
320
321 /// A simple visitor that collects import and export nodes.
322 class _ImportExportCollector extends GeneralizingASTVisitor {
323 final imports = <ImportDirective>[];
324 final exports = <ExportDirective>[];
325
326 _ImportExportCollector();
327
328 visitImportDirective(ImportDirective node) => imports.add(node);
329 visitExportDirective(ExportDirective node) => exports.add(node);
330 }
331
332 // TODO(nweiz): fold this into the analyzer (issue 9781).
333 /// Converts an AST node representing a string literal into a [String].
334 String _stringLiteralToString(StringLiteral literal) {
335 if (literal is AdjacentStrings) {
336 return literal.strings.map(_stringLiteralToString).join();
337 } else if (literal is SimpleStringLiteral) {
338 return literal.value;
339 } else {
340 throw new ArgumentError('Unknown string type for $literal');
341 }
342 }
OLDNEW
« no previous file with comments | « sdk/lib/_internal/dartdoc/lib/src/dartdoc/utils.dart ('k') | sdk/lib/_internal/dartdoc/pubspec.yaml » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698