From cad67986c38f13bbfacfacd731e24245da2ee886 Mon Sep 17 00:00:00 2001 From: Ilia Malakhov Date: Wed, 12 Aug 2026 09:54:44 +0200 Subject: [PATCH] IJPL-252666 fix module indexable iterators not created if CONTENT root was under CONTENT_NON_INDEXABLE from the same module. GitOrigin-RevId: 9bd6b465ae3d6442480f670abc8213eebf9fe965 --- .../roots/IndexingIteratorsProviderImpl.kt | 72 ++++++++++- .../roots/IndexingRootsCollectionUtil.kt | 57 --------- .../IndexingIteratorsProviderImplTest.kt | 116 ++++++++++++++++++ 3 files changed, 187 insertions(+), 58 deletions(-) create mode 100644 platform/lang-impl/testSources/com/intellij/util/indexing/roots/IndexingIteratorsProviderImplTest.kt diff --git a/platform/lang-impl/src/com/intellij/util/indexing/roots/IndexingIteratorsProviderImpl.kt b/platform/lang-impl/src/com/intellij/util/indexing/roots/IndexingIteratorsProviderImpl.kt index 166345a99d23..4161c5b5cb2f 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/roots/IndexingIteratorsProviderImpl.kt +++ b/platform/lang-impl/src/com/intellij/util/indexing/roots/IndexingIteratorsProviderImpl.kt @@ -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 } -} \ No newline at end of file +} + +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 { + return processLibrary(entity.name, fileSet) +} + +internal fun processLibrary(libraryName: String, fileSet: WorkspaceFileSet): Pair { + 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 +} diff --git a/platform/lang-impl/src/com/intellij/util/indexing/roots/IndexingRootsCollectionUtil.kt b/platform/lang-impl/src/com/intellij/util/indexing/roots/IndexingRootsCollectionUtil.kt index 1e3f36b41041..1388bcfdd353 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/roots/IndexingRootsCollectionUtil.kt +++ b/platform/lang-impl/src/com/intellij/util/indexing/roots/IndexingRootsCollectionUtil.kt @@ -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 { } } -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 { - 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(contributor: WorkspaceFileIndexContributor, ignoreModuleRoots: Boolean) : WorkspaceFileSetRegistrar { diff --git a/platform/lang-impl/testSources/com/intellij/util/indexing/roots/IndexingIteratorsProviderImplTest.kt b/platform/lang-impl/testSources/com/intellij/util/indexing/roots/IndexingIteratorsProviderImplTest.kt new file mode 100644 index 000000000000..7e4830ca178f --- /dev/null +++ b/platform/lang-impl/testSources/com/intellij/util/indexing/roots/IndexingIteratorsProviderImplTest.kt @@ -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() + 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() + 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 { + override val entityClass: Class = 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 { + override val entityClass: Class = 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 +}