package com.jetbrains.lang.dart.resolve; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.impl.scopes.LibraryScope; import com.intellij.openapi.module.impl.scopes.LibraryScopeBase; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.*; import com.intellij.openapi.roots.impl.libraries.ProjectLibraryTable; import com.intellij.openapi.roots.libraries.Library; import com.intellij.openapi.util.UserDataHolder; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiManager; import com.intellij.psi.ResolveScopeProvider; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.GlobalSearchScopesCore; import com.intellij.psi.util.CachedValueProvider; import com.intellij.psi.util.CachedValuesManager; import com.intellij.psi.util.PsiModificationTracker; import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; import com.jetbrains.lang.dart.DartFileType; import com.jetbrains.lang.dart.sdk.DartSdk; import com.jetbrains.lang.dart.util.PubspecYamlUtil; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.Collections; import java.util.Set; import static com.jetbrains.lang.dart.util.DartUrlResolver.PACKAGES_FOLDER_NAME; public class DartResolveScopeProvider extends ResolveScopeProvider { @Nullable @Override public GlobalSearchScope getResolveScope(@NotNull final VirtualFile file, @NotNull final Project project) { return getDartScope(project, file, false); } /** * @param strict Strict scope respects <a href="https://www.dartlang.org/tools/pub/package-layout.html">Dart Package Layout Conventions</a>, * not strict includes the whole Dart project and Path Packages it depends on. * But both strict and not strict scope for file in 'packages' folder includes only 'packages', 'lib' and Path Packages folders. */ @Nullable public static GlobalSearchScope getDartScope(@NotNull final Project project, @NotNull final VirtualFile file, boolean strict) { if (file.getFileType() != DartFileType.INSTANCE) return null; final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); final DartSdk sdk = DartSdk.getDartSdk(project); if (fileIndex.isInLibraryClasses(file) && !fileIndex.isInContent(file)) { if (sdk != null && file.getPath().startsWith(sdk.getHomePath() + "/")) { return getDartSdkResolveScope(project); } return getLibraryAndSdkScope(project, file, sdk); } final Module module = fileIndex.getModuleForFile(file); if (module == null) { return null; } VirtualFile contextSubdir = null; VirtualFile dir = file.getParent(); while (dir != null && fileIndex.isInContent(dir)) { final VirtualFile pubspecFile = dir.findChild(PubspecYamlUtil.PUBSPEC_YAML); if (pubspecFile != null) { final boolean inPackages = contextSubdir != null && PACKAGES_FOLDER_NAME.equals(contextSubdir.getName()); return getDartResolveScope(module, pubspecFile, strict || inPackages ? contextSubdir : null); } contextSubdir = dir; dir = dir.getParent(); } // no pubspec.yaml => return module content scope + libs + SDK return module.getModuleContentWithDependenciesScope().union(module.getModuleWithLibrariesScope()); } @Nullable private static GlobalSearchScope getDartSdkResolveScope(@NotNull final Project project) { return CachedValuesManager.getManager(project).getCachedValue(project, () -> { final Library library = ProjectLibraryTable.getInstance(project).getLibraryByName(DartSdk.DART_SDK_LIB_NAME); final LibraryScope scope = library == null ? null : new LibraryScope(project, library); return new CachedValueProvider.Result<GlobalSearchScope>(scope, ProjectRootManager.getInstance(project)); }); } private static GlobalSearchScope getLibraryAndSdkScope(@NotNull final Project project, @NotNull final VirtualFile file, @Nullable final DartSdk sdk) { return CachedValuesManager.getManager(project).getCachedValue(project, () -> { final GlobalSearchScope sdkScope = sdk == null ? null : getDartSdkResolveScope(project); final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); final Set<VirtualFile> roots = new THashSet<>(); for (OrderEntry orderEntry : fileIndex.getOrderEntriesForFile(file)) { Collections.addAll(roots, orderEntry.getFiles(OrderRootType.CLASSES)); } final LibraryScopeBase libraryScope = new DartLibraryScope(project, roots); final GlobalSearchScope scope = sdkScope != null ? sdkScope.union(libraryScope) : libraryScope; return new CachedValueProvider.Result<>(scope, ProjectRootManager.getInstance(project)); }); } @Nullable private static GlobalSearchScope getDartResolveScope(@NotNull final Module module, @NotNull final VirtualFile pubspecFile, @Nullable final VirtualFile contextSubdir) { final Project project = module.getProject(); final UserDataHolder dataHolder = contextSubdir != null ? PsiManager.getInstance(project).findDirectory(contextSubdir) : PsiManager.getInstance(project).findFile(pubspecFile); if (dataHolder == null) { return null; // unlikely as contextSubdir and pubspecFile are checked to be in project } return CachedValuesManager.getManager(project).getCachedValue(dataHolder, () -> { final Collection<VirtualFile> pathPackageRoots = getPathPackageRoots(module.getProject(), pubspecFile); final VirtualFile dartRoot = pubspecFile.getParent(); final GlobalSearchScope scope; if (contextSubdir == null || "test".equals(contextSubdir.getName())) { // the biggest scope scope = createDirectoriesScope(project, pathPackageRoots, dartRoot); } else if ("lib".equals(contextSubdir.getName()) || PACKAGES_FOLDER_NAME.equals(contextSubdir.getName())) { // the smallest scope scope = createDirectoriesScope(project, pathPackageRoots, dartRoot.findChild("lib"), dartRoot.findChild(PACKAGES_FOLDER_NAME)); } else { scope = createDirectoriesScope(project, pathPackageRoots, contextSubdir, dartRoot.findChild("lib"), dartRoot.findChild(PACKAGES_FOLDER_NAME)); } final GlobalSearchScope scopeWithLibs = scope.intersectWith(GlobalSearchScope.projectScope(project)).union(getModuleLibrariesClassesScope(module)); return new CachedValueProvider.Result<>(scopeWithLibs, PsiModificationTracker.MODIFICATION_COUNT); }); } private static Collection<VirtualFile> getPathPackageRoots(@NotNull final Project project, @NotNull final VirtualFile pubspecFile) { final Collection<VirtualFile> result = new SmartList<>(); PubspecYamlUtil.processInProjectPathPackagesRecursively(project, pubspecFile, (packageName, packageDir) -> result.add(packageDir)); return result; } @NotNull private static GlobalSearchScope createDirectoriesScope(@NotNull final Project project, @NotNull final Collection<VirtualFile> pathPackageRoots, @NotNull final VirtualFile... dirs) { GlobalSearchScope scope = null; for (VirtualFile dir : dirs) { if (dir != null && dir.isDirectory()) { if (scope == null) { scope = GlobalSearchScopesCore.directoryScope(project, dir, true); } else { scope = scope.union(GlobalSearchScopesCore.directoryScope(project, dir, true)); } } } assert scope != null; for (VirtualFile packageRoot : pathPackageRoots) { scope = scope.union(GlobalSearchScopesCore.directoryScope(project, packageRoot, true)); } return scope; } private static GlobalSearchScope getModuleLibrariesClassesScope(@NotNull final Module module) { return CachedValuesManager.getManager(module.getProject()).getCachedValue(module, () -> { final Set<VirtualFile> roots = new THashSet<>(); for (OrderEntry orderEntry : ModuleRootManager.getInstance(module).getOrderEntries()) { if (orderEntry instanceof LibraryOrderEntry) { ContainerUtil.addAll(roots, ((LibraryOrderEntry)orderEntry).getRootFiles(OrderRootType.CLASSES)); } } return new CachedValueProvider.Result<GlobalSearchScope>(new DartLibraryScope(module.getProject(), roots), ProjectRootManager.getInstance(module.getProject())); }); } private static class DartLibraryScope extends LibraryScopeBase { public DartLibraryScope(@NotNull final Project project, @NotNull final Set<VirtualFile> roots) { super(project, roots.toArray(new VirtualFile[roots.size()]), VirtualFile.EMPTY_ARRAY); } } }