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

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

Issue 14088002: Attempt to re-commit Dartdoc exports. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 8 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
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
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
7 /// display exported members as part of the exporting library, since dart2js
8 /// doesn't provide this information itself.
9 library export_map;
10
11 import 'dart:io';
12 import 'dart:uri';
13
14 import 'package:analyzer_experimental/src/generated/ast.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;
20
21 import 'dartdoc/utils.dart';
22
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.
121 class Export {
122 /// The absolute path of the library that contains this export.
123 final String exporter;
124
125 /// The absolute path of the library being exported.
126 final String path;
127
128 /// The set of identifiers that are explicitly being exported. If this is
129 /// non-empty, no identifiers other than these will be visible.
130 ///
131 /// One or both of [show] and [hide] will always be empty.
132 Set<String> get show => _show;
133 Set<String> _show;
134
135 /// The set of identifiers that are not exported.
136 ///
137 /// One or both of [show] and [hide] will always be empty.
138 Set<String> get hide => _hide;
139 Set<String> _hide;
140
141 /// Whether or not members exported are hidden by default.
142 bool get _hideByDefault => !show.isEmpty;
143
144 /// Creates a new export.
145 ///
146 /// This will normalize [show] and [hide] so that if both are non-empty, only
147 /// [show] will be set.
148 Export(this.exporter, this.path, {Iterable<String> show,
149 Iterable<String> hide}) {
150 _show = new Set<String>.from(show == null ? [] : show);
151 _hide = new Set<String>.from(hide == null ? [] : hide);
152
153 if (!_show.isEmpty) {
154 _show.removeAll(_hide);
155 _hide = new Set<String>();
156 }
157 }
158
159 /// Returns a new [Export] that represents [this] composed with [nested], as
160 /// though [this] was used to export a library that in turn exported [nested].
161 Export compose(Export nested) {
162 var show = new Set<String>();
163 var hide = new Set<String>();
164
165 if (this._hideByDefault) {
166 show.addAll(this.show);
167 if (nested._hideByDefault) {
168 show.retainAll(nested.show);
169 } else {
170 show.removeAll(nested.hide);
171 }
172 } else if (nested._hideByDefault) {
173 show.addAll(nested.show);
174 show.removeAll(this.hide);
175 } else {
176 hide.addAll(this.hide);
177 hide.addAll(nested.hide);
178 }
179
180 return new Export(this.exporter, nested.path, show: show, hide: hide);
181 }
182
183 /// Returns a new [Export] that merges [this] with [nested], as though both
184 /// exports were included in the same library.
185 ///
186 /// [this] and [other] must have the same values for [exporter] and [path].
187 Export merge(Export other) {
188 if (this.path != other.path) {
189 throw new ArgumentError("Can't merge two Exports with different paths: "
190 "export '$path' from '$exporter' and export '${other.path}' from "
191 "'${other.exporter}'.");
192 } if (this.exporter != other.exporter) {
193 throw new ArgumentError("Can't merge two Exports with different "
194 "exporters: export '$path' from '$exporter' and export "
195 "'${other.path}' from '${other.exporter}'.");
196 }
197
198 var show = new Set<String>();
199 var hide = new Set<String>();
200
201 if (this._hideByDefault) {
202 if (other._hideByDefault) {
203 show.addAll(this.show);
204 show.addAll(other.show);
205 } else {
206 hide.addAll(other.hide);
207 hide.removeAll(this.show);
208 }
209 } else {
210 hide.addAll(this.hide);
211 if (other._hideByDefault) {
212 hide.removeAll(other.show);
213 } else {
214 hide.retainAll(other.hide);
215 }
216 }
217
218 return new Export(exporter, path, show: show, hide: hide);
219 }
220
221 /// Returns whether or not a member named [name] is visible through this
222 /// import, as goverend by [show] and [hide].
223 bool isMemberVisible(String name) =>
224 _hideByDefault ? show.contains(name) : !hide.contains(name);
225
226 bool operator==(other) => other is Export && other.exporter == exporter &&
227 other.path == path && show.containsAll(other.show) &&
228 other.show.containsAll(show) && hide.containsAll(other.hide) &&
229 other.hide.containsAll(hide);
230
231 int get hashCode {
232 var hashCode = exporter.hashCode ^ path.hashCode;
233 hashCode = show.reduce(hashCode, (hash, name) => hash ^ name.hashCode);
234 return hide.reduce(hashCode, (hash, name) => hash ^ name.hashCode);
235 }
236
237 String toString() {
238 var combinator = '';
239 if (!show.isEmpty) {
240 combinator = ' show ${show.join(', ')}';
241 } else if (!hide.isEmpty) {
242 combinator = ' hide ${hide.join(', ')}';
243 }
244 return "export '$path'$combinator (from $exporter)";
245 }
246 }
247
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