IJPL-252666 fix module indexable iterators not created

if CONTENT root was under CONTENT_NON_INDEXABLE from the same module.

GitOrigin-RevId: 9bd6b465ae3d6442480f670abc8213eebf9fe965
This commit is contained in:
Ilia Malakhov
2026-08-17 09:13:24 +00:00
committed by intellij-monorepo-bot
parent 32960aeff5
commit cad67986c3
3 changed files with 187 additions and 58 deletions
@@ -6,6 +6,7 @@ import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.SdkType
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.module.Module
import com.intellij.platform.backend.workspace.WorkspaceModel
import com.intellij.platform.workspace.jps.entities.LibraryEntity
import com.intellij.platform.workspace.jps.entities.ModuleEntity
@@ -18,8 +19,10 @@ import com.intellij.util.indexing.dependenciesCache.DependenciesIndexedStatusSer
import com.intellij.util.indexing.roots.kind.LibraryOrigin
import com.intellij.util.indexing.roots.origin.IndexingRootHolder
import com.intellij.util.indexing.roots.origin.IndexingSourceRootHolder
import com.intellij.util.indexing.roots.origin.LibraryOriginImpl
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileIndex
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileKind
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileSet
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileSetWithCustomData
import com.intellij.workspaceModel.core.fileIndex.impl.ModuleRelatedRootData
import com.intellij.workspaceModel.core.fileIndex.impl.WorkspaceFileIndexEx
@@ -131,4 +134,71 @@ class IndexingIteratorsProviderImpl(
}
return iterators
}
}
}
internal fun processModuleRoot(
fileSet: WorkspaceFileSetWithCustomData<*>,
project: Project,
includeNestedRoots: Boolean = false,
): IndexableFilesIterator? {
val customData = fileSet.data
val root = fileSet.root
customData as ModuleRelatedRootData
return if (!includeNestedRoots && isNestedRootOfModuleIndexableContent(root, customData.module, WorkspaceFileIndexEx.getInstance(project))) {
null
}
else {
ModuleFilesIteratorImpl(customData.module, root, fileSet.recursive, true)
}
}
internal fun processLibraryEntity(entity: LibraryEntity, fileSet: WorkspaceFileSet): Pair<LibraryOrigin, IndexableFilesIterator> {
return processLibrary(entity.name, fileSet)
}
internal fun processLibrary(libraryName: String, fileSet: WorkspaceFileSet): Pair<LibraryOrigin, IndexableFilesIterator> {
val sourceRoot = fileSet.kind == WorkspaceFileKind.EXTERNAL_SOURCE
val origin = if (sourceRoot) {
LibraryOriginImpl(emptyList(), listOf(fileSet.root))
}
else {
LibraryOriginImpl(listOf(fileSet.root), emptyList())
}
val iterator = GenericDependencyIterator.forLibraryEntity(origin, libraryName, fileSet.root, sourceRoot)
return origin to iterator
}
private fun isNestedRootOfModuleIndexableContent(
root: VirtualFile,
module: Module,
workspaceFileIndex: WorkspaceFileIndexEx,
): Boolean {
val parent = root.getParent()
if (parent == null) {
return false
}
val fileInfo = workspaceFileIndex.getFileInfo(
parent,
honorExclusion = false,
includeContentSets = true,
includeContentNonIndexableSets = false,
includeExternalSets = false,
includeExternalSourceSets = false,
includeExternalNonIndexableSets = false,
includeCustomKindSets = false
)
return fileInfo.findFileSet { fileSet -> hasRecursiveRootFromModuleContent(fileSet, module) } != null
}
private fun hasRecursiveRootFromModuleContent(
fileSet: WorkspaceFileSetWithCustomData<*>,
module: Module,
): Boolean {
return fileSet.recursive && isInContent(fileSet, module)
}
private fun isInContent(fileSet: WorkspaceFileSetWithCustomData<*>, module: Module): Boolean {
val data = fileSet.data
return data is ModuleRelatedRootData && module == data.module
}
@@ -7,33 +7,26 @@ import com.intellij.openapi.extensions.forEachExtensionSafeInline
import com.intellij.openapi.module.Module
import com.intellij.openapi.progress.Cancellation
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Condition
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.io.OSAgnosticPathUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.platform.backend.workspace.virtualFile
import com.intellij.platform.workspace.jps.entities.LibraryEntity
import com.intellij.platform.workspace.storage.EntityPointer
import com.intellij.platform.workspace.storage.EntityStorage
import com.intellij.platform.workspace.storage.WorkspaceEntity
import com.intellij.platform.workspace.storage.url.VirtualFileUrl
import com.intellij.util.indexing.roots.kind.LibraryOrigin
import com.intellij.util.indexing.roots.origin.IndexingUrlRootHolder
import com.intellij.util.indexing.roots.origin.IndexingUrlSourceRootHolder
import com.intellij.util.indexing.roots.origin.LibraryOriginImpl
import com.intellij.util.indexing.roots.origin.MutableIndexingUrlRootHolder
import com.intellij.util.indexing.roots.origin.MutableIndexingUrlSourceRootHolder
import com.intellij.workspaceModel.core.fileIndex.EntityStorageKind
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileIndexContributor
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileKind
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileSet
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileSetData
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileSetExclusionCondition
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileSetRegistrar
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileSetWithCustomData
import com.intellij.workspaceModel.core.fileIndex.impl.ModuleRelatedRootData
import com.intellij.workspaceModel.core.fileIndex.impl.WorkspaceFileIndexEx
import com.intellij.workspaceModel.core.fileIndex.impl.WorkspaceFileIndexImpl
import java.util.NavigableMap
import java.util.TreeMap
@@ -252,56 +245,6 @@ private class RootData<E : WorkspaceEntity> {
}
}
internal fun processModuleRoot(fileSet: WorkspaceFileSetWithCustomData<*>, project: Project, includeNestedRoots: Boolean = false): IndexableFilesIterator? {
val customData = fileSet.data
val root = fileSet.root
customData as ModuleRelatedRootData
return if (!includeNestedRoots && isNestedRootOfModuleContent(root, customData.module, WorkspaceFileIndexEx.getInstance(project))) {
null
} else {
ModuleFilesIteratorImpl(customData.module, root, fileSet.recursive, true)
}
}
internal fun processLibraryEntity(entity: LibraryEntity, fileSet: WorkspaceFileSet): Pair<LibraryOrigin, IndexableFilesIterator> {
val sourceRoot = fileSet.kind == WorkspaceFileKind.EXTERNAL_SOURCE
val origin = if (sourceRoot) {
LibraryOriginImpl(emptyList(), listOf(fileSet.root))
}
else {
LibraryOriginImpl(listOf(fileSet.root), emptyList())
}
val iterator = GenericDependencyIterator.forLibraryEntity(origin, entity.name, fileSet.root, sourceRoot)
return origin to iterator
}
private fun isNestedRootOfModuleContent(root: VirtualFile, module: Module, workspaceFileIndex: WorkspaceFileIndexEx): Boolean {
val parent = root.getParent()
if (parent == null) {
return false
}
val fileInfo = workspaceFileIndex.getFileInfo(
parent,
honorExclusion = false,
includeContentSets = true,
includeContentNonIndexableSets = true,
includeExternalSets = false,
includeExternalSourceSets = false,
includeExternalNonIndexableSets = false,
includeCustomKindSets = false
)
return fileInfo.findFileSet { fileSet -> hasRecursiveRootFromModuleContent(fileSet, module) } != null
}
private fun hasRecursiveRootFromModuleContent(fileSet: WorkspaceFileSetWithCustomData<*>, module: Module): Boolean {
return fileSet.recursive && isInContent(fileSet, module)
}
private fun isInContent(fileSet: WorkspaceFileSetWithCustomData<*>, module: Module): Boolean {
val data = fileSet.data
return data is ModuleRelatedRootData && module == data.module
}
private class MyWorkspaceFileSetRegistrar<E : WorkspaceEntity>(contributor: WorkspaceFileIndexContributor<E>,
ignoreModuleRoots: Boolean) : WorkspaceFileSetRegistrar {
@@ -0,0 +1,116 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by a license that can be found in the LICENSE file.
package com.intellij.util.indexing.roots
import com.intellij.openapi.application.readAction
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ContentIterator
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileFilter
import com.intellij.platform.backend.workspace.toVirtualFileUrl
import com.intellij.platform.backend.workspace.workspaceModel
import com.intellij.platform.workspace.storage.EntityStorage
import com.intellij.platform.workspace.storage.url.VirtualFileUrl
import com.intellij.testFramework.common.timeoutRunBlocking
import com.intellij.testFramework.junit5.TestApplication
import com.intellij.testFramework.rules.ProjectModelExtension
import com.intellij.testFramework.workspaceModel.update
import com.intellij.util.containers.TreeNodeProcessingResult
import com.intellij.util.indexing.testEntities.IndexingTestEntity
import com.intellij.util.indexing.testEntities.NonIndexableTestEntity
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileIndexContributor
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileKind
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileSetRegistrar
import com.intellij.workspaceModel.core.fileIndex.impl.ModuleRelatedRootData
import com.intellij.workspaceModel.core.fileIndex.impl.WorkspaceFileIndexEx
import com.intellij.workspaceModel.core.fileIndex.impl.WorkspaceFileIndexImpl
import com.intellij.workspaceModel.ide.NonPersistentEntitySource
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.RegisterExtension
@TestApplication
internal class IndexingIteratorsProviderImplTest {
@RegisterExtension
private val projectModel = ProjectModelExtension()
private val project: Project get() = projectModel.project
private val workspaceModel get() = project.workspaceModel
private val disposable get() = projectModel.disposableRule.disposable
@Test
fun `iterates indexable content below non-indexable content`() = timeoutRunBlocking {
val project = projectModel.project
val module = projectModel.createModule()
val nonIndexableRoot = projectModel.baseProjectDir.newVirtualDirectory("non-indexable")
val nonIndexableFile = projectModel.baseProjectDir.newVirtualFile("non-indexable/non-indexable.txt")
val indexableRoot = projectModel.baseProjectDir.newVirtualDirectory("non-indexable/indexable")
val indexableFile = projectModel.baseProjectDir.newVirtualFile("non-indexable/indexable/indexable.txt")
WorkspaceFileIndexImpl.EP_NAME.point.registerExtension(ModuleNonIndexableFileSetContributor(module), disposable)
WorkspaceFileIndexImpl.EP_NAME.point.registerExtension(ModuleContentFileSetContributor(module), disposable)
workspaceModel.update {
it.addEntity(NonIndexableTestEntity(nonIndexableRoot.toVirtualFileUrl(), NonPersistentEntitySource))
it.addEntity(IndexingTestEntity(listOf(indexableRoot.toVirtualFileUrl()), emptyList(), NonPersistentEntitySource))
}
val indexableFiles = readAction {
val files = mutableSetOf<VirtualFile>()
val processed = WorkspaceFileIndexEx.getInstance(project).processIndexableContentUnderDirectory(
nonIndexableRoot,
{ file -> files.add(file); TreeNodeProcessingResult.CONTINUE },
VirtualFileFilter.ALL,
) { true }
assertTrue(processed)
assertTrue(nonIndexableFile !in files)
assertTrue(indexableRoot in files)
assertTrue(indexableFile in files)
files
}
val iteratedFiles = readAction {
val iterators = IndexingIteratorsProviderImpl(project).getIndexingIterators()
val files = mutableSetOf<VirtualFile>()
for (iterator in iterators) {
assertTrue(iterator.iterateFiles(project, ContentIterator { file -> files.add(file); true }, VirtualFileFilter.ALL))
}
files
}
assertTrue(iteratedFiles.containsAll(indexableFiles), "Missing indexable files: ${indexableFiles - iteratedFiles}")
}
private fun VirtualFile.toVirtualFileUrl(): VirtualFileUrl = toVirtualFileUrl(workspaceModel.getVirtualFileUrlManager())
private class ModuleNonIndexableFileSetContributor(private val module: Module) :
WorkspaceFileIndexContributor<NonIndexableTestEntity> {
override val entityClass: Class<NonIndexableTestEntity> = NonIndexableTestEntity::class.java
override fun registerFileSets(
entity: NonIndexableTestEntity,
registrar: WorkspaceFileSetRegistrar,
storage: EntityStorage,
) {
registrar.registerFileSet(entity.root, WorkspaceFileKind.CONTENT_NON_INDEXABLE, entity, ModuleRootData(module))
}
}
private class ModuleContentFileSetContributor(private val module: Module) :
WorkspaceFileIndexContributor<IndexingTestEntity> {
override val entityClass: Class<IndexingTestEntity> = IndexingTestEntity::class.java
override fun registerFileSets(
entity: IndexingTestEntity,
registrar: WorkspaceFileSetRegistrar,
storage: EntityStorage,
) {
for (root in entity.roots) {
registrar.registerFileSet(root, WorkspaceFileKind.CONTENT, entity, ModuleRootData(module))
}
}
}
private data class ModuleRootData(override val module: Module) : ModuleRelatedRootData
}