[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.project.Project
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import org.jetbrains.annotations.ApiStatus
@Service(Service.Level.PROJECT)
class ProjectInspectionToolRegistrar(private val project: Project, scope: CoroutineScope) : InspectionToolsSupplier() {
@ApiStatus.Internal
class ProjectInspectionToolRegistrar(project: Project, scope: CoroutineScope) : InspectionToolsSupplier() {
companion object {
@JvmStatic
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)
.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 {
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<*, *>> {
return (InspectionToolRegistrar.getInstance().createTools() + dynamicInspectionsFlow.value.map { it.toolWrapper }).toMutableList()
suspend fun waitForDynamicInspectionsInitialization() {
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.trySendBlocking
import kotlinx.coroutines.flow.*
import org.jetbrains.annotations.ApiStatus
private val EP_NAME = ExtensionPointName<DynamicInspectionsProvider>("com.intellij.dynamicInspectionsProvider")
@ApiStatus.Internal
interface DynamicInspectionsProvider {
fun inspectionsFlow(project: Project): Flow<Set<DynamicInspectionDescriptor>>
fun inspections(project: Project): Flow<Set<DynamicInspectionDescriptor>>
}
@ApiStatus.Internal
sealed class DynamicInspectionDescriptor {
companion object {
fun fromTool(tool: InspectionProfileEntry): DynamicInspectionDescriptor {
@@ -34,30 +37,27 @@ sealed class DynamicInspectionDescriptor {
val toolWrapper: InspectionToolWrapper<*, *> by lazy {
when(this) {
is Global -> object : GlobalInspectionToolWrapper(tool) {
override fun createCopy(): GlobalInspectionToolWrapper {
return GlobalInspectionToolWrapper(tool)
}
}
is Local -> object : LocalInspectionToolWrapper(tool) {
override fun createCopy(): LocalInspectionToolWrapper {
return LocalInspectionToolWrapper(tool)
}
}
is Local -> DynamicLocalInspectionToolWrapper(tool)
is Global -> DynamicGlobalInspectionToolWrapper(tool)
}
}
class Local(val tool: LocalInspectionTool) : 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)
internal fun dynamicInspectionsFlow(project: Project): Flow<Set<DynamicInspectionDescriptor>> {
val epUpdatedFlow = callbackFlow {
trySendBlocking(Unit)
val disposable = Disposer.newDisposable()
val listener = object : ExtensionPointListener<DynamicInspectionsProvider> {
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)
return epUpdatedFlow
.onStart { emit(Unit) }
.flatMapLatest {
val allDynamicCustomInspectionsFlows: List<Flow<Set<DynamicInspectionDescriptor>>> = EP_NAME.extensionList.map { provider ->
provider.inspectionsFlow(project)
.map { inspections -> inspections.toSet() }
.onStart { emit(emptySet()) }
val allDynamicInspectionsFlows: List<Flow<Set<DynamicInspectionDescriptor>>> = EP_NAME.extensionList.map { provider ->
// if returned flow is simply empty, do not block collection of others in combine
provider.inspections(project).onEmpty { emit(emptySet()) }
}
combine(allDynamicCustomInspectionsFlows) {
combine(allDynamicInspectionsFlows) {
it.toList().flatten().toSet()
}.onEmpty {
// if there are no EP impls, emit the empty set
emit(emptySet())
}
}.distinctUntilChanged()
}

View File

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

View File

@@ -116,7 +116,7 @@ class YamlInspectionProfileImpl private constructor(override val profileName: St
@JvmStatic
fun loadFrom(project: Project,
filePath: String = "${getDefaultProfileDirectory(project)}/profile.yaml",
toolsSupplier: InspectionToolsSupplier = InspectionToolRegistrar.getInstance(),
toolsSupplier: InspectionToolsSupplier = ProjectInspectionToolRegistrar.getInstance(project),
profileManager: BaseInspectionProfileManager = ProjectInspectionProfileManager.getInstance(project)
): YamlInspectionProfileImpl {
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.codeInsight.daemon.impl.HighlightInfoType;
import com.intellij.codeInsight.daemon.impl.SeverityRegistrar;
import com.intellij.codeInspection.ex.InspectionProfileImpl;
import com.intellij.codeInspection.ex.InspectionProfileModifiableModel;
import com.intellij.codeInspection.ex.InspectionToolRegistrar;
import com.intellij.codeInspection.ex.*;
import com.intellij.lang.LangBundle;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.openapi.application.ApplicationManager;
@@ -253,8 +251,9 @@ public final class InspectionProfileSchemesPanel extends AbstractDescriptionAwar
final boolean isProjectLevel = selectedProfile.isProjectLevel() ^ modifyLevel;
BaseInspectionProfileManager profileManager = isProjectLevel ? myProjectProfileManager : myAppProfileManager;
InspectionProfileImpl inspectionProfile =
new InspectionProfileImpl(newName, InspectionToolRegistrar.getInstance(), profileManager);
InspectionToolsSupplier inspectionsRegistrar = isProjectLevel ? ProjectInspectionToolRegistrar.getInstance(project)
: InspectionToolRegistrar.getInstance();
InspectionProfileImpl inspectionProfile = new InspectionProfileImpl(newName, inspectionsRegistrar, profileManager);
inspectionProfile.copyFrom(selectedProfile);
inspectionProfile.setName(newName);