Skip to content

Commit 6146dfc

Browse files
chashnikovintellij-monorepo-bot
authored andcommitted
[platform] support custom content entries in Module::getModuleContent*Scope methods (IDEA-307392)
If some IDE (e.g. Rider) contributes custom files to the module content which aren't located under the module content roots, they still need to be included in Module::getModuleContentScope. In order to support this, separate classes for content of a module and for content of a module and its dependencies were extracted from ModuleWithDependenciesScope. These new classes use ProjectFileIndex::getModuleForFile instead of getContentRootForFile, so they work for custom content files, and this also works faster than the generic implementation from ModuleWithDependenciesScope. VirtualFileEnumeration isn't supported for ModuleContentScope because it seems that this optimization is relevant for ModuleWithDependenciesScope only, and currently there is no efficient way to compute all content roots of a module including custom ones. GitOrigin-RevId: df8ec70a81d1a2954f64abc13a25fafd97a31eda
1 parent c7ff71c commit 6146dfc

File tree

6 files changed

+102
-47
lines changed

6 files changed

+102
-47
lines changed

java/java-tests/testSrc/com/intellij/roots/ModuleScopesTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@ public void testContentFileOutsideSourceRoots() throws IOException {
7878
VirtualFile file = myFixture.createFile("a/data/A.java", "class A {}");
7979
PsiTestUtil.addContentRoot(module, file.getParent());
8080
assertFalse(module.getModuleScope().contains(file));
81+
assertTrue(module.getModuleContentScope().contains(file));
8182
assertFalse(module.getModuleWithDependenciesScope().contains(file));
83+
assertTrue(module.getModuleContentWithDependenciesScope().contains(file));
8284
assertFalse(module.getModuleWithDependenciesAndLibrariesScope(true).contains(file));
8385
}
8486

@@ -127,6 +129,7 @@ public void testTestOnlyModuleDependency() throws Exception {
127129
VirtualFile classB = myFixture.createFile("b/Test.java", "public class Test { }");
128130
assertTrue(moduleA.getModuleWithDependenciesAndLibrariesScope(true).contains(classB));
129131
assertFalse(moduleA.getModuleWithDependenciesAndLibrariesScope(false).contains(classB));
132+
assertFalse(moduleA.getModuleContentWithDependenciesScope().contains(classB));
130133
assertFalse(moduleA.getModuleWithDependenciesAndLibrariesScope(false).isSearchInModuleContent(moduleB));
131134

132135
VirtualFile[] compilationClasspath = getCompilationClasspath(moduleA);
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
2+
@file:Suppress("EqualsOrHashCode")
3+
package com.intellij.openapi.module.impl.scopes
4+
5+
import com.intellij.openapi.module.Module
6+
import com.intellij.openapi.roots.ModuleRootManager
7+
import com.intellij.openapi.roots.ProjectFileIndex
8+
import com.intellij.openapi.vfs.VirtualFile
9+
import com.intellij.psi.search.GlobalSearchScope
10+
import com.intellij.util.indexing.IndexingBundle
11+
import it.unimi.dsi.fastutil.objects.Object2IntMap
12+
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
13+
14+
internal class ModuleContentScope(private val module: Module) : GlobalSearchScope(module.project) {
15+
private val projectFileIndex = ProjectFileIndex.getInstance(module.project)
16+
17+
override fun contains(file: VirtualFile): Boolean = projectFileIndex.getModuleForFile(file) == module
18+
override fun isSearchInModuleContent(aModule: Module): Boolean = aModule == module
19+
override fun isSearchInLibraries(): Boolean = false
20+
override fun equals(other: Any?): Boolean = (other as? ModuleContentScope)?.module == module
21+
override fun calcHashCode(): Int = module.hashCode()
22+
override fun toString(): String = "ModuleContentScope{module=${module.name}}"
23+
override fun getDisplayName(): String = IndexingBundle.message("search.scope.module", module.name)
24+
}
25+
26+
internal class ModuleWithDependenciesContentScope(private val rootModule: Module) : GlobalSearchScope(rootModule.project) {
27+
private val moduleToOrder : Object2IntMap<Module>
28+
private val projectFileIndex = ProjectFileIndex.getInstance(rootModule.project)
29+
30+
init {
31+
moduleToOrder = Object2IntOpenHashMap()
32+
var i = 1
33+
ModuleRootManager.getInstance(rootModule).orderEntries().recursively().withoutLibraries().withoutSdk().productionOnly().forEachModule {
34+
moduleToOrder.put(it, i++)
35+
true
36+
}
37+
}
38+
39+
override fun contains(file: VirtualFile): Boolean {
40+
val module = projectFileIndex.getModuleForFile(file)
41+
return module != null && module in moduleToOrder
42+
}
43+
44+
override fun isSearchInModuleContent(aModule: Module): Boolean = aModule in moduleToOrder
45+
override fun isSearchInLibraries(): Boolean = false
46+
47+
override fun compare(file1: VirtualFile, file2: VirtualFile): Int {
48+
val order1 = projectFileIndex.getModuleForFile(file1)?.let { moduleToOrder.getInt(it) } ?: 0
49+
val order2 = projectFileIndex.getModuleForFile(file2)?.let { moduleToOrder.getInt(it) } ?: 0
50+
//see javadoc of GlobalSearchScope::compareTo: a positive result indicates that file1 is located in the classpath before file2
51+
return order2.compareTo(order1)
52+
}
53+
54+
override fun equals(other: Any?): Boolean = (other as? ModuleWithDependenciesContentScope)?.rootModule == rootModule
55+
override fun calcHashCode(): Int = rootModule.hashCode()
56+
override fun toString(): String = "ModuleWithDependenciesContentScope{rootModule=${rootModule.name}}"
57+
override fun getDisplayName(): String = IndexingBundle.message("search.scope.module", rootModule.name)
58+
}

platform/analysis-impl/src/com/intellij/openapi/module/impl/scopes/ModuleScopeProviderImpl.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public class ModuleScopeProviderImpl implements ModuleScopeProvider {
1616
private final IntObjectMap<GlobalSearchScope> myScopeCache =
1717
ConcurrentCollectionFactory.createConcurrentIntObjectMap();
1818
private ModuleWithDependentsTestScope myModuleTestsWithDependentsScope;
19+
private volatile ModuleWithDependenciesContentScope myModuleWithDependenciesContentScope;
1920

2021
public ModuleScopeProviderImpl(@NotNull Module module) {
2122
myModule = module;
@@ -58,13 +59,17 @@ public GlobalSearchScope getModuleWithDependenciesScope() {
5859
@NotNull
5960
@Override
6061
public GlobalSearchScope getModuleContentScope() {
61-
return getCachedScope(ModuleWithDependenciesScope.CONTENT);
62+
return new ModuleContentScope(myModule);
6263
}
6364

6465
@NotNull
6566
@Override
6667
public GlobalSearchScope getModuleContentWithDependenciesScope() {
67-
return getCachedScope(ModuleWithDependenciesScope.CONTENT | ModuleWithDependenciesScope.MODULES);
68+
ModuleWithDependenciesContentScope scope = myModuleWithDependenciesContentScope;
69+
if (scope == null) {
70+
myModuleWithDependenciesContentScope = scope = new ModuleWithDependenciesContentScope(myModule);
71+
}
72+
return scope;
6873
}
6974

7075
@Override
@@ -112,5 +117,6 @@ public GlobalSearchScope getModuleRuntimeScope(boolean includeTests) {
112117
public void clearCache() {
113118
myScopeCache.clear();
114119
myModuleTestsWithDependentsScope = null;
120+
myModuleWithDependenciesContentScope = null;
115121
}
116122
}

platform/analysis-impl/src/com/intellij/openapi/module/impl/scopes/ModuleWithDependenciesScope.java

Lines changed: 13 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,8 @@ public final class ModuleWithDependenciesScope extends GlobalSearchScope impleme
4141
public static final int LIBRARIES = 0x02;
4242
public static final int MODULES = 0x04;
4343
public static final int TESTS = 0x08;
44-
public static final int CONTENT = 0x20;
4544

46-
@MagicConstant(flags = {COMPILE_ONLY, LIBRARIES, MODULES, TESTS, CONTENT})
45+
@MagicConstant(flags = {COMPILE_ONLY, LIBRARIES, MODULES, TESTS})
4746
@interface ScopeConstant {}
4847

4948
private static final Key<CachedValue<ConcurrentMap<Integer, VirtualFileEnumeration>>> CACHED_FILE_ID_ENUMERATIONS_KEY =
@@ -68,25 +67,14 @@ public final class ModuleWithDependenciesScope extends GlobalSearchScope impleme
6867

6968
private Object2IntMap<VirtualFile> calcRoots(@Nullable ModelBranch branch) {
7069
Set<VirtualFile> roots = new LinkedHashSet<>();
71-
if (hasOption(CONTENT)) {
72-
Set<Module> modules = calcModules();
73-
myModules = new HashSet<>(modules);
74-
for (Module m : modules) {
75-
for (ContentEntry entry : ModuleRootManager.getInstance(m).getContentEntries()) {
76-
ContainerUtil.addIfNotNull(roots, branch == null ? entry.getFile() : branch.findFileByUrl(entry.getUrl()));
77-
}
78-
}
79-
}
80-
else {
81-
OrderRootsEnumerator en = getOrderEnumeratorForOptions().roots(entry -> {
82-
if (entry instanceof ModuleOrderEntry || entry instanceof ModuleSourceOrderEntry) return OrderRootType.SOURCES;
83-
return OrderRootType.CLASSES;
84-
});
85-
if (branch == null) {
86-
Collections.addAll(roots, en.getRoots());
87-
} else {
88-
roots.addAll(ContainerUtil.mapNotNull(en.getUrls(), branch::findFileByUrl));
89-
}
70+
OrderRootsEnumerator en = getOrderEnumeratorForOptions().roots(entry -> {
71+
if (entry instanceof ModuleOrderEntry || entry instanceof ModuleSourceOrderEntry) return OrderRootType.SOURCES;
72+
return OrderRootType.CLASSES;
73+
});
74+
if (branch == null) {
75+
Collections.addAll(roots, en.getRoots());
76+
} else {
77+
roots.addAll(ContainerUtil.mapNotNull(en.getUrls(), branch::findFileByUrl));
9078
}
9179

9280
int i = 1;
@@ -109,9 +97,7 @@ private OrderEnumerator getOrderEnumeratorForOptions() {
10997

11098
@NotNull
11199
private Set<Module> calcModules() {
112-
// In the case that hasOption(CONTENT), the order of the modules set matters for
113-
// ordering the content roots, so use a LinkedHashSet
114-
Set<Module> modules = new LinkedHashSet<>();
100+
Set<Module> modules = new HashSet<>();
115101
OrderEnumerator en = getOrderEnumeratorForOptions();
116102
en.forEach(each -> {
117103
if (each instanceof ModuleOrderEntry) {
@@ -162,12 +148,8 @@ public boolean isSearchInLibraries() {
162148

163149
@Override
164150
public boolean contains(@NotNull VirtualFile file) {
165-
Object2IntMap<VirtualFile> roots = getRoots(file);
166-
if (hasOption(CONTENT)) {
167-
return roots.containsKey(myProjectFileIndex.getContentRootForFile(file));
168-
}
169151
VirtualFile root = myProjectFileIndex.getModuleSourceOrLibraryClassesRoot(file);
170-
return root != null && roots.containsKey(root);
152+
return root != null && getRoots(file).containsKey(root);
171153
}
172154

173155
private Object2IntMap<VirtualFile> getRoots(@NotNull VirtualFile file) {
@@ -188,8 +170,8 @@ private Object2IntMap<VirtualFile> obtainBranchRoots(ModelBranch branch) {
188170

189171
@Override
190172
public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) {
191-
VirtualFile r1 = getFileRoot(file1);
192-
VirtualFile r2 = getFileRoot(file2);
173+
VirtualFile r1 = myProjectFileIndex.getModuleSourceOrLibraryClassesRoot(file1);
174+
VirtualFile r2 = myProjectFileIndex.getModuleSourceOrLibraryClassesRoot(file2);
193175
if (Comparing.equal(r1, r2)) return 0;
194176

195177
if (r1 == null) return -1;
@@ -203,14 +185,6 @@ public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) {
203185
return i1 > 0 ? 1 : -1;
204186
}
205187

206-
@Nullable
207-
private VirtualFile getFileRoot(@NotNull VirtualFile file) {
208-
if (hasOption(CONTENT)) {
209-
return myProjectFileIndex.getContentRootForFile(file);
210-
}
211-
return myProjectFileIndex.getModuleSourceOrLibraryClassesRoot(file);
212-
}
213-
214188
@TestOnly
215189
public Collection<VirtualFile> getRoots() {
216190
List<VirtualFile> result = new ArrayList<>(myRoots.keySet());

platform/platform-tests/testSrc/com/intellij/psi/search/GlobalSearchScopeTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ public void testUnionWithEmptyScopeMustNotAffectCompare() {
137137
assertNotNull(moduleRoot2);
138138
PsiTestUtil.addSourceRoot(getModule(), moduleRoot2);
139139

140-
GlobalSearchScope modScope = getModule().getModuleContentScope();
140+
GlobalSearchScope modScope = getModule().getModuleScope();
141141
int compare = modScope.compare(moduleRoot, moduleRoot2);
142142
assertTrue(compare != 0);
143143
GlobalSearchScope union = modScope.uniteWith(GlobalSearchScope.EMPTY_SCOPE);

platform/platform-tests/testSrc/com/intellij/workspaceModel/core/fileIndex/CustomContentFileSetTest.kt

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@ package com.intellij.workspaceModel.core.fileIndex
33

44
import com.intellij.openapi.Disposable
55
import com.intellij.openapi.application.readAction
6+
import com.intellij.openapi.module.Module
7+
import com.intellij.openapi.roots.ProjectFileIndex
68
import com.intellij.openapi.roots.impl.assertIteratedContent
79
import com.intellij.openapi.vfs.VirtualFile
810
import com.intellij.testFramework.junit5.TestApplication
911
import com.intellij.testFramework.junit5.TestDisposable
1012
import com.intellij.testFramework.rules.ProjectModelExtension
1113
import com.intellij.testFramework.workspaceModel.updateProjectModelAsync
1214
import com.intellij.util.indexing.testEntities.IndexingTestEntity
15+
import com.intellij.workspaceModel.core.fileIndex.impl.ModuleRelatedRootData
1316
import com.intellij.workspaceModel.core.fileIndex.impl.WorkspaceFileIndexImpl
1417
import com.intellij.workspaceModel.ide.NonPersistentEntitySource
1518
import com.intellij.workspaceModel.ide.WorkspaceModel
@@ -18,11 +21,10 @@ import com.intellij.workspaceModel.ide.toVirtualFileUrl
1821
import com.intellij.workspaceModel.storage.EntityStorage
1922
import com.intellij.workspaceModel.storage.url.VirtualFileUrlManager
2023
import kotlinx.coroutines.runBlocking
24+
import org.junit.jupiter.api.Assertions.*
2125
import org.junit.jupiter.api.BeforeEach
2226
import org.junit.jupiter.api.Test
2327
import org.junit.jupiter.api.extension.RegisterExtension
24-
import kotlin.test.assertFalse
25-
import kotlin.test.assertTrue
2628

2729
@TestApplication
2830
class CustomContentFileSetTest {
@@ -33,14 +35,19 @@ class CustomContentFileSetTest {
3335
private val fileIndex
3436
get() = WorkspaceFileIndex.getInstance(projectModel.project)
3537

38+
private val projectFileIndex
39+
get() = ProjectFileIndex.getInstance(projectModel.project)
40+
3641
@TestDisposable
3742
private lateinit var disposable: Disposable
3843
private lateinit var customContentFileSetRoot: VirtualFile
44+
private lateinit var module: Module
3945

4046
@BeforeEach
4147
fun setUp() {
4248
customContentFileSetRoot = projectModel.baseProjectDir.newVirtualDirectory("root")
43-
WorkspaceFileIndexImpl.EP_NAME.point.registerExtension(CustomContentFileSetContributor(), disposable)
49+
module = projectModel.createModule()
50+
WorkspaceFileIndexImpl.EP_NAME.point.registerExtension(CustomContentFileSetContributor(module), disposable)
4451
}
4552

4653
@Test
@@ -59,6 +66,9 @@ class CustomContentFileSetTest {
5966

6067
readAction {
6168
assertTrue(fileIndex.isInContent(file))
69+
assertEquals(module, projectFileIndex.getModuleForFile(file))
70+
assertTrue(module.moduleContentScope.contains(file))
71+
assertFalse(module.moduleScope.contains(file))
6272
assertIteratedContent(projectModel.project, mustContain = listOf(root, file))
6373
}
6474

@@ -68,18 +78,22 @@ class CustomContentFileSetTest {
6878

6979
readAction {
7080
assertFalse(fileIndex.isInContent(root))
81+
assertNull(projectFileIndex.getModuleForFile(file))
82+
assertFalse(module.moduleContentScope.contains(file))
7183
assertIteratedContent(projectModel.project, mustNotContain = listOf(root, file))
7284
}
7385
}
7486

75-
private class CustomContentFileSetContributor : WorkspaceFileIndexContributor<IndexingTestEntity> {
87+
private class CustomContentFileSetContributor(private val module: Module) : WorkspaceFileIndexContributor<IndexingTestEntity> {
7688
override val entityClass: Class<IndexingTestEntity>
7789
get() = IndexingTestEntity::class.java
7890

7991
override fun registerFileSets(entity: IndexingTestEntity, registrar: WorkspaceFileSetRegistrar, storage: EntityStorage) {
8092
for (root in entity.roots) {
81-
registrar.registerFileSet(root, WorkspaceFileKind.CONTENT, entity, null)
93+
registrar.registerFileSet(root, WorkspaceFileKind.CONTENT, entity, CustomModuleRelatedRootData(module))
8294
}
8395
}
8496
}
97+
98+
private class CustomModuleRelatedRootData(override val module: Module): ModuleRelatedRootData
8599
}

0 commit comments

Comments
 (0)