|  | // Copyright (c) 2018, 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 'package:analyzer/dart/analysis/context_root.dart'; | 
|  | import 'package:analyzer/file_system/file_system.dart'; | 
|  | import 'package:analyzer/file_system/physical_file_system.dart' | 
|  | show PhysicalResourceProvider; | 
|  | import 'package:analyzer/src/analysis_options/analysis_options_file.dart'; | 
|  | import 'package:analyzer/src/analysis_options/analysis_options_provider.dart'; | 
|  | import 'package:analyzer/src/context/packages.dart'; | 
|  | import 'package:analyzer/src/dart/analysis/analysis_options.dart'; | 
|  | import 'package:analyzer/src/dart/analysis/context_root.dart'; | 
|  | import 'package:analyzer/src/lint/pub.dart'; | 
|  | import 'package:analyzer/src/util/file_paths.dart' as file_paths; | 
|  | import 'package:analyzer/src/util/yaml.dart'; | 
|  | import 'package:analyzer/src/utilities/extensions/file_system.dart'; | 
|  | import 'package:analyzer/src/workspace/basic.dart'; | 
|  | import 'package:analyzer/src/workspace/blaze.dart'; | 
|  | import 'package:analyzer/src/workspace/gn.dart'; | 
|  | import 'package:analyzer/src/workspace/pub.dart'; | 
|  | import 'package:analyzer/src/workspace/workspace.dart'; | 
|  | import 'package:collection/collection.dart'; | 
|  | import 'package:glob/glob.dart'; | 
|  | import 'package:path/path.dart'; | 
|  | import 'package:yaml/yaml.dart'; | 
|  |  | 
|  | /// Determines the list of analysis contexts that can be used to analyze the | 
|  | /// files and folders that should be analyzed given a list of included files and | 
|  | /// folders and a list of excluded files and folders. | 
|  | class ContextLocatorImpl { | 
|  | /// The resource provider used to access the file system. | 
|  | final ResourceProvider resourceProvider; | 
|  |  | 
|  | /// Initialize a newly created context locator. If a [resourceProvider] is | 
|  | /// supplied, it will be used to access the file system. Otherwise the default | 
|  | /// resource provider will be used. | 
|  | ContextLocatorImpl({ResourceProvider? resourceProvider}) | 
|  | : resourceProvider = resourceProvider ?? PhysicalResourceProvider.INSTANCE; | 
|  |  | 
|  | /// Return a list of the context roots that should be used to analyze the | 
|  | /// files that are included by the list of [includedPaths] and not excluded by | 
|  | /// the list of [excludedPaths]. | 
|  | /// | 
|  | /// If an [optionsFile] is specified, then it is assumed to be the path to the | 
|  | /// `analysis_options.yaml` file that should be used in place of the ones that | 
|  | /// would be found by looking in the directories containing the context roots. | 
|  | /// | 
|  | /// If a [packagesFile] is specified, then it is assumed to be the path to the | 
|  | /// `.packages` file that should be used in place of the one that would be | 
|  | /// found by looking in the directories containing the context roots. | 
|  | List<ContextRoot> locateRoots({ | 
|  | required List<String> includedPaths, | 
|  | List<String>? excludedPaths, | 
|  | String? optionsFile, | 
|  | String? packagesFile, | 
|  | }) { | 
|  | // | 
|  | // Compute the list of folders and files that are to be included. | 
|  | // | 
|  | List<Folder> includedFolders = <Folder>[]; | 
|  | List<File> includedFiles = <File>[]; | 
|  | _resourcesFromPaths(includedPaths, includedFolders, includedFiles); | 
|  | // | 
|  | // Compute the list of folders and files that are to be excluded. | 
|  | // | 
|  | List<Folder> excludedFolders = <Folder>[]; | 
|  | List<File> excludedFiles = <File>[]; | 
|  | _resourcesFromPaths( | 
|  | excludedPaths ?? const <String>[], | 
|  | excludedFolders, | 
|  | excludedFiles, | 
|  | ); | 
|  | // | 
|  | // Use the excluded folders and files to filter the included folders and | 
|  | // files. | 
|  | // | 
|  | includedFolders = includedFolders | 
|  | .where( | 
|  | (Folder includedFolder) => | 
|  | !_containedInAny(excludedFolders, includedFolder), | 
|  | ) | 
|  | .toList(); | 
|  | includedFiles = includedFiles | 
|  | .where( | 
|  | (File includedFile) => | 
|  | !_containedInAny(excludedFolders, includedFile) && | 
|  | !excludedFiles.contains(includedFile), | 
|  | ) | 
|  | .toList(); | 
|  | // | 
|  | // We now have a list of all of the files and folders that need to be | 
|  | // analyzed. For each, walk the directory structure and figure out where to | 
|  | // create context roots. | 
|  | // | 
|  | File? defaultOptionsFile; | 
|  | if (optionsFile != null) { | 
|  | defaultOptionsFile = resourceProvider.getFile(optionsFile); | 
|  | if (!defaultOptionsFile.exists) { | 
|  | defaultOptionsFile = null; | 
|  | } | 
|  | } | 
|  | File? defaultPackagesFile; | 
|  | if (packagesFile != null) { | 
|  | defaultPackagesFile = resourceProvider.getFile(packagesFile); | 
|  | if (!defaultPackagesFile.exists) { | 
|  | defaultPackagesFile = null; | 
|  | } | 
|  | } | 
|  |  | 
|  | var workspaceResolutionRootMap = <String, List<Folder>>{}; | 
|  | var nonWorkspaceResolutionFolders = <Folder>[]; | 
|  | _sortIncludedFoldersIntoWorkspaceResolutions( | 
|  | includedFolders, | 
|  | defaultOptionsFile, | 
|  | defaultPackagesFile, | 
|  | nonWorkspaceResolutionFolders, | 
|  | workspaceResolutionRootMap, | 
|  | ); | 
|  |  | 
|  | var roots = <ContextRootImpl>[]; | 
|  | for (var workspaceResolution in workspaceResolutionRootMap.entries) { | 
|  | var workspaceRootFolder = resourceProvider.getFolder( | 
|  | workspaceResolution.key, | 
|  | ); | 
|  | var location = _contextRootLocation( | 
|  | workspaceRootFolder, | 
|  | defaultOptionsFile: defaultOptionsFile, | 
|  | defaultPackagesFile: defaultPackagesFile, | 
|  | defaultRootFolder: () => workspaceRootFolder, | 
|  | ); | 
|  |  | 
|  | ContextRootImpl root = _createContextRoot( | 
|  | roots, | 
|  | rootFolder: workspaceRootFolder, | 
|  | workspace: location.workspace, | 
|  | optionsFile: location.optionsFile, | 
|  | packagesFile: location.packagesFile, | 
|  | ); | 
|  |  | 
|  | var rootEnabledLegacyPlugins = _getEnabledLegacyPlugins( | 
|  | location.workspace, | 
|  | location.optionsFile, | 
|  | ); | 
|  |  | 
|  | Set<String> visited = {}; | 
|  | bool usedRoot = false; | 
|  |  | 
|  | for (var folder in workspaceResolution.value) { | 
|  | if (!root.isAnalyzed(folder.path)) { | 
|  | root.included.add(folder); | 
|  | } | 
|  |  | 
|  | usedRoot |= _createContextRoots( | 
|  | roots, | 
|  | visited, | 
|  | folder, | 
|  | excludedFolders, | 
|  | root, | 
|  | rootEnabledLegacyPlugins, | 
|  | root.excludedGlobs, | 
|  | defaultOptionsFile, | 
|  | defaultPackagesFile, | 
|  | ); | 
|  | } | 
|  | if (!usedRoot) { | 
|  | // If all included folders under this workspace resolution ended up | 
|  | // creating new contexts remove the (not used) root. | 
|  | roots.remove(root); | 
|  | } | 
|  | } | 
|  |  | 
|  | for (Folder folder in nonWorkspaceResolutionFolders) { | 
|  | var location = _contextRootLocation( | 
|  | folder, | 
|  | defaultOptionsFile: defaultOptionsFile, | 
|  | defaultPackagesFile: defaultPackagesFile, | 
|  | defaultRootFolder: () => folder, | 
|  | ); | 
|  |  | 
|  | ContextRootImpl? root; | 
|  | // Check whether there are existing roots that overlap with this one. | 
|  | for (var existingRoot in roots) { | 
|  | if (existingRoot.root.isOrContains(folder.path)) { | 
|  | if (_matchRootWithLocation(existingRoot, location)) { | 
|  | // This root is covered exactly by the existing root (with the same | 
|  | // options/packages file) so we can simple use it. | 
|  | root = existingRoot; | 
|  | break; | 
|  | } else { | 
|  | // This root is within another (but doesn't share options/packages) | 
|  | // so we still need a new root. However, we should exclude this | 
|  | // from the existing root so these files aren't analyzed by both. | 
|  | // | 
|  | // It's possible this folder is already excluded (for example | 
|  | // because it's also a project and had a context root created as | 
|  | // part of the parent analysis root). | 
|  | if (!existingRoot.excluded.contains(folder)) { | 
|  | existingRoot.excluded.add(folder); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | root ??= _createContextRoot( | 
|  | roots, | 
|  | rootFolder: folder, | 
|  | workspace: location.workspace, | 
|  | optionsFile: location.optionsFile, | 
|  | packagesFile: location.packagesFile, | 
|  | ); | 
|  |  | 
|  | if (!root.isAnalyzed(folder.path)) { | 
|  | root.included.add(folder); | 
|  | } | 
|  |  | 
|  | var rootEnabledLegacyPlugins = _getEnabledLegacyPlugins( | 
|  | location.workspace, | 
|  | location.optionsFile, | 
|  | ); | 
|  |  | 
|  | _createContextRootsIn( | 
|  | roots, | 
|  | {}, | 
|  | folder, | 
|  | excludedFolders, | 
|  | root, | 
|  | rootEnabledLegacyPlugins, | 
|  | root.excludedGlobs, | 
|  | defaultOptionsFile, | 
|  | defaultPackagesFile, | 
|  | ); | 
|  | } | 
|  |  | 
|  | for (File file in includedFiles) { | 
|  | Folder parent = file.parent; | 
|  |  | 
|  | var location = _contextRootLocation( | 
|  | parent, | 
|  | defaultOptionsFile: defaultOptionsFile, | 
|  | defaultPackagesFile: defaultPackagesFile, | 
|  | defaultRootFolder: () => _fileSystemRoot(parent), | 
|  | ); | 
|  |  | 
|  | ContextRootImpl? root; | 
|  | for (var existingRoot in roots) { | 
|  | if (existingRoot.root.isOrContains(file.path) && | 
|  | _matchRootWithLocation(existingRoot, location)) { | 
|  | root = existingRoot; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | root ??= _createContextRoot( | 
|  | roots, | 
|  | rootFolder: location.rootFolder, | 
|  | workspace: location.workspace, | 
|  | optionsFile: location.optionsFile, | 
|  | packagesFile: location.packagesFile, | 
|  | ); | 
|  |  | 
|  | if (!root.isAnalyzed(file.path)) { | 
|  | root.included.add(file); | 
|  | } | 
|  | } | 
|  | return roots; | 
|  | } | 
|  |  | 
|  | /// Return `true` if the given [resource] is contained in one or more of the | 
|  | /// given [folders]. | 
|  | bool _containedInAny(Iterable<Folder> folders, Resource resource) => | 
|  | folders.any((Folder folder) => folder.contains(resource.path)); | 
|  |  | 
|  | /// Return the location of a context root for a file in the [parent]. | 
|  | /// | 
|  | /// If the [defaultOptionsFile] is provided, it will be used, not a file | 
|  | /// found relative to the [parent]. | 
|  | /// | 
|  | /// If the [defaultPackagesFile] is provided, it will be used, not a file | 
|  | /// found relative to the [parent]. | 
|  | /// | 
|  | /// The root folder of the context is the parent of either the options, | 
|  | /// or the packages (grand-parent for `.dart_tool/package_config.json`) file, | 
|  | /// whichever is lower. | 
|  | _RootLocation _contextRootLocation( | 
|  | Folder parent, { | 
|  | required File? defaultOptionsFile, | 
|  | required File? defaultPackagesFile, | 
|  | required Folder Function() defaultRootFolder, | 
|  | }) { | 
|  | File? optionsFile; | 
|  | Folder? optionsFolderToChooseRoot; | 
|  | if (defaultOptionsFile != null) { | 
|  | optionsFile = defaultOptionsFile; | 
|  | } else { | 
|  | optionsFile = parent.findAnalysisOptionsYamlFile(); | 
|  | optionsFolderToChooseRoot = optionsFile?.parent; | 
|  | } | 
|  |  | 
|  | File? packagesFile; | 
|  | Folder? packagesFolderToChooseRoot; | 
|  | if (defaultPackagesFile != null) { | 
|  | packagesFile = defaultPackagesFile; | 
|  | // If  the packages file is in .dart_tool directory, use the grandparent | 
|  | // folder, else use the parent folder. | 
|  | packagesFolderToChooseRoot = | 
|  | _findPackagesFile(packagesFile.parent)?.parent ?? packagesFile.parent; | 
|  | } | 
|  |  | 
|  | var buildGnFile = _findBuildGnFile(parent); | 
|  |  | 
|  | var rootFolder = _lowest([optionsFolderToChooseRoot, buildGnFile?.parent]); | 
|  |  | 
|  | // If default packages file is given, create workspace for it. | 
|  | var workspace = _createWorkspace( | 
|  | folder: parent, | 
|  | packagesFile: packagesFile, | 
|  | buildGnFile: buildGnFile, | 
|  | ); | 
|  |  | 
|  | if (workspace is! BasicWorkspace) { | 
|  | rootFolder = _lowest([ | 
|  | rootFolder, | 
|  | resourceProvider.getFolder(workspace.root), | 
|  | ]); | 
|  | } | 
|  |  | 
|  | if (workspace is PackageConfigWorkspace) { | 
|  | packagesFile ??= workspace.packageConfigFile; | 
|  | // If the default packages folder is a parent of the workspace root, | 
|  | // choose that as the root. | 
|  | if (rootFolder != null && packagesFolderToChooseRoot != null) { | 
|  | if (packagesFolderToChooseRoot.contains(rootFolder.path)) { | 
|  | rootFolder = packagesFolderToChooseRoot; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (rootFolder == null) { | 
|  | rootFolder = defaultRootFolder(); | 
|  | if (workspace is BasicWorkspace) { | 
|  | workspace = _createWorkspace( | 
|  | folder: rootFolder, | 
|  | packagesFile: packagesFile, | 
|  | buildGnFile: buildGnFile, | 
|  | ); | 
|  | } | 
|  | } | 
|  |  | 
|  | return _RootLocation( | 
|  | rootFolder: rootFolder, | 
|  | workspace: workspace, | 
|  | optionsFile: optionsFile, | 
|  | packagesFile: packagesFile, | 
|  | ); | 
|  | } | 
|  |  | 
|  | ContextRootImpl _createContextRoot( | 
|  | List<ContextRootImpl> roots, { | 
|  | required Folder rootFolder, | 
|  | required Workspace workspace, | 
|  | required File? optionsFile, | 
|  | required File? packagesFile, | 
|  | }) { | 
|  | optionsFile ??= _findDefaultOptionsFile(workspace); | 
|  |  | 
|  | var root = ContextRootImpl(resourceProvider, rootFolder, workspace); | 
|  | root.packagesFile = packagesFile; | 
|  | root.optionsFile = optionsFile; | 
|  | if (optionsFile != null) { | 
|  | root.optionsFileMap[rootFolder] = optionsFile; | 
|  | } | 
|  |  | 
|  | root.excludedGlobs = _getExcludedGlobs(optionsFile, workspace); | 
|  | roots.add(root); | 
|  | return root; | 
|  | } | 
|  |  | 
|  | /// If the given [folder] should be the root of a new analysis context, then | 
|  | /// create a new context root for it and add it to the list of context | 
|  | /// [roots]. The [containingRoot] is the context root from an enclosing | 
|  | /// directory and is used to inherit configuration information that isn't | 
|  | /// overridden. | 
|  | /// | 
|  | /// If either the [optionsFile] or [packagesFile] is non-`null` then the given | 
|  | /// file will be used even if there is a local version of the file. | 
|  | /// | 
|  | /// For each directory within the given [folder] that is neither in the list | 
|  | /// of [excludedFolders] nor excluded by the [excludedGlobs], recursively | 
|  | /// search for nested context roots. | 
|  | /// | 
|  | /// Returns true if the folder was contained in the root and did not create a | 
|  | /// new root, false if it did create a new root. | 
|  | bool _createContextRoots( | 
|  | List<ContextRoot> roots, | 
|  | Set<String> visited, | 
|  | Folder folder, | 
|  | List<Folder> excludedFolders, | 
|  | ContextRoot containingRoot, | 
|  | Set<String> containingRootEnabledLegacyPlugins, | 
|  | List<LocatedGlob> excludedGlobs, | 
|  | File? optionsFile, | 
|  | File? packagesFile, | 
|  | ) { | 
|  | // | 
|  | // If the options and packages files are allowed to be locally specified, | 
|  | // then look to see whether they are. | 
|  | // | 
|  | File? localOptionsFile; | 
|  | if (optionsFile == null) { | 
|  | localOptionsFile = folder.existingAnalysisOptionsYamlFile; | 
|  | } | 
|  | File? localPackagesFile; | 
|  | if (packagesFile == null) { | 
|  | localPackagesFile = _getPackagesFile(folder); | 
|  | } | 
|  | var buildGnFile = folder.getExistingFile(file_paths.buildGn); | 
|  |  | 
|  | var localEnabledPlugins = _getEnabledLegacyPlugins( | 
|  | containingRoot.workspace, | 
|  | localOptionsFile, | 
|  | ); | 
|  | // Legacy plugins differ only if there is an analysis_options and it | 
|  | // contains a different set of plugins from the containing context. | 
|  | var pluginsDiffer = | 
|  | localOptionsFile != null && | 
|  | !const SetEquality<String>().equals( | 
|  | containingRootEnabledLegacyPlugins, | 
|  | localEnabledPlugins, | 
|  | ); | 
|  |  | 
|  | bool usedThisRoot = true; | 
|  |  | 
|  | // Create a context root for the given [folder] if a packages or build file | 
|  | // is locally specified, or the set of enabled legacy plugins changed. | 
|  | if (pluginsDiffer || localPackagesFile != null || buildGnFile != null) { | 
|  | if (optionsFile != null) { | 
|  | localOptionsFile = optionsFile; | 
|  | } | 
|  | if (packagesFile != null) { | 
|  | localPackagesFile = packagesFile; | 
|  | } | 
|  | var rootPackagesFile = localPackagesFile ?? containingRoot.packagesFile; | 
|  | var workspace = _createWorkspace( | 
|  | folder: folder, | 
|  | packagesFile: rootPackagesFile, | 
|  | buildGnFile: buildGnFile, | 
|  | ); | 
|  | var root = ContextRootImpl(resourceProvider, folder, workspace); | 
|  | root.packagesFile = rootPackagesFile; | 
|  | // Check for analysis options file in the parent directories, from | 
|  | // root folder to the containing root folder. Pick the one closest | 
|  | // to the root. | 
|  | if (localOptionsFile == null) { | 
|  | var parentFolder = root.root.parent; | 
|  | while (parentFolder != containingRoot.root) { | 
|  | localOptionsFile = parentFolder.existingAnalysisOptionsYamlFile; | 
|  | if (localOptionsFile != null) { | 
|  | break; | 
|  | } | 
|  | parentFolder = parentFolder.parent; | 
|  | } | 
|  | } | 
|  | root.optionsFile = localOptionsFile ?? containingRoot.optionsFile; | 
|  | root.included.add(folder); | 
|  | containingRoot.excluded.add(folder); | 
|  | roots.add(root); | 
|  | containingRoot = root; | 
|  | containingRootEnabledLegacyPlugins = localEnabledPlugins; | 
|  | excludedGlobs = _getExcludedGlobs(root.optionsFile, workspace); | 
|  | root.excludedGlobs = excludedGlobs; | 
|  | usedThisRoot = false; | 
|  | } | 
|  |  | 
|  | if (localOptionsFile != null) { | 
|  | (containingRoot as ContextRootImpl).optionsFileMap[folder] = | 
|  | localOptionsFile; | 
|  | // Add excluded globs. | 
|  | var excludes = _getExcludedGlobs( | 
|  | localOptionsFile, | 
|  | containingRoot.workspace, | 
|  | ); | 
|  | containingRoot.excludedGlobs.addAll(excludes); | 
|  | } | 
|  | _createContextRootsIn( | 
|  | roots, | 
|  | visited, | 
|  | folder, | 
|  | excludedFolders, | 
|  | containingRoot, | 
|  | containingRootEnabledLegacyPlugins, | 
|  | excludedGlobs, | 
|  | optionsFile, | 
|  | packagesFile, | 
|  | ); | 
|  |  | 
|  | return usedThisRoot; | 
|  | } | 
|  |  | 
|  | /// For each directory within the given [folder] that is neither in the list | 
|  | /// of [excludedFolders] nor excluded by the [excludedGlobs], recursively | 
|  | /// search for nested context roots and add them to the list of [roots]. | 
|  | /// | 
|  | /// If either the [optionsFile] or [packagesFile] is non-`null` then the given | 
|  | /// file will be used even if there is a local version of the file. | 
|  | void _createContextRootsIn( | 
|  | List<ContextRoot> roots, | 
|  | Set<String> visited, | 
|  | Folder folder, | 
|  | List<Folder> excludedFolders, | 
|  | ContextRoot containingRoot, | 
|  | Set<String> containingRootEnabledLegacyPlugins, | 
|  | List<LocatedGlob> excludedGlobs, | 
|  | File? optionsFile, | 
|  | File? packagesFile, | 
|  | ) { | 
|  | bool isExcluded(Folder folder) { | 
|  | if (excludedFolders.contains(folder) || | 
|  | folder.shortName.startsWith('.')) { | 
|  | return true; | 
|  | } | 
|  | // TODO(scheglov): Why not take it from `containingRoot`? | 
|  | for (var pattern in excludedGlobs) { | 
|  | if (pattern.matches(folder.path)) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Stop infinite recursion via links. | 
|  | try { | 
|  | var canonicalFolderPath = folder.resolveSymbolicLinksSync().path; | 
|  | if (!visited.add(canonicalFolderPath)) { | 
|  | return; | 
|  | } | 
|  | } on FileSystemException { | 
|  | return; | 
|  | } | 
|  |  | 
|  | // | 
|  | // Check each of the subdirectories to see whether a context root needs to | 
|  | // be added for it. | 
|  | // | 
|  | try { | 
|  | for (Resource child in folder.getChildren()) { | 
|  | if (child is Folder) { | 
|  | if (excludedFolders.contains(child)) { | 
|  | containingRoot.excluded.add(child); | 
|  | } else if (!isExcluded(child)) { | 
|  | _createContextRoots( | 
|  | roots, | 
|  | visited, | 
|  | child, | 
|  | excludedFolders, | 
|  | containingRoot, | 
|  | containingRootEnabledLegacyPlugins, | 
|  | excludedGlobs, | 
|  | optionsFile, | 
|  | packagesFile, | 
|  | ); | 
|  | } | 
|  | } | 
|  | } | 
|  | } on FileSystemException { | 
|  | // The directory either doesn't exist or cannot be read. Either way, there | 
|  | // are no subdirectories that need to be added. | 
|  | } | 
|  | } | 
|  |  | 
|  | Workspace _createWorkspace({ | 
|  | required Folder folder, | 
|  | required File? packagesFile, | 
|  | required File? buildGnFile, | 
|  | }) { | 
|  | if (buildGnFile != null) { | 
|  | var workspace = GnWorkspace.find(buildGnFile); | 
|  | if (workspace != null) { | 
|  | return workspace; | 
|  | } | 
|  | } | 
|  |  | 
|  | Packages packages; | 
|  | if (packagesFile != null) { | 
|  | packages = parsePackageConfigJsonFile(resourceProvider, packagesFile); | 
|  | } else { | 
|  | packages = Packages.empty; | 
|  | } | 
|  |  | 
|  | var rootPath = folder.path; | 
|  |  | 
|  | Workspace? workspace; | 
|  | workspace = BlazeWorkspace.find( | 
|  | resourceProvider, | 
|  | rootPath, | 
|  | lookForBuildFileSubstitutes: false, | 
|  | ); | 
|  | workspace = _mostSpecificWorkspace( | 
|  | workspace, | 
|  | PackageConfigWorkspace.find(resourceProvider, packages, rootPath), | 
|  | ); | 
|  | workspace ??= BasicWorkspace.find(resourceProvider, packages, rootPath); | 
|  | return workspace; | 
|  | } | 
|  |  | 
|  | File? _findBuildGnFile(Folder folder) { | 
|  | for (var current in folder.withAncestors) { | 
|  | var file = current.getExistingFile(file_paths.buildGn); | 
|  | if (file != null) { | 
|  | return file; | 
|  | } | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | File? _findDefaultOptionsFile(Workspace workspace) { | 
|  | if (workspace is! WorkspaceWithDefaultAnalysisOptions) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | // TODO(scheglov): Create SourceFactory once. | 
|  | var sourceFactory = workspace.createSourceFactory(null, null); | 
|  | var uriStr = WorkspaceWithDefaultAnalysisOptions.uri; | 
|  | var path = sourceFactory.forUri(uriStr)?.fullName; | 
|  | if (path != null) { | 
|  | var file = resourceProvider.getFile(path); | 
|  | if (file.exists) { | 
|  | return file; | 
|  | } | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | /// Return the packages file to be used to analyze files in the given | 
|  | /// [folder], or `null` if there is no packages file in the given folder or | 
|  | /// any parent folder. | 
|  | _PackagesFile? _findPackagesFile(Folder folder) { | 
|  | for (var current in folder.withAncestors) { | 
|  | var file = _getPackagesFile(current); | 
|  | if (file != null) { | 
|  | return _PackagesFile(current, file); | 
|  | } | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | /// Gets the set of enabled legacy plugins for [optionsFile], taking into | 
|  | /// account any includes. | 
|  | Set<String> _getEnabledLegacyPlugins(Workspace workspace, File? optionsFile) { | 
|  | if (optionsFile == null) { | 
|  | return const {}; | 
|  | } | 
|  | try { | 
|  | var provider = AnalysisOptionsProvider( | 
|  | workspace.createSourceFactory(null, null), | 
|  | ); | 
|  |  | 
|  | var options = AnalysisOptionsImpl.fromYaml( | 
|  | optionsMap: provider.getOptionsFromFile(optionsFile), | 
|  | file: optionsFile, | 
|  | resourceProvider: resourceProvider, | 
|  | ); | 
|  |  | 
|  | return options.enabledLegacyPluginNames.toSet(); | 
|  | } catch (_) { | 
|  | // No legacy plugins will be enabled if the file doesn't parse or cannot | 
|  | // be read for any reason. | 
|  | return {}; | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Return a list containing the glob patterns used to exclude files from | 
|  | /// analysis by the given [optionsFile]. The list will be empty if there is no | 
|  | /// options file or if there are no exclusion patterns in the options file. | 
|  | List<LocatedGlob> _getExcludedGlobs(File? optionsFile, Workspace workspace) { | 
|  | List<LocatedGlob> patterns = []; | 
|  | if (optionsFile != null) { | 
|  | try { | 
|  | var doc = AnalysisOptionsProvider( | 
|  | workspace.createSourceFactory(null, null), | 
|  | ).getOptionsFromFile(optionsFile); | 
|  |  | 
|  | var analyzerOptions = doc.valueAt(AnalysisOptionsFile.analyzer); | 
|  | if (analyzerOptions is YamlMap) { | 
|  | var excludeOptions = analyzerOptions.valueAt( | 
|  | AnalysisOptionsFile.exclude, | 
|  | ); | 
|  | if (excludeOptions is YamlList) { | 
|  | var pathContext = resourceProvider.pathContext; | 
|  |  | 
|  | void addGlob(List<String> components) { | 
|  | var pattern = posix.joinAll(components); | 
|  | patterns.add( | 
|  | LocatedGlob( | 
|  | optionsFile.parent, | 
|  | Glob(pattern, context: pathContext), | 
|  | ), | 
|  | ); | 
|  | } | 
|  |  | 
|  | for (String excludedPath in excludeOptions.whereType<String>()) { | 
|  | var excludedComponents = posix.split(excludedPath); | 
|  | addGlob(excludedComponents); | 
|  | if (excludedComponents.last == '**') { | 
|  | addGlob(excludedComponents..removeLast()); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } catch (exception) { | 
|  | // If we can't read and parse the analysis options file, then there | 
|  | // aren't any excluded files that need to be read. | 
|  | } | 
|  | } | 
|  | return patterns; | 
|  | } | 
|  |  | 
|  | /// Return the packages file in the given [folder], or `null` if the folder | 
|  | /// does not contain a packages file. | 
|  | File? _getPackagesFile(Folder folder) { | 
|  | var file = folder | 
|  | .getChildAssumingFolder(file_paths.dotDartTool) | 
|  | .getChildAssumingFile(file_paths.packageConfigJson); | 
|  | if (file.exists) { | 
|  | return file; | 
|  | } | 
|  |  | 
|  | return null; | 
|  | } | 
|  |  | 
|  | /// Load the `workspace` paths from the pubspec file in the given [root]. | 
|  | /// | 
|  | /// From https://dart.dev/tools/pub/workspaces a root folder pubspec file will | 
|  | /// look like this: | 
|  | /// | 
|  | /// ``` | 
|  | /// name: _ | 
|  | /// publish_to: none | 
|  | /// environment: | 
|  | ///   sdk: ^3.6.0 | 
|  | /// workspace: | 
|  | ///   - packages/helper | 
|  | ///   - packages/client_package | 
|  | ///   - packages/server_package | 
|  | /// ``` | 
|  | /// | 
|  | /// This loads the paths from the `workspace` entry and return them as | 
|  | /// Folders if they exist as folders in the filesystem. | 
|  | Set<Folder> _loadWorkspaceDetailsFromPubspec(String root) { | 
|  | var result = <Folder>{}; | 
|  | var rootFolder = resourceProvider.getFolder(root); | 
|  | var rootPubspecFile = rootFolder.getChildAssumingFile( | 
|  | file_paths.pubspecYaml, | 
|  | ); | 
|  | if (rootPubspecFile.exists) { | 
|  | var rootPubspec = Pubspec.parse(rootPubspecFile.readAsStringSync()); | 
|  | var workspace = rootPubspec.workspace; | 
|  | if (workspace != null) { | 
|  | for (var entry in workspace) { | 
|  | if (entry.text case var relativePath?) { | 
|  | var child = rootFolder.getChild(relativePath); | 
|  | if (child.exists && child is Folder) { | 
|  | result.add(child); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /// Add to the given lists of [folders] and [files] all of the resources in | 
|  | /// the given list of [paths] that exist and are not contained within one of | 
|  | /// the folders. | 
|  | void _resourcesFromPaths( | 
|  | List<String> paths, | 
|  | List<Folder> folders, | 
|  | List<File> files, | 
|  | ) { | 
|  | for (String path in _uniqueSortedPaths(paths)) { | 
|  | Resource resource = resourceProvider.getResource(path); | 
|  | if (resource is Folder) { | 
|  | folders.add(resource); | 
|  | } else if (resource is File) { | 
|  | files.add(resource); | 
|  | } else { | 
|  | // Internal error: unhandled kind of resource. | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Sorts [includedFolders] into either pub workspace resolution or not. | 
|  | /// | 
|  | /// For each [Folder] in [includedFolders] sort into either | 
|  | /// [nonWorkspaceResolutionFolders] or [workspaceResolutionRootMap] depending | 
|  | /// on `pubspec.yaml` specifications. | 
|  | /// | 
|  | /// Folders with `pubspec.yaml` files with a `resolution: workspace` setting | 
|  | /// that matches a root-folders `pubspec.yaml` files `workspace` list is | 
|  | /// sorted into the [workspaceResolutionRootMap] map. Other folders end up in | 
|  | /// [nonWorkspaceResolutionFolders]. | 
|  | void _sortIncludedFoldersIntoWorkspaceResolutions( | 
|  | List<Folder> includedFolders, | 
|  | File? defaultOptionsFile, | 
|  | File? defaultPackagesFile, | 
|  | List<Folder> nonWorkspaceResolutionFolders, | 
|  | Map<String, List<Folder>> workspaceResolutionRootMap, | 
|  | ) { | 
|  | var rootWorkspaceSpecification = <String, Set<Folder>>{}; | 
|  | for (Folder folder in includedFolders) { | 
|  | var location = _contextRootLocation( | 
|  | folder, | 
|  | defaultOptionsFile: defaultOptionsFile, | 
|  | defaultPackagesFile: defaultPackagesFile, | 
|  | defaultRootFolder: () => folder, | 
|  | ); | 
|  |  | 
|  | var addedToWorkspace = false; | 
|  |  | 
|  | if (folder.path == location.workspace.root) { | 
|  | // If opening the root don't try to do anything special. | 
|  | var known = rootWorkspaceSpecification[location.workspace.root] ??= {}; | 
|  | known.clear(); | 
|  | nonWorkspaceResolutionFolders.addAll( | 
|  | workspaceResolutionRootMap[location.workspace.root] ?? [], | 
|  | ); | 
|  | } else { | 
|  | var pubspecFile = folder.getChildAssumingFile(file_paths.pubspecYaml); | 
|  | if (pubspecFile.exists) { | 
|  | var pubspec = Pubspec.parse(pubspecFile.readAsStringSync()); | 
|  | var resolution = pubspec.resolution; | 
|  | if (resolution != null && resolution.value.text == 'workspace') { | 
|  | var known = rootWorkspaceSpecification[location.workspace.root] ??= | 
|  | _loadWorkspaceDetailsFromPubspec(location.workspace.root); | 
|  | if (known.contains(folder)) { | 
|  | (workspaceResolutionRootMap[location.workspace.root] ??= []).add( | 
|  | folder, | 
|  | ); | 
|  | addedToWorkspace = true; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | if (!addedToWorkspace) { | 
|  | nonWorkspaceResolutionFolders.add(folder); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Return a list of paths that contains all of the unique elements from the | 
|  | /// given list of [paths], sorted such that shorter paths are first. | 
|  | List<String> _uniqueSortedPaths(List<String> paths) { | 
|  | Set<String> uniquePaths = Set<String>.from(paths); | 
|  | List<String> sortedPaths = uniquePaths.toList(); | 
|  | sortedPaths.sort((a, b) => a.length - b.length); | 
|  | return sortedPaths; | 
|  | } | 
|  |  | 
|  | static Folder _fileSystemRoot(Resource resource) { | 
|  | for (var current = resource.parent; ; current = current.parent) { | 
|  | if (current.isRoot) { | 
|  | return current; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Every element in [folders] must be a folder on the path from a file to | 
|  | /// the root of the file system. As such, they are either the same folder, | 
|  | /// or one is strictly above the other. | 
|  | static Folder? _lowest(List<Folder?> folders) { | 
|  | return folders.fold<Folder?>(null, (result, folder) { | 
|  | if (result == null) { | 
|  | return folder; | 
|  | } else if (folder != null && result.contains(folder.path)) { | 
|  | return folder; | 
|  | } else { | 
|  | return result; | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | /// Return `true` if the configuration of [existingRoot] is the same as | 
|  | /// the requested configuration for the [location]. | 
|  | static bool _matchRootWithLocation( | 
|  | ContextRootImpl existingRoot, | 
|  | _RootLocation location, | 
|  | ) { | 
|  | if (existingRoot.optionsFile != location.optionsFile) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (existingRoot.packagesFile != location.packagesFile) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // BasicWorkspace has no special meaning, so can be ignored. | 
|  | // Other workspaces have semantic meaning, so must match. | 
|  | var workspace = location.workspace; | 
|  | if (workspace is! BasicWorkspace) { | 
|  | if (existingRoot.workspace.root != workspace.root) { | 
|  | return false; | 
|  | } | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /// Pick a workspace with the most specific root. If the root of [first] is | 
|  | /// non-null and is within the root of [second], return [second]. If any of | 
|  | /// [first] and [second] is null, return the other one. If the roots aren't | 
|  | /// within each other, return [first]. | 
|  | static Workspace? _mostSpecificWorkspace( | 
|  | Workspace? first, | 
|  | Workspace? second, | 
|  | ) { | 
|  | if (first == null) return second; | 
|  | if (second == null) return first; | 
|  | if (isWithin(first.root, second.root)) { | 
|  | return second; | 
|  | } | 
|  | return first; | 
|  | } | 
|  | } | 
|  |  | 
|  | /// The packages [file] found for the [parent]. | 
|  | /// | 
|  | /// In case of `.packages` file, [parent] is the parent of [file]. | 
|  | /// | 
|  | /// In case of `.dart_tool/package_config.json` it is a grand-parent. | 
|  | class _PackagesFile { | 
|  | final Folder parent; | 
|  | final File file; | 
|  |  | 
|  | _PackagesFile(this.parent, this.file); | 
|  | } | 
|  |  | 
|  | class _RootLocation { | 
|  | final Folder rootFolder; | 
|  | final Workspace workspace; | 
|  | final File? optionsFile; | 
|  | final File? packagesFile; | 
|  |  | 
|  | _RootLocation({ | 
|  | required this.rootFolder, | 
|  | required this.workspace, | 
|  | required this.optionsFile, | 
|  | required this.packagesFile, | 
|  | }); | 
|  | } |