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

Side by Side Diff: pkg/analysis_server/lib/src/single_context_manager.dart

Issue 2945313002: Remove the unused class SingleContextManager (Closed)
Patch Set: Created 3 years, 6 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
(Empty)
1 // Copyright (c) 2016, 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 import 'dart:async';
6 import 'dart:core';
7 import 'dart:math' as math;
8
9 import 'package:analysis_server/src/context_manager.dart';
10 import 'package:analyzer/file_system/file_system.dart';
11 import 'package:analyzer/plugin/resolver_provider.dart';
12 import 'package:analyzer/src/dart/analysis/driver.dart';
13 import 'package:analyzer/src/generated/engine.dart';
14 import 'package:analyzer/src/generated/sdk.dart';
15 import 'package:analyzer/src/generated/source.dart';
16 import 'package:analyzer/src/util/glob.dart';
17 import 'package:path/path.dart' as path;
18 import 'package:watcher/watcher.dart';
19
20 /**
21 * Implementation of [ContextManager] that supports only one [AnalysisContext].
22 * So, sources from all analysis roots are added to this single context. All
23 * features that could otherwise cause creating additional contexts, such as
24 * presence of `pubspec.yaml` or `.packages` files, or analysis options files
25 * are ignored.
26 */
27 class SingleContextManager implements ContextManager {
28 /**
29 * The [ResourceProvider] using which paths are converted into [Resource]s.
30 */
31 final ResourceProvider resourceProvider;
32
33 /**
34 * The context used to work with file system paths.
35 */
36 path.Context pathContext;
37
38 /**
39 * The manager used to access the SDK that should be associated with a
40 * particular context.
41 */
42 final DartSdkManager sdkManager;
43
44 /**
45 * A function that will return a [UriResolver] that can be used to resolve
46 * `package:` URIs.
47 */
48 final ResolverProvider packageResolverProvider;
49
50 /**
51 * A list of the globs used to determine which files should be analyzed.
52 */
53 final List<Glob> analyzedFilesGlobs;
54
55 /**
56 * The default options used to create new analysis contexts.
57 */
58 final AnalysisOptionsImpl defaultContextOptions;
59
60 /**
61 * The list of included paths (folders and files) most recently passed to
62 * [setRoots].
63 */
64 List<String> includedPaths = <String>[];
65
66 /**
67 * The list of excluded paths (folders and files) most recently passed to
68 * [setRoots].
69 */
70 List<String> excludedPaths = <String>[];
71
72 /**
73 * The map of package roots most recently passed to [setRoots].
74 */
75 Map<String, String> packageRoots = <String, String>{};
76
77 /**
78 * Same as [packageRoots], except that source folders have been normalized
79 * and non-folders have been removed.
80 */
81 Map<String, String> normalizedPackageRoots = <String, String>{};
82
83 @override
84 ContextManagerCallbacks callbacks;
85
86 /**
87 * The analysis driver which analyses everything.
88 */
89 AnalysisDriver analysisDriver;
90
91 /**
92 * The context in which everything is being analyzed.
93 */
94 AnalysisContext context;
95
96 /**
97 * The folder associated with the context.
98 */
99 Folder contextFolder;
100
101 /**
102 * The current watch subscriptions.
103 */
104 Map<String, StreamSubscription<WatchEvent>> watchSubscriptions =
105 new Map<String, StreamSubscription<WatchEvent>>();
106
107 /**
108 * The [packageResolverProvider] must not be `null`.
109 */
110 SingleContextManager(
111 this.resourceProvider,
112 this.sdkManager,
113 this.packageResolverProvider,
114 this.analyzedFilesGlobs,
115 this.defaultContextOptions) {
116 pathContext = resourceProvider.pathContext;
117 }
118
119 @override
120 Iterable<AnalysisContext> get analysisContexts =>
121 context == null ? <AnalysisContext>[] : <AnalysisContext>[context];
122
123 @override
124 Map<Folder, AnalysisDriver> get driverMap => {contextFolder: analysisDriver};
125
126 @override
127 Map<Folder, AnalysisContext> get folderMap => {contextFolder: context};
128
129 @override
130 List<AnalysisContext> contextsInAnalysisRoot(Folder analysisRoot) {
131 if (context == null || !includedPaths.contains(analysisRoot.path)) {
132 return <AnalysisContext>[];
133 }
134 return <AnalysisContext>[context];
135 }
136
137 @override
138 Folder getContextFolderFor(String path) {
139 if (isInAnalysisRoot(path)) {
140 return contextFolder;
141 }
142 return null;
143 }
144
145 @override
146 AnalysisContext getContextFor(String path) {
147 if (context == null) {
148 return null;
149 } else if (_isContainedIn(includedPaths, path)) {
150 return context;
151 }
152 return null;
153 }
154
155 @override
156 AnalysisDriver getDriverFor(String path) {
157 throw new UnimplementedError(
158 'Unexpected invocation of getDriverFor in SingleContextManager');
159 }
160
161 @override
162 List<AnalysisDriver> getDriversInAnalysisRoot(Folder analysisRoot) {
163 throw new UnimplementedError(
164 'Unexpected invocation of getDriversInAnalysisRoot in SingleContextManag er');
165 }
166
167 @override
168 bool isIgnored(String path) {
169 return !_isContainedIn(includedPaths, path) || _isExcludedPath(path);
170 }
171
172 @override
173 bool isInAnalysisRoot(String path) {
174 return _isContainedIn(includedPaths, path) &&
175 !_isContainedIn(excludedPaths, path);
176 }
177
178 @override
179 void refresh(List<Resource> roots) {
180 if (context != null) {
181 callbacks.removeContext(contextFolder, null);
182 context.dispose();
183 context = null;
184 contextFolder = null;
185 _cancelCurrentWatchSubscriptions();
186 setRoots(includedPaths, excludedPaths, packageRoots);
187 }
188 }
189
190 @override
191 void setRoots(List<String> includedPaths, List<String> excludedPaths,
192 Map<String, String> packageRoots) {
193 includedPaths = _nonOverlappingPaths(includedPaths);
194 excludedPaths = _nonOverlappingPaths(excludedPaths);
195 this.packageRoots = packageRoots;
196 _updateNormalizedPackageRoots();
197 // Update context path.
198 {
199 String contextPath = _commonPrefix(includedPaths);
200 Folder contextFolder = resourceProvider.getFolder(contextPath);
201 if (contextFolder != this.contextFolder) {
202 if (context != null) {
203 callbacks.moveContext(this.contextFolder, contextFolder);
204 }
205 this.contextFolder = contextFolder;
206 }
207 }
208 // Start new watchers and cancel old ones.
209 {
210 Map<String, StreamSubscription<WatchEvent>> newSubscriptions =
211 new Map<String, StreamSubscription<WatchEvent>>();
212 for (String includedPath in includedPaths) {
213 Resource resource = resourceProvider.getResource(includedPath);
214 if (resource is Folder) {
215 // Extract the existing subscription or create a new one.
216 StreamSubscription<WatchEvent> subscription =
217 watchSubscriptions.remove(includedPath);
218 if (subscription == null) {
219 subscription = resource.changes.listen(_handleWatchEvent);
220 }
221 // Remember the subscription.
222 newSubscriptions[includedPath] = subscription;
223 }
224 _cancelCurrentWatchSubscriptions();
225 this.watchSubscriptions = newSubscriptions;
226 }
227 }
228 // Create or update the analysis context.
229 if (context == null) {
230 context = callbacks.addContext(contextFolder, defaultContextOptions);
231 ChangeSet changeSet =
232 _buildChangeSet(added: _includedFiles(includedPaths, excludedPaths));
233 callbacks.applyChangesToContext(contextFolder, changeSet);
234 } else {
235 // TODO(brianwilkerson) Optimize this.
236 List<File> oldFiles =
237 _includedFiles(this.includedPaths, this.excludedPaths);
238 List<File> newFiles = _includedFiles(includedPaths, excludedPaths);
239 ChangeSet changeSet = _buildChangeSet(
240 added: _diff(newFiles, oldFiles), removed: _diff(oldFiles, newFiles));
241 callbacks.applyChangesToContext(contextFolder, changeSet);
242 }
243 this.includedPaths = includedPaths;
244 this.excludedPaths = excludedPaths;
245 }
246
247 /**
248 * Recursively add the given [resource] (if it's a file) or its children (if
249 * it's a folder) to the [addedFiles].
250 */
251 void _addFilesInResource(
252 List<File> addedFiles, Resource resource, List<String> excludedPaths) {
253 if (_isImplicitlyExcludedResource(resource)) {
254 return;
255 }
256 String path = resource.path;
257 if (_isEqualOrWithinAny(excludedPaths, path)) {
258 return;
259 }
260 if (resource is File) {
261 if (_matchesAnyAnalyzedFilesGlob(path) && resource.exists) {
262 addedFiles.add(resource);
263 }
264 } else if (resource is Folder) {
265 for (Resource child in _getChildrenSafe(resource)) {
266 _addFilesInResource(addedFiles, child, excludedPaths);
267 }
268 }
269 }
270
271 ChangeSet _buildChangeSet({List<File> added, List<File> removed}) {
272 ChangeSet changeSet = new ChangeSet();
273 if (added != null) {
274 for (File file in added) {
275 Source source = createSourceInContext(context, file);
276 changeSet.addedSource(source);
277 }
278 }
279 if (removed != null) {
280 for (File file in removed) {
281 Source source = createSourceInContext(context, file);
282 changeSet.removedSource(source);
283 }
284 }
285 return changeSet;
286 }
287
288 void _cancelCurrentWatchSubscriptions() {
289 for (StreamSubscription<WatchEvent> subscription
290 in watchSubscriptions.values) {
291 subscription.cancel();
292 }
293 watchSubscriptions.clear();
294 }
295
296 String _commonPrefix(List<String> paths) {
297 if (paths.isEmpty) {
298 return '';
299 }
300 List<String> left = pathContext.split(paths[0]);
301 int count = left.length;
302 for (int i = 1; i < paths.length; i++) {
303 List<String> right = pathContext.split(paths[i]);
304 count = _commonComponents(left, count, right);
305 }
306 return pathContext.joinAll(left.sublist(0, count));
307 }
308
309 List<Resource> _existingResources(List<String> pathList) {
310 List<Resource> resources = <Resource>[];
311 for (String path in pathList) {
312 Resource resource = resourceProvider.getResource(path);
313 if (resource is Folder) {
314 resources.add(resource);
315 } else if (!resource.exists) {
316 // Non-existent resources are ignored. TODO(paulberry): we should set
317 // up a watcher to ensure that if the resource appears later, we will
318 // begin analyzing it.
319 } else if (resource is File) {
320 resources.add(resource);
321 } else {
322 throw new UnimplementedError('$path is not a folder. '
323 'Only support for file and folder analysis is implemented.');
324 }
325 }
326 return resources;
327 }
328
329 void _handleWatchEvent(WatchEvent event) {
330 String path = event.path;
331 // Ignore if excluded.
332 if (_isExcludedPath(path)) {
333 return;
334 }
335 // Ignore if not in a root.
336 if (!_isContainedIn(includedPaths, path)) {
337 return;
338 }
339 // Handle the change.
340 switch (event.type) {
341 case ChangeType.ADD:
342 Resource resource = resourceProvider.getResource(path);
343 if (resource is File) {
344 if (_matchesAnyAnalyzedFilesGlob(path)) {
345 callbacks.applyChangesToContext(
346 contextFolder, _buildChangeSet(added: <File>[resource]));
347 }
348 }
349 break;
350 case ChangeType.REMOVE:
351 List<Source> sources = context.getSourcesWithFullName(path);
352 if (!sources.isEmpty) {
353 ChangeSet changeSet = new ChangeSet();
354 sources.forEach(changeSet.removedSource);
355 callbacks.applyChangesToContext(contextFolder, changeSet);
356 }
357 break;
358 case ChangeType.MODIFY:
359 List<Source> sources = context.getSourcesWithFullName(path);
360 if (!sources.isEmpty) {
361 ChangeSet changeSet = new ChangeSet();
362 sources.forEach(changeSet.changedSource);
363 callbacks.applyChangesToContext(contextFolder, changeSet);
364 }
365 break;
366 }
367 }
368
369 List<File> _includedFiles(
370 List<String> includedPaths, List<String> excludedPaths) {
371 List<Resource> includedResources = _existingResources(includedPaths);
372 List<File> includedFiles = <File>[];
373 for (Resource resource in includedResources) {
374 _addFilesInResource(includedFiles, resource, excludedPaths);
375 }
376 return includedFiles;
377 }
378
379 bool _isContainedIn(List<String> pathList, String path) {
380 for (String pathInList in pathList) {
381 if (_isEqualOrWithin(pathInList, path)) {
382 return true;
383 }
384 }
385 return false;
386 }
387
388 bool _isEqualOrWithin(String parent, String child) {
389 return child == parent || pathContext.isWithin(parent, child);
390 }
391
392 bool _isEqualOrWithinAny(List<String> parents, String child) {
393 for (String parent in parents) {
394 if (_isEqualOrWithin(parent, child)) {
395 return true;
396 }
397 }
398 return false;
399 }
400
401 /**
402 * Return `true` if the given [path] should be excluded, using explicit
403 * or implicit rules.
404 */
405 bool _isExcludedPath(String path) {
406 List<String> parts = resourceProvider.pathContext.split(path);
407 // Implicit rules.
408 for (String part in parts) {
409 if (part.startsWith('.')) {
410 return true;
411 }
412 }
413 // Explicitly excluded paths.
414 if (_isEqualOrWithinAny(excludedPaths, path)) {
415 return true;
416 }
417 // OK
418 return false;
419 }
420
421 /**
422 * Return `true` if the given [resource] and children should be excluded
423 * because of some implicit exclusion rules, e.g. `.name`.
424 */
425 bool _isImplicitlyExcludedResource(Resource resource) {
426 String shortName = resource.shortName;
427 if (shortName.startsWith('.')) {
428 return true;
429 }
430 return false;
431 }
432
433 /**
434 * Return `true` if the given [path] matches one of the [analyzedFilesGlobs].
435 */
436 bool _matchesAnyAnalyzedFilesGlob(String path) {
437 for (Glob glob in analyzedFilesGlobs) {
438 if (glob.matches(path)) {
439 return true;
440 }
441 }
442 return false;
443 }
444
445 /**
446 * Return a list consisting of the elements from [pathList] that describe the
447 * minimal set of directories that include everything in the original list of
448 * paths and nothing more. In particular:
449 *
450 * * if a path is in the input list multiple times it will appear at most
451 * once in the output list, and
452 * * if a directory D and a subdirectory of it are both in the input list
453 * then only the directory D will be in the output list.
454 *
455 * The original list is not modified.
456 */
457 List<String> _nonOverlappingPaths(List<String> pathList) {
458 List<String> sortedPaths = new List<String>.from(pathList);
459 sortedPaths.sort((a, b) => a.length - b.length);
460 int pathCount = sortedPaths.length;
461 for (int i = pathCount - 1; i > 0; i--) {
462 String path = sortedPaths[i];
463 for (int j = 0; j < i; j++) {
464 if (_isEqualOrWithin(path, sortedPaths[j])) {
465 sortedPaths.removeAt(i);
466 break;
467 }
468 }
469 }
470 return sortedPaths;
471 }
472
473 /**
474 * Normalize all package root sources by mapping them to folders on the
475 * filesystem. Ignore any package root sources that aren't folders.
476 */
477 void _updateNormalizedPackageRoots() {
478 normalizedPackageRoots = <String, String>{};
479 packageRoots.forEach((String sourcePath, String targetPath) {
480 Resource resource = resourceProvider.getResource(sourcePath);
481 if (resource is Folder) {
482 normalizedPackageRoots[resource.path] = targetPath;
483 }
484 });
485 }
486
487 /**
488 * Create and return a source representing the given [file] within the given
489 * [context].
490 */
491 static Source createSourceInContext(AnalysisContext context, File file) {
492 // TODO(brianwilkerson) Optimize this, by allowing support for source
493 // factories to restore URI's from a file path rather than a source.
494 Source source = file.createSource();
495 if (context == null) {
496 return source;
497 }
498 Uri uri = context.sourceFactory.restoreUri(source);
499 return file.createSource(uri);
500 }
501
502 static int _commonComponents(
503 List<String> left, int count, List<String> right) {
504 int max = math.min(count, right.length);
505 for (int i = 0; i < max; i++) {
506 if (left[i] != right[i]) {
507 return i;
508 }
509 }
510 return max;
511 }
512
513 /**
514 * Return a list of all the files in the [left] that are not in the [right].
515 */
516 static List<File> _diff(List<File> left, List<File> right) {
517 List<File> diff = new List.from(left);
518 for (File file in right) {
519 diff.remove(file);
520 }
521 return diff;
522 }
523
524 static List<Resource> _getChildrenSafe(Folder folder) {
525 try {
526 return folder.getChildren();
527 } on FileSystemException {
528 // The folder either doesn't exist or cannot be read.
529 // Either way, there are no children.
530 return const <Resource>[];
531 }
532 }
533 }
OLDNEW
« no previous file with comments | « pkg/analysis_server/lib/src/server/driver.dart ('k') | pkg/analysis_server/lib/src/socket_server.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698