Index: pkg/analysis_server/lib/src/single_context_manager.dart |
diff --git a/pkg/analysis_server/lib/src/single_context_manager.dart b/pkg/analysis_server/lib/src/single_context_manager.dart |
deleted file mode 100644 |
index b0bf0334a741711492b30e5ed8bcaea8039e3a6a..0000000000000000000000000000000000000000 |
--- a/pkg/analysis_server/lib/src/single_context_manager.dart |
+++ /dev/null |
@@ -1,533 +0,0 @@ |
-// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
-// for details. All rights reserved. Use of this source code is governed by a |
-// BSD-style license that can be found in the LICENSE file. |
- |
-import 'dart:async'; |
-import 'dart:core'; |
-import 'dart:math' as math; |
- |
-import 'package:analysis_server/src/context_manager.dart'; |
-import 'package:analyzer/file_system/file_system.dart'; |
-import 'package:analyzer/plugin/resolver_provider.dart'; |
-import 'package:analyzer/src/dart/analysis/driver.dart'; |
-import 'package:analyzer/src/generated/engine.dart'; |
-import 'package:analyzer/src/generated/sdk.dart'; |
-import 'package:analyzer/src/generated/source.dart'; |
-import 'package:analyzer/src/util/glob.dart'; |
-import 'package:path/path.dart' as path; |
-import 'package:watcher/watcher.dart'; |
- |
-/** |
- * Implementation of [ContextManager] that supports only one [AnalysisContext]. |
- * So, sources from all analysis roots are added to this single context. All |
- * features that could otherwise cause creating additional contexts, such as |
- * presence of `pubspec.yaml` or `.packages` files, or analysis options files |
- * are ignored. |
- */ |
-class SingleContextManager implements ContextManager { |
- /** |
- * The [ResourceProvider] using which paths are converted into [Resource]s. |
- */ |
- final ResourceProvider resourceProvider; |
- |
- /** |
- * The context used to work with file system paths. |
- */ |
- path.Context pathContext; |
- |
- /** |
- * The manager used to access the SDK that should be associated with a |
- * particular context. |
- */ |
- final DartSdkManager sdkManager; |
- |
- /** |
- * A function that will return a [UriResolver] that can be used to resolve |
- * `package:` URIs. |
- */ |
- final ResolverProvider packageResolverProvider; |
- |
- /** |
- * A list of the globs used to determine which files should be analyzed. |
- */ |
- final List<Glob> analyzedFilesGlobs; |
- |
- /** |
- * The default options used to create new analysis contexts. |
- */ |
- final AnalysisOptionsImpl defaultContextOptions; |
- |
- /** |
- * The list of included paths (folders and files) most recently passed to |
- * [setRoots]. |
- */ |
- List<String> includedPaths = <String>[]; |
- |
- /** |
- * The list of excluded paths (folders and files) most recently passed to |
- * [setRoots]. |
- */ |
- List<String> excludedPaths = <String>[]; |
- |
- /** |
- * The map of package roots most recently passed to [setRoots]. |
- */ |
- Map<String, String> packageRoots = <String, String>{}; |
- |
- /** |
- * Same as [packageRoots], except that source folders have been normalized |
- * and non-folders have been removed. |
- */ |
- Map<String, String> normalizedPackageRoots = <String, String>{}; |
- |
- @override |
- ContextManagerCallbacks callbacks; |
- |
- /** |
- * The analysis driver which analyses everything. |
- */ |
- AnalysisDriver analysisDriver; |
- |
- /** |
- * The context in which everything is being analyzed. |
- */ |
- AnalysisContext context; |
- |
- /** |
- * The folder associated with the context. |
- */ |
- Folder contextFolder; |
- |
- /** |
- * The current watch subscriptions. |
- */ |
- Map<String, StreamSubscription<WatchEvent>> watchSubscriptions = |
- new Map<String, StreamSubscription<WatchEvent>>(); |
- |
- /** |
- * The [packageResolverProvider] must not be `null`. |
- */ |
- SingleContextManager( |
- this.resourceProvider, |
- this.sdkManager, |
- this.packageResolverProvider, |
- this.analyzedFilesGlobs, |
- this.defaultContextOptions) { |
- pathContext = resourceProvider.pathContext; |
- } |
- |
- @override |
- Iterable<AnalysisContext> get analysisContexts => |
- context == null ? <AnalysisContext>[] : <AnalysisContext>[context]; |
- |
- @override |
- Map<Folder, AnalysisDriver> get driverMap => {contextFolder: analysisDriver}; |
- |
- @override |
- Map<Folder, AnalysisContext> get folderMap => {contextFolder: context}; |
- |
- @override |
- List<AnalysisContext> contextsInAnalysisRoot(Folder analysisRoot) { |
- if (context == null || !includedPaths.contains(analysisRoot.path)) { |
- return <AnalysisContext>[]; |
- } |
- return <AnalysisContext>[context]; |
- } |
- |
- @override |
- Folder getContextFolderFor(String path) { |
- if (isInAnalysisRoot(path)) { |
- return contextFolder; |
- } |
- return null; |
- } |
- |
- @override |
- AnalysisContext getContextFor(String path) { |
- if (context == null) { |
- return null; |
- } else if (_isContainedIn(includedPaths, path)) { |
- return context; |
- } |
- return null; |
- } |
- |
- @override |
- AnalysisDriver getDriverFor(String path) { |
- throw new UnimplementedError( |
- 'Unexpected invocation of getDriverFor in SingleContextManager'); |
- } |
- |
- @override |
- List<AnalysisDriver> getDriversInAnalysisRoot(Folder analysisRoot) { |
- throw new UnimplementedError( |
- 'Unexpected invocation of getDriversInAnalysisRoot in SingleContextManager'); |
- } |
- |
- @override |
- bool isIgnored(String path) { |
- return !_isContainedIn(includedPaths, path) || _isExcludedPath(path); |
- } |
- |
- @override |
- bool isInAnalysisRoot(String path) { |
- return _isContainedIn(includedPaths, path) && |
- !_isContainedIn(excludedPaths, path); |
- } |
- |
- @override |
- void refresh(List<Resource> roots) { |
- if (context != null) { |
- callbacks.removeContext(contextFolder, null); |
- context.dispose(); |
- context = null; |
- contextFolder = null; |
- _cancelCurrentWatchSubscriptions(); |
- setRoots(includedPaths, excludedPaths, packageRoots); |
- } |
- } |
- |
- @override |
- void setRoots(List<String> includedPaths, List<String> excludedPaths, |
- Map<String, String> packageRoots) { |
- includedPaths = _nonOverlappingPaths(includedPaths); |
- excludedPaths = _nonOverlappingPaths(excludedPaths); |
- this.packageRoots = packageRoots; |
- _updateNormalizedPackageRoots(); |
- // Update context path. |
- { |
- String contextPath = _commonPrefix(includedPaths); |
- Folder contextFolder = resourceProvider.getFolder(contextPath); |
- if (contextFolder != this.contextFolder) { |
- if (context != null) { |
- callbacks.moveContext(this.contextFolder, contextFolder); |
- } |
- this.contextFolder = contextFolder; |
- } |
- } |
- // Start new watchers and cancel old ones. |
- { |
- Map<String, StreamSubscription<WatchEvent>> newSubscriptions = |
- new Map<String, StreamSubscription<WatchEvent>>(); |
- for (String includedPath in includedPaths) { |
- Resource resource = resourceProvider.getResource(includedPath); |
- if (resource is Folder) { |
- // Extract the existing subscription or create a new one. |
- StreamSubscription<WatchEvent> subscription = |
- watchSubscriptions.remove(includedPath); |
- if (subscription == null) { |
- subscription = resource.changes.listen(_handleWatchEvent); |
- } |
- // Remember the subscription. |
- newSubscriptions[includedPath] = subscription; |
- } |
- _cancelCurrentWatchSubscriptions(); |
- this.watchSubscriptions = newSubscriptions; |
- } |
- } |
- // Create or update the analysis context. |
- if (context == null) { |
- context = callbacks.addContext(contextFolder, defaultContextOptions); |
- ChangeSet changeSet = |
- _buildChangeSet(added: _includedFiles(includedPaths, excludedPaths)); |
- callbacks.applyChangesToContext(contextFolder, changeSet); |
- } else { |
- // TODO(brianwilkerson) Optimize this. |
- List<File> oldFiles = |
- _includedFiles(this.includedPaths, this.excludedPaths); |
- List<File> newFiles = _includedFiles(includedPaths, excludedPaths); |
- ChangeSet changeSet = _buildChangeSet( |
- added: _diff(newFiles, oldFiles), removed: _diff(oldFiles, newFiles)); |
- callbacks.applyChangesToContext(contextFolder, changeSet); |
- } |
- this.includedPaths = includedPaths; |
- this.excludedPaths = excludedPaths; |
- } |
- |
- /** |
- * Recursively add the given [resource] (if it's a file) or its children (if |
- * it's a folder) to the [addedFiles]. |
- */ |
- void _addFilesInResource( |
- List<File> addedFiles, Resource resource, List<String> excludedPaths) { |
- if (_isImplicitlyExcludedResource(resource)) { |
- return; |
- } |
- String path = resource.path; |
- if (_isEqualOrWithinAny(excludedPaths, path)) { |
- return; |
- } |
- if (resource is File) { |
- if (_matchesAnyAnalyzedFilesGlob(path) && resource.exists) { |
- addedFiles.add(resource); |
- } |
- } else if (resource is Folder) { |
- for (Resource child in _getChildrenSafe(resource)) { |
- _addFilesInResource(addedFiles, child, excludedPaths); |
- } |
- } |
- } |
- |
- ChangeSet _buildChangeSet({List<File> added, List<File> removed}) { |
- ChangeSet changeSet = new ChangeSet(); |
- if (added != null) { |
- for (File file in added) { |
- Source source = createSourceInContext(context, file); |
- changeSet.addedSource(source); |
- } |
- } |
- if (removed != null) { |
- for (File file in removed) { |
- Source source = createSourceInContext(context, file); |
- changeSet.removedSource(source); |
- } |
- } |
- return changeSet; |
- } |
- |
- void _cancelCurrentWatchSubscriptions() { |
- for (StreamSubscription<WatchEvent> subscription |
- in watchSubscriptions.values) { |
- subscription.cancel(); |
- } |
- watchSubscriptions.clear(); |
- } |
- |
- String _commonPrefix(List<String> paths) { |
- if (paths.isEmpty) { |
- return ''; |
- } |
- List<String> left = pathContext.split(paths[0]); |
- int count = left.length; |
- for (int i = 1; i < paths.length; i++) { |
- List<String> right = pathContext.split(paths[i]); |
- count = _commonComponents(left, count, right); |
- } |
- return pathContext.joinAll(left.sublist(0, count)); |
- } |
- |
- List<Resource> _existingResources(List<String> pathList) { |
- List<Resource> resources = <Resource>[]; |
- for (String path in pathList) { |
- Resource resource = resourceProvider.getResource(path); |
- if (resource is Folder) { |
- resources.add(resource); |
- } else if (!resource.exists) { |
- // Non-existent resources are ignored. TODO(paulberry): we should set |
- // up a watcher to ensure that if the resource appears later, we will |
- // begin analyzing it. |
- } else if (resource is File) { |
- resources.add(resource); |
- } else { |
- throw new UnimplementedError('$path is not a folder. ' |
- 'Only support for file and folder analysis is implemented.'); |
- } |
- } |
- return resources; |
- } |
- |
- void _handleWatchEvent(WatchEvent event) { |
- String path = event.path; |
- // Ignore if excluded. |
- if (_isExcludedPath(path)) { |
- return; |
- } |
- // Ignore if not in a root. |
- if (!_isContainedIn(includedPaths, path)) { |
- return; |
- } |
- // Handle the change. |
- switch (event.type) { |
- case ChangeType.ADD: |
- Resource resource = resourceProvider.getResource(path); |
- if (resource is File) { |
- if (_matchesAnyAnalyzedFilesGlob(path)) { |
- callbacks.applyChangesToContext( |
- contextFolder, _buildChangeSet(added: <File>[resource])); |
- } |
- } |
- break; |
- case ChangeType.REMOVE: |
- List<Source> sources = context.getSourcesWithFullName(path); |
- if (!sources.isEmpty) { |
- ChangeSet changeSet = new ChangeSet(); |
- sources.forEach(changeSet.removedSource); |
- callbacks.applyChangesToContext(contextFolder, changeSet); |
- } |
- break; |
- case ChangeType.MODIFY: |
- List<Source> sources = context.getSourcesWithFullName(path); |
- if (!sources.isEmpty) { |
- ChangeSet changeSet = new ChangeSet(); |
- sources.forEach(changeSet.changedSource); |
- callbacks.applyChangesToContext(contextFolder, changeSet); |
- } |
- break; |
- } |
- } |
- |
- List<File> _includedFiles( |
- List<String> includedPaths, List<String> excludedPaths) { |
- List<Resource> includedResources = _existingResources(includedPaths); |
- List<File> includedFiles = <File>[]; |
- for (Resource resource in includedResources) { |
- _addFilesInResource(includedFiles, resource, excludedPaths); |
- } |
- return includedFiles; |
- } |
- |
- bool _isContainedIn(List<String> pathList, String path) { |
- for (String pathInList in pathList) { |
- if (_isEqualOrWithin(pathInList, path)) { |
- return true; |
- } |
- } |
- return false; |
- } |
- |
- bool _isEqualOrWithin(String parent, String child) { |
- return child == parent || pathContext.isWithin(parent, child); |
- } |
- |
- bool _isEqualOrWithinAny(List<String> parents, String child) { |
- for (String parent in parents) { |
- if (_isEqualOrWithin(parent, child)) { |
- return true; |
- } |
- } |
- return false; |
- } |
- |
- /** |
- * Return `true` if the given [path] should be excluded, using explicit |
- * or implicit rules. |
- */ |
- bool _isExcludedPath(String path) { |
- List<String> parts = resourceProvider.pathContext.split(path); |
- // Implicit rules. |
- for (String part in parts) { |
- if (part.startsWith('.')) { |
- return true; |
- } |
- } |
- // Explicitly excluded paths. |
- if (_isEqualOrWithinAny(excludedPaths, path)) { |
- return true; |
- } |
- // OK |
- return false; |
- } |
- |
- /** |
- * Return `true` if the given [resource] and children should be excluded |
- * because of some implicit exclusion rules, e.g. `.name`. |
- */ |
- bool _isImplicitlyExcludedResource(Resource resource) { |
- String shortName = resource.shortName; |
- if (shortName.startsWith('.')) { |
- return true; |
- } |
- return false; |
- } |
- |
- /** |
- * Return `true` if the given [path] matches one of the [analyzedFilesGlobs]. |
- */ |
- bool _matchesAnyAnalyzedFilesGlob(String path) { |
- for (Glob glob in analyzedFilesGlobs) { |
- if (glob.matches(path)) { |
- return true; |
- } |
- } |
- return false; |
- } |
- |
- /** |
- * Return a list consisting of the elements from [pathList] that describe the |
- * minimal set of directories that include everything in the original list of |
- * paths and nothing more. In particular: |
- * |
- * * if a path is in the input list multiple times it will appear at most |
- * once in the output list, and |
- * * if a directory D and a subdirectory of it are both in the input list |
- * then only the directory D will be in the output list. |
- * |
- * The original list is not modified. |
- */ |
- List<String> _nonOverlappingPaths(List<String> pathList) { |
- List<String> sortedPaths = new List<String>.from(pathList); |
- sortedPaths.sort((a, b) => a.length - b.length); |
- int pathCount = sortedPaths.length; |
- for (int i = pathCount - 1; i > 0; i--) { |
- String path = sortedPaths[i]; |
- for (int j = 0; j < i; j++) { |
- if (_isEqualOrWithin(path, sortedPaths[j])) { |
- sortedPaths.removeAt(i); |
- break; |
- } |
- } |
- } |
- return sortedPaths; |
- } |
- |
- /** |
- * Normalize all package root sources by mapping them to folders on the |
- * filesystem. Ignore any package root sources that aren't folders. |
- */ |
- void _updateNormalizedPackageRoots() { |
- normalizedPackageRoots = <String, String>{}; |
- packageRoots.forEach((String sourcePath, String targetPath) { |
- Resource resource = resourceProvider.getResource(sourcePath); |
- if (resource is Folder) { |
- normalizedPackageRoots[resource.path] = targetPath; |
- } |
- }); |
- } |
- |
- /** |
- * Create and return a source representing the given [file] within the given |
- * [context]. |
- */ |
- static Source createSourceInContext(AnalysisContext context, File file) { |
- // TODO(brianwilkerson) Optimize this, by allowing support for source |
- // factories to restore URI's from a file path rather than a source. |
- Source source = file.createSource(); |
- if (context == null) { |
- return source; |
- } |
- Uri uri = context.sourceFactory.restoreUri(source); |
- return file.createSource(uri); |
- } |
- |
- static int _commonComponents( |
- List<String> left, int count, List<String> right) { |
- int max = math.min(count, right.length); |
- for (int i = 0; i < max; i++) { |
- if (left[i] != right[i]) { |
- return i; |
- } |
- } |
- return max; |
- } |
- |
- /** |
- * Return a list of all the files in the [left] that are not in the [right]. |
- */ |
- static List<File> _diff(List<File> left, List<File> right) { |
- List<File> diff = new List.from(left); |
- for (File file in right) { |
- diff.remove(file); |
- } |
- return diff; |
- } |
- |
- static List<Resource> _getChildrenSafe(Folder folder) { |
- try { |
- return folder.getChildren(); |
- } on FileSystemException { |
- // The folder either doesn't exist or cannot be read. |
- // Either way, there are no children. |
- return const <Resource>[]; |
- } |
- } |
-} |