[qodana] QD-8459 Wait for initialization of dynamic inspections in qodana

- ProjectInspectionToolRegistrar#dynamicInspectionsFlow: initial value is `null`, when is not `null` it's considered initialized
- use `ProjectInspectionToolRegistrar` where it's missing
- set category FlexInspect for .inspection.kts, include this category to bundled profiles
- introduce Qodana project level inspection registrar since now there is platform one

GitOrigin-RevId: 1db1cd6aff95406f7f7dd5558187250a38842492
This commit is contained in:
Mikhail Shagvaliev
2024-02-26 10:53:05 +01:00
committed by intellij-monorepo-bot
parent 9476a90553
commit afbbf30d42
5 changed files with 76 additions and 57 deletions

View File

@@ -5,49 +5,66 @@ import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx
import com.intellij.openapi.components.Service import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch import org.jetbrains.annotations.ApiStatus
@Service(Service.Level.PROJECT) @Service(Service.Level.PROJECT)
class ProjectInspectionToolRegistrar(private val project: Project, scope: CoroutineScope) : InspectionToolsSupplier() { @ApiStatus.Internal
class ProjectInspectionToolRegistrar(project: Project, scope: CoroutineScope) : InspectionToolsSupplier() {
companion object { companion object {
@JvmStatic
fun getInstance(project: Project): ProjectInspectionToolRegistrar = project.service() fun getInstance(project: Project): ProjectInspectionToolRegistrar = project.service()
} }
private val dynamicInspectionsFlow: StateFlow<Set<DynamicInspectionDescriptor>> = dynamicInspectionsFlow(project) private val dynamicInspectionsFlow: StateFlow<Set<DynamicInspectionDescriptor>?> = dynamicInspectionsFlow(project)
.flowOn(Dispatchers.Default) .flowOn(Dispatchers.Default)
.stateIn(scope, SharingStarted.Eagerly, initialValue = emptySet()) .stateIn(scope, SharingStarted.Lazily, initialValue = null)
private val dynamicInspectionsWereInitialized = Job()
private val updateInspectionProfilesSubscription = scope.launch(Dispatchers.Default, start = CoroutineStart.LAZY) {
var oldInspections = emptySet<DynamicInspectionDescriptor>()
dynamicInspectionsFlow
.filterNotNull()
.collect { currentInspections ->
try {
if (oldInspections == currentInspections) return@collect
val newInspections = currentInspections - oldInspections
val outdatedInspections = oldInspections - currentInspections
listeners.forEach { listener ->
outdatedInspections.forEach {
listener.toolRemoved(it.toolWrapper)
}
}
listeners.forEach { listener ->
newInspections.forEach {
listener.toolAdded(it.toolWrapper)
}
}
oldInspections = currentInspections
DaemonCodeAnalyzerEx.getInstance(project).restart()
}
finally {
dynamicInspectionsWereInitialized.complete()
}
}
}
init { init {
InspectionToolRegistrar.getInstance() InspectionToolRegistrar.getInstance()
scope.launch(Dispatchers.Default) {
var oldInspections = emptySet<DynamicInspectionDescriptor>()
dynamicInspectionsFlow.collectLatest { currentInspections ->
if (oldInspections == currentInspections) return@collectLatest
val newInspections = currentInspections - oldInspections
val outdatedInspections = oldInspections - currentInspections
listeners.forEach { listener ->
outdatedInspections.forEach {
listener.toolRemoved(it.toolWrapper)
}
}
listeners.forEach { listener ->
newInspections.forEach {
listener.toolAdded(it.toolWrapper)
}
}
oldInspections = currentInspections
DaemonCodeAnalyzerEx.getInstance(project).restart()
}
}
} }
override fun createTools(): MutableList<InspectionToolWrapper<*, *>> { suspend fun waitForDynamicInspectionsInitialization() {
return (InspectionToolRegistrar.getInstance().createTools() + dynamicInspectionsFlow.value.map { it.toolWrapper }).toMutableList() updateInspectionProfilesSubscription.start()
dynamicInspectionsWereInitialized.join()
}
override fun createTools(): List<InspectionToolWrapper<*, *>> {
updateInspectionProfilesSubscription.start()
val dynamicTools = dynamicInspectionsFlow.value?.map { it.toolWrapper } ?: emptyList()
return InspectionToolRegistrar.getInstance().createTools() + dynamicTools
} }
} }

View File

@@ -14,13 +14,16 @@ import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.trySendBlocking import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import org.jetbrains.annotations.ApiStatus
private val EP_NAME = ExtensionPointName<DynamicInspectionsProvider>("com.intellij.dynamicInspectionsProvider") private val EP_NAME = ExtensionPointName<DynamicInspectionsProvider>("com.intellij.dynamicInspectionsProvider")
@ApiStatus.Internal
interface DynamicInspectionsProvider { interface DynamicInspectionsProvider {
fun inspectionsFlow(project: Project): Flow<Set<DynamicInspectionDescriptor>> fun inspections(project: Project): Flow<Set<DynamicInspectionDescriptor>>
} }
@ApiStatus.Internal
sealed class DynamicInspectionDescriptor { sealed class DynamicInspectionDescriptor {
companion object { companion object {
fun fromTool(tool: InspectionProfileEntry): DynamicInspectionDescriptor { fun fromTool(tool: InspectionProfileEntry): DynamicInspectionDescriptor {
@@ -34,30 +37,27 @@ sealed class DynamicInspectionDescriptor {
val toolWrapper: InspectionToolWrapper<*, *> by lazy { val toolWrapper: InspectionToolWrapper<*, *> by lazy {
when(this) { when(this) {
is Global -> object : GlobalInspectionToolWrapper(tool) { is Local -> DynamicLocalInspectionToolWrapper(tool)
override fun createCopy(): GlobalInspectionToolWrapper { is Global -> DynamicGlobalInspectionToolWrapper(tool)
return GlobalInspectionToolWrapper(tool)
}
}
is Local -> object : LocalInspectionToolWrapper(tool) {
override fun createCopy(): LocalInspectionToolWrapper {
return LocalInspectionToolWrapper(tool)
}
}
} }
} }
class Local(val tool: LocalInspectionTool) : DynamicInspectionDescriptor()
class Global(val tool: GlobalInspectionTool) : DynamicInspectionDescriptor() class Global(val tool: GlobalInspectionTool) : DynamicInspectionDescriptor()
private class DynamicLocalInspectionToolWrapper(tool: LocalInspectionTool) : LocalInspectionToolWrapper(tool) {
override fun createCopy(): LocalInspectionToolWrapper = DynamicLocalInspectionToolWrapper(tool)
}
class Local(val tool: LocalInspectionTool) : DynamicInspectionDescriptor() private class DynamicGlobalInspectionToolWrapper(tool: GlobalInspectionTool) : GlobalInspectionToolWrapper(tool) {
override fun createCopy(): GlobalInspectionToolWrapper = DynamicGlobalInspectionToolWrapper(tool)
}
} }
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
internal fun dynamicInspectionsFlow(project: Project): Flow<Set<DynamicInspectionDescriptor>> { internal fun dynamicInspectionsFlow(project: Project): Flow<Set<DynamicInspectionDescriptor>> {
val epUpdatedFlow = callbackFlow { val epUpdatedFlow = callbackFlow {
trySendBlocking(Unit)
val disposable = Disposer.newDisposable() val disposable = Disposer.newDisposable()
val listener = object : ExtensionPointListener<DynamicInspectionsProvider> { val listener = object : ExtensionPointListener<DynamicInspectionsProvider> {
override fun extensionAdded(extension: DynamicInspectionsProvider, pluginDescriptor: PluginDescriptor) { override fun extensionAdded(extension: DynamicInspectionsProvider, pluginDescriptor: PluginDescriptor) {
@@ -73,14 +73,17 @@ internal fun dynamicInspectionsFlow(project: Project): Flow<Set<DynamicInspectio
}.buffer(capacity = 1, onBufferOverflow = BufferOverflow.DROP_LATEST) }.buffer(capacity = 1, onBufferOverflow = BufferOverflow.DROP_LATEST)
return epUpdatedFlow return epUpdatedFlow
.onStart { emit(Unit) }
.flatMapLatest { .flatMapLatest {
val allDynamicCustomInspectionsFlows: List<Flow<Set<DynamicInspectionDescriptor>>> = EP_NAME.extensionList.map { provider -> val allDynamicInspectionsFlows: List<Flow<Set<DynamicInspectionDescriptor>>> = EP_NAME.extensionList.map { provider ->
provider.inspectionsFlow(project) // if returned flow is simply empty, do not block collection of others in combine
.map { inspections -> inspections.toSet() } provider.inspections(project).onEmpty { emit(emptySet()) }
.onStart { emit(emptySet()) }
} }
combine(allDynamicCustomInspectionsFlows) { combine(allDynamicInspectionsFlows) {
it.toList().flatten().toSet() it.toList().flatten().toSet()
}.onEmpty {
// if there are no EP impls, emit the empty set
emit(emptySet())
} }
}.distinctUntilChanged() }.distinctUntilChanged()
} }

View File

@@ -29,7 +29,7 @@ public final class InspectionProfileLoadUtil {
} }
public static @NotNull InspectionProfileImpl load(@NotNull Path file, public static @NotNull InspectionProfileImpl load(@NotNull Path file,
@NotNull InspectionToolRegistrar registrar, @NotNull InspectionToolsSupplier registrar,
@NotNull InspectionProfileManager profileManager) throws JDOMException, IOException { @NotNull InspectionProfileManager profileManager) throws JDOMException, IOException {
Element element = JDOMUtil.load(file); Element element = JDOMUtil.load(file);
String profileName = getProfileName(file, element); String profileName = getProfileName(file, element);

View File

@@ -116,7 +116,7 @@ class YamlInspectionProfileImpl private constructor(override val profileName: St
@JvmStatic @JvmStatic
fun loadFrom(project: Project, fun loadFrom(project: Project,
filePath: String = "${getDefaultProfileDirectory(project)}/profile.yaml", filePath: String = "${getDefaultProfileDirectory(project)}/profile.yaml",
toolsSupplier: InspectionToolsSupplier = InspectionToolRegistrar.getInstance(), toolsSupplier: InspectionToolsSupplier = ProjectInspectionToolRegistrar.getInstance(project),
profileManager: BaseInspectionProfileManager = ProjectInspectionProfileManager.getInstance(project) profileManager: BaseInspectionProfileManager = ProjectInspectionProfileManager.getInstance(project)
): YamlInspectionProfileImpl { ): YamlInspectionProfileImpl {
val configFile = File(filePath).absoluteFile val configFile = File(filePath).absoluteFile

View File

@@ -8,9 +8,7 @@ import com.intellij.application.options.schemes.AbstractSchemeActions;
import com.intellij.application.options.schemes.DescriptionAwareSchemeActions; import com.intellij.application.options.schemes.DescriptionAwareSchemeActions;
import com.intellij.codeInsight.daemon.impl.HighlightInfoType; import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
import com.intellij.codeInsight.daemon.impl.SeverityRegistrar; import com.intellij.codeInsight.daemon.impl.SeverityRegistrar;
import com.intellij.codeInspection.ex.InspectionProfileImpl; import com.intellij.codeInspection.ex.*;
import com.intellij.codeInspection.ex.InspectionProfileModifiableModel;
import com.intellij.codeInspection.ex.InspectionToolRegistrar;
import com.intellij.lang.LangBundle; import com.intellij.lang.LangBundle;
import com.intellij.lang.annotation.HighlightSeverity; import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ApplicationManager;
@@ -253,8 +251,9 @@ public final class InspectionProfileSchemesPanel extends AbstractDescriptionAwar
final boolean isProjectLevel = selectedProfile.isProjectLevel() ^ modifyLevel; final boolean isProjectLevel = selectedProfile.isProjectLevel() ^ modifyLevel;
BaseInspectionProfileManager profileManager = isProjectLevel ? myProjectProfileManager : myAppProfileManager; BaseInspectionProfileManager profileManager = isProjectLevel ? myProjectProfileManager : myAppProfileManager;
InspectionProfileImpl inspectionProfile = InspectionToolsSupplier inspectionsRegistrar = isProjectLevel ? ProjectInspectionToolRegistrar.getInstance(project)
new InspectionProfileImpl(newName, InspectionToolRegistrar.getInstance(), profileManager); : InspectionToolRegistrar.getInstance();
InspectionProfileImpl inspectionProfile = new InspectionProfileImpl(newName, inspectionsRegistrar, profileManager);
inspectionProfile.copyFrom(selectedProfile); inspectionProfile.copyFrom(selectedProfile);
inspectionProfile.setName(newName); inspectionProfile.setName(newName);