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

Unified Diff: pkg/analysis_server/lib/src/single_context_manager.dart

Issue 1918893002: Initial version of SingleContextManager. (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Created 4 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 side-by-side diff with in-line comments
Download patch
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
new file mode 100644
index 0000000000000000000000000000000000000000..6e5d51e19a4c68ab0b6a9dfc9963407fcfe2b266
--- /dev/null
+++ b/pkg/analysis_server/lib/src/single_context_manager.dart
@@ -0,0 +1,366 @@
+// 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.
+
+library analysis_server.src.experimental;
Brian Wilkerson 2016/04/25 15:31:06 "experimental" --> "single_context_manager"
+
+import 'dart:core' hide Resource;
+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/source/path_filter.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;
+
+/**
+ * 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 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:` URI's within a given folder, or `null` if we should fall back
+ * to the standard URI resolver.
+ */
+ final ResolverProvider packageResolverProvider;
+
+ /**
+ * A list of the globs used to determine which files should be analyzed.
+ */
+ final List<Glob> analyzedFilesGlobs;
+
+ /**
+ * 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 context in which everything is being analyzed.
+ */
+ AnalysisContext context;
+
+ /**
+ * The folder associated with the context.
+ */
+ Folder contextFolder;
+
+ /**
+ * The [PathFilter] used to filter sources from being analyzed.
+ */
+ PathFilter pathFilter;
+
+ /**
+ * The [packageResolverProvider] must not be `null`.
+ */
+ SingleContextManager(this.resourceProvider, this.sdkManager,
+ this.packageResolverProvider, this.analyzedFilesGlobs);
+
+ @override
+ Iterable<AnalysisContext> get analysisContexts =>
+ context == null ? <AnalysisContext>[] : <AnalysisContext>[context];
+
+ @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
+ AnalysisContext getContextFor(String path) {
+ if (context == null) {
+ return null;
+ } else if (_isContainedIn(includedPaths, path)) {
+ return context;
+ }
+ return null;
+ }
+
+ @override
+ bool isIgnored(String path) => pathFilter.ignored(path);
+
+ @override
+ bool isInAnalysisRoot(String path) {
+ return _isContainedIn(includedPaths, path) &&
+ !_isContainedIn(excludedPaths, path);
+ }
+
+ @override
+ void refresh(List<Resource> roots) {
+ if (context != null) {
+ // TODO(brianwilkerson) Not sure whether this is right.
+ callbacks.removeContext(contextFolder, null);
+ context.dispose();
+ context = null;
+ contextFolder = null;
+ pathFilter = null;
+ 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();
+ if (context == null) {
+ String contextPath = _commonPrefix(includedPaths);
+ contextFolder = resourceProvider.getFolder(contextPath);
+ // TODO(scheglov) watch
Brian Wilkerson 2016/04/25 15:31:06 More detail would be good; I'm not sure what "watc
+ pathFilter = new PathFilter(
+ contextFolder.path, null, resourceProvider.pathContext);
+ AnalysisOptions options = new AnalysisOptionsImpl();
+ FolderDisposition disposition = new CustomPackageResolverDisposition(
+ packageResolverProvider(contextFolder));
+ context = callbacks.addContext(contextFolder, options, disposition);
+ ChangeSet changeSet =
+ _buildChangeSet(added: _includedFiles(includedPaths, excludedPaths));
+ callbacks.applyChangesToContext(contextFolder, changeSet);
+ } else {
+ // TODO(scheglov) in general 'contextFolder' may be different now
+ // 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;
+ }
+ // TODO(scheglov) apply pathFilter
+ if (_isEqualOrWithinAny(excludedPaths, resource.path)) {
+ return;
+ }
+ if (resource is File && resource.exists) {
+ addedFiles.add(resource);
+ } else if (resource is Folder) {
+ for (Resource child in _getChildrenSafe(resource)) {
+ _addFilesInResource(addedFiles, child, excludedPaths);
+ }
+ }
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * 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;
+ }
+ });
+ }
+
+ static ChangeSet _buildChangeSet({List<File> added, List<File> removed}) {
+ ChangeSet changeSet = new ChangeSet();
+ if (added != null) {
+ for (File file in added) {
+ changeSet.addedSource(file.createSource());
+ }
+ }
+ if (removed != null) {
+ for (File file in removed) {
+ changeSet.removedSource(file.createSource());
+ }
+ }
+ return changeSet;
+ }
+
+ 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;
+ }
+
+ static String _commonPrefix(List<String> paths) {
+ if (paths.isEmpty) {
+ return '';
+ }
+ List<String> left = path.split(paths[0]);
+ int count = left.length;
+ for (int i = 0; i < paths.length; i++) {
+ List<String> right = path.split(paths[0]);
+ count = _commonComponents(left, count, right);
+ }
+ return path.joinAll(left);
+ }
+
+ /**
+ * 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>[];
+ }
+ }
+
+ static bool _isContainedIn(List<String> pathList, String path) {
+ for (String pathInList in pathList) {
+ if (_isEqualOrWithin(path, pathInList)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static bool _isEqualOrWithin(String parent, String child) {
+ return child == parent || path.isWithin(parent, child);
+ }
+
+ static bool _isEqualOrWithinAny(List<String> parents, String child) {
+ for (String parent in parents) {
+ if (_isEqualOrWithin(parent, child)) {
+ 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.
+ */
+ static 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;
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698