From 3b5c542ff519022baae75755b331ba096c8673de Mon Sep 17 00:00:00 2001 From: Kirill Bochkarev Date: Thu, 18 Dec 2025 02:45:24 +0100 Subject: [PATCH] IJPL-15941 Workspace Model: report codegen problems instead of throwing exceptions GitOrigin-RevId: e22ecff8580e73951d1672e709e2a004258f6e04 --- .../storage/generatedCodeCompatibility.kt | 2 +- .../metaModel/WorkspaceMetaModelBuilder.kt | 5 +- .../WorkspaceMetaModelProviderImpl.kt | 6 +- .../k2/src/metaModel/KaExtensions.kt | 12 ++-- .../metaModel/WorkspaceMetaModelBuilder.kt | 57 +++++++++------ .../WorkspaceMetaModelProviderImpl.kt | 30 ++++++-- .../src/codegen/writer/CodeWriter.kt | 12 +++- .../WorkspaceCodegenProblemsProvider.kt | 71 +++++++++++++------ .../src/metaModel/Exceptions.kt | 12 ++++ .../src/metaModel/Utils.kt | 14 ++-- .../metaModel/WorkspaceMetaModelProvider.kt | 2 +- .../testSrc/AbstractEntityCodeGenTest.kt | 6 +- 12 files changed, 153 insertions(+), 76 deletions(-) create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/Exceptions.kt diff --git a/platform/workspace/storage/src/com/intellij/platform/workspace/storage/generatedCodeCompatibility.kt b/platform/workspace/storage/src/com/intellij/platform/workspace/storage/generatedCodeCompatibility.kt index 7e4f1de54a00..448206503829 100644 --- a/platform/workspace/storage/src/com/intellij/platform/workspace/storage/generatedCodeCompatibility.kt +++ b/platform/workspace/storage/src/com/intellij/platform/workspace/storage/generatedCodeCompatibility.kt @@ -9,7 +9,7 @@ public object CodeGeneratorVersions { /** these constants are accessed from WorkspaceImplObsoleteInspection */ private const val API_VERSION_INTERNAL = 3 private const val IMPL_MAJOR_VERSION_INTERNAL = 7 - private const val IMPL_MINOR_VERSION_INTERNAL = 10 + private const val IMPL_MINOR_VERSION_INTERNAL = 11 @set:TestOnly public var API_VERSION: Int = API_VERSION_INTERNAL diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k1/src/metaModel/WorkspaceMetaModelBuilder.kt b/plugins/devkit/intellij.devkit.workspaceModel/k1/src/metaModel/WorkspaceMetaModelBuilder.kt index b0da35c84bd2..2ed219deef94 100644 --- a/plugins/devkit/intellij.devkit.workspaceModel/k1/src/metaModel/WorkspaceMetaModelBuilder.kt +++ b/plugins/devkit/intellij.devkit.workspaceModel/k1/src/metaModel/WorkspaceMetaModelBuilder.kt @@ -1,7 +1,7 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.devkit.workspaceModel.k1.metaModel -import com.intellij.devkit.workspaceModel.metaModel.IncorrectObjInterfaceException +import com.intellij.devkit.workspaceModel.metaModel.MetaModelBuilderException import com.intellij.devkit.workspaceModel.metaModel.WorkspaceEntityInheritsEntitySourceException import com.intellij.devkit.workspaceModel.metaModel.WorkspaceEntityMultipleInheritanceException import com.intellij.devkit.workspaceModel.metaModel.WorkspaceModelDefaults @@ -12,6 +12,7 @@ import com.intellij.openapi.project.Project import com.intellij.psi.JavaPsiFacade import com.intellij.psi.search.GlobalSearchScope import com.intellij.workspaceModel.codegen.deft.meta.* +import org.jetbrains.kotlin.backend.jvm.ir.psiElement import org.jetbrains.kotlin.caches.resolve.KotlinCacheService import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.idea.base.projectStructure.productionSourceInfo @@ -259,7 +260,7 @@ internal class WorkspaceMetaModelBuilder( } classDescriptor.isAbstractClassOrInterface -> { if (!processAbstractTypes) { - throw IncorrectObjInterfaceException("$javaClassFqn is abstract type. Abstract types are not supported in generator") + throw MetaModelBuilderException("$javaClassFqn is abstract type. Abstract types are not supported in generator", classDescriptor.psiElement) } val inheritors = classDescriptor.inheritors(javaPsiFacade, allScope) .filter { !it.isEntitySource || ( it.packageName == compiledObjModule.name) } // && it.module == moduleDescriptor) } diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k1/src/metaModel/WorkspaceMetaModelProviderImpl.kt b/plugins/devkit/intellij.devkit.workspaceModel/k1/src/metaModel/WorkspaceMetaModelProviderImpl.kt index d60541d8901f..38f704f55a49 100644 --- a/plugins/devkit/intellij.devkit.workspaceModel/k1/src/metaModel/WorkspaceMetaModelProviderImpl.kt +++ b/plugins/devkit/intellij.devkit.workspaceModel/k1/src/metaModel/WorkspaceMetaModelProviderImpl.kt @@ -1,6 +1,7 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.devkit.workspaceModel.k1.metaModel +import com.intellij.devkit.workspaceModel.metaModel.MetaProblem import com.intellij.devkit.workspaceModel.metaModel.WorkspaceMetaModelProvider import com.intellij.openapi.module.Module import com.intellij.workspaceModel.codegen.deft.meta.CompiledObjModule @@ -13,13 +14,14 @@ internal class WorkspaceMetaModelProviderImpl : WorkspaceMetaModelProvider { module: Module, processAbstractTypes: Boolean, isTestSourceFolder: Boolean, - ): List { + ): Pair, List> { val packages = ktClasses.values.mapTo(LinkedHashSet()) { it.containingKtFile.packageFqName.asString() } val metaModelProvider = WorkspaceMetaModelBuilder( processAbstractTypes = processAbstractTypes, module.project ) - return packages.filter { it != "" }.map { metaModelProvider.getObjModule(it, module, isTestSourceFolder) } + val compiledObjModules = packages.filter { it != "" }.map { metaModelProvider.getObjModule(it, module, isTestSourceFolder) } + return compiledObjModules to emptyList() } } diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k2/src/metaModel/KaExtensions.kt b/plugins/devkit/intellij.devkit.workspaceModel/k2/src/metaModel/KaExtensions.kt index 04713165bea9..3448ac0bd900 100644 --- a/plugins/devkit/intellij.devkit.workspaceModel/k2/src/metaModel/KaExtensions.kt +++ b/plugins/devkit/intellij.devkit.workspaceModel/k2/src/metaModel/KaExtensions.kt @@ -1,6 +1,7 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.devkit.workspaceModel.k2.metaModel +import com.intellij.devkit.workspaceModel.metaModel.MetaModelBuilderException import com.intellij.devkit.workspaceModel.metaModel.WorkspaceModelDefaults import com.intellij.devkit.workspaceModel.metaModel.impl.CompiledObjModuleImpl import com.intellij.devkit.workspaceModel.metaModel.impl.ObjAnnotationImpl @@ -39,11 +40,10 @@ internal fun KaSession.inheritors( // can be rewritten using org.jetbrains.kotlin.idea.searching.inheritors.KotlinSearchUtilKt.findAllInheritors val psiClass = javaPsiFacade.findClass(classSymbol.javaClassFqn, scope) ?: return emptyList() return ClassInheritorsSearch.search(psiClass, scope, true, true, false) - .asIterable() .filterNot { it.isAnonymous } .sortedBy { it.qualifiedName } // Sorting is needed for consistency in case of regeneration - .mapNotNull { - KotlinFullClassNameIndex[it.qualifiedName!!, it.project, scope].firstOrNull() + .mapNotNull { psiClass -> + psiClass.qualifiedName?.let { KotlinFullClassNameIndex[it, psiClass.project, scope].firstOrNull() } } } @@ -88,7 +88,7 @@ internal val KaClassLikeSymbol.packageName: String get() = packageOrDie.asString() internal val KaClassLikeSymbol.packageOrDie: FqName - get() = classId?.packageFqName ?: error("$name has no package") + get() = classId?.packageFqName ?: throw MetaModelBuilderException("$name has no package", sourcePsiSafe()) internal fun KaSession.getPackageSymbol(classSymbol: KaClassSymbol): KaPackageSymbol? = classSymbol.classId?.packageFqName?.let { findPackage(it) } @@ -145,8 +145,10 @@ internal fun KaSession.createObjTypeStub(symbol: KaClassSymbol, module: Compiled val propertyAnnotations = symbol.annotations .mapNotNull { it.classId?.asSingleFqName() } .map { ObjAnnotationImpl(it.asString(), it.pathSegments().map { segment -> segment.asString() }) } + + val identifier = symbol.name?.identifier ?: throw MetaModelBuilderException("Could not get identifier of ${symbol.name}", symbol.sourcePsiSafe()) - return ObjClassImpl(module, symbol.name?.identifier!!, openness, symbol.sourcePsi(), propertyAnnotations) + return ObjClassImpl(module, identifier, openness, symbol.sourcePsi(), propertyAnnotations) } internal fun KaSession.isParent(kaType: KaAnnotated) = diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k2/src/metaModel/WorkspaceMetaModelBuilder.kt b/plugins/devkit/intellij.devkit.workspaceModel/k2/src/metaModel/WorkspaceMetaModelBuilder.kt index d296e7c644f7..8194472d794a 100644 --- a/plugins/devkit/intellij.devkit.workspaceModel/k2/src/metaModel/WorkspaceMetaModelBuilder.kt +++ b/plugins/devkit/intellij.devkit.workspaceModel/k2/src/metaModel/WorkspaceMetaModelBuilder.kt @@ -1,12 +1,8 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.devkit.workspaceModel.k2.metaModel -import com.intellij.devkit.workspaceModel.metaModel.WorkspaceEntityInheritsEntitySourceException -import com.intellij.devkit.workspaceModel.metaModel.IncorrectObjInterfaceException -import com.intellij.devkit.workspaceModel.metaModel.WorkspaceEntityMultipleInheritanceException -import com.intellij.devkit.workspaceModel.metaModel.WorkspaceModelDefaults +import com.intellij.devkit.workspaceModel.metaModel.* import com.intellij.devkit.workspaceModel.metaModel.impl.* -import com.intellij.devkit.workspaceModel.metaModel.unsupportedType import com.intellij.openapi.project.Project import com.intellij.psi.JavaPsiFacade import com.intellij.psi.search.GlobalSearchScope @@ -36,8 +32,9 @@ internal class WorkspaceMetaModelBuilder( kaModule: KaModule, ): CompiledObjModule = analyze(kaModule) { val ktFile = KotlinExactPackagesIndex.get(packageName, project, kaModule.contentScope).firstOrNull() - ?: error("Cannot find any files with package $packageName in module ${kaModule}") - val packageSymbol = findPackage(ktFile.packageFqName) ?: error("Could not find package $packageName in module ${kaModule}") + ?: throw MetaModelBuilderException("Cannot find any files with package $packageName in module ${kaModule}", null) + val packageSymbol = findPackage(ktFile.packageFqName) + ?: throw MetaModelBuilderException("Could not find package $packageName in module ${kaModule}", null) getObjModule(packageName, packageSymbol, kaModule) } @@ -92,10 +89,17 @@ internal class WorkspaceMetaModelBuilder( } private fun KaSession.getObjClass(entityInterface: KaClassSymbol): ObjClass<*> { - val containingPackage = getPackageSymbol(entityInterface) ?: error("Cannot find package for ${entityInterface.name ?: entityInterface}") - val objModule = getObjModule(entityInterface.packageName, containingPackage, entityInterface.containingModule) - val entityInterfaceName = entityInterface.name?.identifier ?: error("Too many errors") - return objModule.types.find { it.name == entityInterfaceName } ?: error("Cannot find $entityInterfaceName in $objModule") + val packageSymbol = getPackageSymbol(entityInterface) + if (packageSymbol == null) { + throw MetaModelBuilderException("Cannot find package for ${entityInterface.name ?: entityInterface}", entityInterface.sourcePsiSafe()) + } + val objModule = getObjModule(entityInterface.packageName, packageSymbol, entityInterface.containingModule) + val objClass = entityInterface.name?.identifier?.let { entityInterfaceName -> objModule.types.find { it.name == entityInterfaceName } } + if (objClass == null) { + throw MetaModelBuilderException("Cannot find class ${entityInterface.name ?: entityInterface} in module ${objModule.name}", + entityInterface.sourcePsiSafe()) + } + return objClass } private inner class ObjModuleStub( @@ -136,11 +140,11 @@ internal class WorkspaceMetaModelBuilder( extendedAbstract.add(superClass.name) } if (isEntitySource(superSymbol)) { - throw WorkspaceEntityInheritsEntitySourceException(classSymbol.javaClassFqn) + throw MetaModelBuilderException("${classSymbol.javaClassFqn} should not extend WorkspaceEntity and EntitySource at the same time", classSymbol.sourcePsiSafe()) } } if (extendedAbstract.size > 1) { - throw WorkspaceEntityMultipleInheritanceException(classSymbol.javaClassFqn, extendedAbstract) + throw MetaModelBuilderException("${classSymbol.javaClassFqn} should not extend multiple @Abstract entities: ${extendedAbstract.joinToString(", ")}", classSymbol.sourcePsiSafe()) } compiledObjModule.addType(objType) @@ -207,9 +211,17 @@ internal class WorkspaceMetaModelBuilder( } } ?: emptyList() - return ExtPropertyImpl(findObjClass(receiverClass), extProperty.name.identifier, valueType, computeKind(extProperty), - extProperty.isAnnotatedBy(WorkspaceModelDefaults.OPEN_ANNOTATION.classId), !extProperty.isVal, false, compiledObjModule, - extPropertyId, propertyAnnotations, extProperty.sourcePsiSafe()) + return ExtPropertyImpl(findObjClass(receiverClass), + extProperty.name.identifier, + valueType, + computeKind(extProperty), + extProperty.isAnnotatedBy(WorkspaceModelDefaults.OPEN_ANNOTATION.classId), + !extProperty.isVal, + false, + compiledObjModule, + extPropertyId, + propertyAnnotations, + extProperty.sourcePsiSafe()) } private fun KaSession.convertType( @@ -217,7 +229,8 @@ internal class WorkspaceMetaModelBuilder( knownTypes: MutableMap>, hasParentAnnotation: Boolean, ): ValueType<*> { - if (type !is KaClassType) error("$type is not a class in module ${compiledObjModule.name}") + if (type !is KaClassType) throw MetaModelBuilderException("$type is not a class in module ${compiledObjModule.name}", + type.expandedSymbol?.sourcePsiSafe()) if (type.isMarkedNullable) { val nonNullableType = type.withNullability(false) return ValueType.Optional(convertType(nonNullableType, knownTypes, hasParentAnnotation)) @@ -234,7 +247,7 @@ internal class WorkspaceMetaModelBuilder( when { type.isSubtypeOf(StandardClassIds.List) -> return ValueType.List(genericType) type.isSubtypeOf(StandardClassIds.Set) -> return ValueType.Set(genericType) - else -> return unsupportedType(type.toString()) + else -> return unsupportedType(type.toString(), type.expandedSymbol?.sourcePsiSafe()) } } @@ -252,13 +265,14 @@ internal class WorkspaceMetaModelBuilder( return classSymbolToValueType(symbol, knownTypes, processAbstractTypes) } - return unsupportedType(type.toString()) + return unsupportedType(type.toString(), type.expandedSymbol?.sourcePsiSafe()) } private fun KaSession.findObjClass(classSymbol: KaClassSymbol): ObjClass<*> { if (classSymbol.packageOrDie.asString() == compiledObjModule.name) { return entityTypes.find { it.first.classId == classSymbol.classId }?.second - ?: error("Cannot find ${classSymbol.name} in $compiledObjModule") + ?: throw MetaModelBuilderException("Cannot find ${classSymbol.name} in ${compiledObjModule.name}", + classSymbol.sourcePsiSafe()) } return getObjClass(classSymbol) } @@ -294,7 +308,8 @@ internal class WorkspaceMetaModelBuilder( } classSymbol.classKind == KaClassKind.INTERFACE || classSymbol.modality != KaSymbolModality.FINAL -> { if (!processAbstractTypes) { - throw IncorrectObjInterfaceException("$javaClassFqn is abstract type. Abstract types are not supported in generator") + throw MetaModelBuilderException("$javaClassFqn is abstract type. Abstract types are not supported in generator", + classSymbol.sourcePsiSafe()) } val inheritors = inheritors(classSymbol, javaPsiFacade, allProjectScope) .mapNotNull { ktClassOrObject -> diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k2/src/metaModel/WorkspaceMetaModelProviderImpl.kt b/plugins/devkit/intellij.devkit.workspaceModel/k2/src/metaModel/WorkspaceMetaModelProviderImpl.kt index 5a45b05fdf16..7bad04309854 100644 --- a/plugins/devkit/intellij.devkit.workspaceModel/k2/src/metaModel/WorkspaceMetaModelProviderImpl.kt +++ b/plugins/devkit/intellij.devkit.workspaceModel/k2/src/metaModel/WorkspaceMetaModelProviderImpl.kt @@ -1,6 +1,8 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.devkit.workspaceModel.k2.metaModel +import com.intellij.devkit.workspaceModel.metaModel.MetaModelBuilderException +import com.intellij.devkit.workspaceModel.metaModel.MetaProblem import com.intellij.devkit.workspaceModel.metaModel.WorkspaceMetaModelProvider import com.intellij.openapi.module.Module import com.intellij.workspaceModel.codegen.deft.meta.CompiledObjModule @@ -8,6 +10,7 @@ import org.jetbrains.kotlin.analysis.api.permissions.KaAllowAnalysisFromWriteAct import org.jetbrains.kotlin.analysis.api.permissions.KaAllowAnalysisOnEdt import org.jetbrains.kotlin.analysis.api.permissions.allowAnalysisFromWriteAction import org.jetbrains.kotlin.analysis.api.permissions.allowAnalysisOnEdt +import org.jetbrains.kotlin.analysis.api.projectStructure.KaSourceModule import org.jetbrains.kotlin.idea.base.projectStructure.toKaSourceModuleForProduction import org.jetbrains.kotlin.idea.base.projectStructure.toKaSourceModuleForTest import org.jetbrains.kotlin.psi.KtClassOrObject @@ -20,7 +23,7 @@ internal class WorkspaceMetaModelProviderImpl : WorkspaceMetaModelProvider { module: Module, processAbstractTypes: Boolean, isTestSourceFolder: Boolean, - ): List { + ): Pair, List> { val packages = ktClasses.values.mapTo(LinkedHashSet()) { it.containingKtFile.packageFqName.asString() } val metaModelProvider = WorkspaceMetaModelBuilder( @@ -34,17 +37,32 @@ internal class WorkspaceMetaModelProviderImpl : WorkspaceMetaModelProvider { module.toKaSourceModuleForTest() } if (kaModule == null) { - return emptyList() + return (emptyList() to emptyList()) } - val compiledObjModule = allowAnalysisOnEdt { + val problems: MutableList = mutableListOf() + val compiledObjModules = allowAnalysisOnEdt { allowAnalysisFromWriteAction { packages .filter { it != "" } - .map { packageName -> - metaModelProvider.getObjModule(packageName, kaModule) + .mapNotNull { packageName -> + metaModelProvider.getObjModuleOrProblem(packageName, kaModule, problems) } } } - return compiledObjModule + return compiledObjModules to problems + } + + private fun WorkspaceMetaModelBuilder.getObjModuleOrProblem( + packageName: String, + kaModule: KaSourceModule, + problems: MutableList + ): CompiledObjModule? { + try { + return getObjModule(packageName, kaModule) + } + catch (e: MetaModelBuilderException) { + problems.add(MetaProblem(e.message ?: "", e.psiToHighlight)) + return null + } } } diff --git a/plugins/devkit/intellij.devkit.workspaceModel/src/codegen/writer/CodeWriter.kt b/plugins/devkit/intellij.devkit.workspaceModel/src/codegen/writer/CodeWriter.kt index 615e6a032f83..43ef3a2fd666 100644 --- a/plugins/devkit/intellij.devkit.workspaceModel/src/codegen/writer/CodeWriter.kt +++ b/plugins/devkit/intellij.devkit.workspaceModel/src/codegen/writer/CodeWriter.kt @@ -98,7 +98,16 @@ object CodeWriter { ApplicationManagerEx.getApplicationEx().runWriteActionWithCancellableProgressInDispatchThread(title, project, null) { indicator -> indicator.text = DevKitWorkspaceModelBundle.message("progress.text.collecting.classes.metadata") val metaLoader: WorkspaceMetaModelProvider = service() - val objModules = metaLoader.loadObjModules(ktClasses, module, processAbstractTypes, isTestSourceFolder) + val (objModules, metaProblems) = metaLoader.loadObjModules(ktClasses, module, processAbstractTypes, isTestSourceFolder) + if (metaProblems.isNotEmpty()) { + WorkspaceCodegenProblemsProvider.getInstance(project).reportMetaProblem(metaProblems) + val genFolder = existingTargetFolder.invoke() + if (genFolder != null) { + indicator.text = DevKitWorkspaceModelBundle.message("progress.text.removing.old.code") + removeGeneratedCode(genFolder) + } + return@runWriteActionWithCancellableProgressInDispatchThread + } val results = generate(codeGenerator, objModules, explicitApiEnabled, isTestModule) val generatedCode = results.flatMap { it.generatedCode } @@ -106,7 +115,6 @@ object CodeWriter { WorkspaceCodegenProblemsProvider.getInstance(project).reportProblems(problems) if (generatedCode.isEmpty() || problems.any { it.level == GenerationProblem.Level.ERROR }) { - LOG.info("Not found types for generation") val genFolder = existingTargetFolder.invoke() if (genFolder != null) { indicator.text = DevKitWorkspaceModelBundle.message("progress.text.removing.old.code") diff --git a/plugins/devkit/intellij.devkit.workspaceModel/src/codegen/writer/WorkspaceCodegenProblemsProvider.kt b/plugins/devkit/intellij.devkit.workspaceModel/src/codegen/writer/WorkspaceCodegenProblemsProvider.kt index 0e15287d9c1a..dd4ed5bb0099 100644 --- a/plugins/devkit/intellij.devkit.workspaceModel/src/codegen/writer/WorkspaceCodegenProblemsProvider.kt +++ b/plugins/devkit/intellij.devkit.workspaceModel/src/codegen/writer/WorkspaceCodegenProblemsProvider.kt @@ -7,6 +7,7 @@ import com.intellij.analysis.problemsView.ProblemsCollector import com.intellij.analysis.problemsView.ProblemsProvider import com.intellij.analysis.problemsView.toolWindow.ProblemsViewToolWindowUtils.selectProjectErrorsTab import com.intellij.codeHighlighting.HighlightDisplayLevel +import com.intellij.devkit.workspaceModel.metaModel.MetaProblem import com.intellij.devkit.workspaceModel.metaModel.ObjMetaElementWithPsi import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.Service @@ -14,6 +15,7 @@ import com.intellij.openapi.components.service import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiElement import com.intellij.psi.PsiNameIdentifierOwner import com.intellij.psi.util.startOffset import com.intellij.workspaceModel.codegen.engine.GenerationProblem @@ -21,19 +23,36 @@ import com.intellij.workspaceModel.codegen.engine.ProblemLocation import javax.swing.Icon @Service(Service.Level.PROJECT) -class WorkspaceCodegenProblemsProvider(override val project: Project): ProblemsProvider { +class WorkspaceCodegenProblemsProvider(override val project: Project) : ProblemsProvider { private var currentProblems = emptyList() - + companion object { fun getInstance(project: Project): WorkspaceCodegenProblemsProvider = project.service() } + + fun reportMetaProblem(metaProblems: List) { + if (ApplicationManager.getApplication().isUnitTestMode) { + val firstProblem = metaProblems.firstOrNull() ?: return + error("Failed to collect metadata: ${firstProblem.message}") + } + + for (currentProblem in currentProblems) { + ProblemsCollector.getInstance(project).problemDisappeared(currentProblem) + } + val problems = metaProblems.map { it.toProblem() } + currentProblems = problems + for (currentProblem in problems) { + ProblemsCollector.getInstance(project).problemAppeared(currentProblem) + } + selectProjectErrorsTab(project) + } fun reportProblems(generationProblems: List) { if (generationProblems.isNotEmpty() && ApplicationManager.getApplication().isUnitTestMode) { val problem = generationProblems.first() error("Failed to generate code for ${problem.location}: ${problem.message}") } - + val problems = generationProblems.map { it.toProblem() } for (currentProblem in currentProblems) { ProblemsCollector.getInstance(project).problemDisappeared(currentProblem) @@ -46,39 +65,45 @@ class WorkspaceCodegenProblemsProvider(override val project: Project): ProblemsP selectProjectErrorsTab(project) } } + + private fun MetaProblem.toProblem(): WorkspaceModelCodeGenerationProblem { + if (psiToHighlight == null) return WorkspaceModelCodeGenerationProblem(message, HighlightDisplayLevel.ERROR.icon) + return problemWithPsi(psiToHighlight, message, HighlightDisplayLevel.ERROR.icon) + } + private fun GenerationProblem.toProblem(): WorkspaceModelCodeGenerationProblem { + val icon = when (level) { + GenerationProblem.Level.WARNING -> HighlightDisplayLevel.WARNING.icon + else -> HighlightDisplayLevel.ERROR.icon + } val psiElement = when (val problemLocation = location) { - is ProblemLocation.Class -> (problemLocation.objClass as ObjMetaElementWithPsi).sourcePsi - is ProblemLocation.Property -> (problemLocation.property as ObjMetaElementWithPsi).sourcePsi - } ?: return WorkspaceModelCodeGenerationProblem(this) + is ProblemLocation.Class -> (problemLocation.objClass as ObjMetaElementWithPsi).sourcePsi + is ProblemLocation.Property -> (problemLocation.property as ObjMetaElementWithPsi).sourcePsi + } ?: return WorkspaceModelCodeGenerationProblem(message, icon) + return problemWithPsi(psiElement, message, icon) + } + + private fun problemWithPsi(psiElement: PsiElement, text: String, icon: Icon): WorkspaceModelCodeGenerationProblem { val psiToHighlight = (psiElement as? PsiNameIdentifierOwner)?.nameIdentifier ?: psiElement val file = psiToHighlight.containingFile.virtualFile - val document = FileDocumentManager.getInstance().getDocument(file) ?: return WorkspaceModelCodeGenerationProblem(this) + val document = FileDocumentManager.getInstance().getDocument(file) ?: return WorkspaceModelCodeGenerationProblem(text, icon) val offset = psiToHighlight.startOffset val line = document.getLineNumber(offset) val column = offset - document.getLineStartOffset(line) - return WorkspaceModelCodeGenerationProblemInFile(this, file, line, column) + return WorkspaceModelCodeGenerationProblemInFile(text, icon, file, line, column) } - private open inner class WorkspaceModelCodeGenerationProblem(private val originalProblem: GenerationProblem) : Problem { + private open inner class WorkspaceModelCodeGenerationProblem(override val text: String, override val icon: Icon) : Problem { override val provider: ProblemsProvider get() = this@WorkspaceCodegenProblemsProvider - - override val text: String - get() = originalProblem.message - - override val icon: Icon - get() = when (originalProblem.level) { - GenerationProblem.Level.ERROR -> HighlightDisplayLevel.ERROR.icon - GenerationProblem.Level.WARNING -> HighlightDisplayLevel.WARNING.icon - } } - - private open inner class WorkspaceModelCodeGenerationProblemInFile( - originalProblem: GenerationProblem, + + private inner class WorkspaceModelCodeGenerationProblemInFile( + text: String, + icon: Icon, override val file: VirtualFile, override val line: Int, - override val column: Int - ) : WorkspaceModelCodeGenerationProblem(originalProblem), FileProblem + override val column: Int, + ) : WorkspaceModelCodeGenerationProblem(text, icon), FileProblem } diff --git a/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/Exceptions.kt b/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/Exceptions.kt new file mode 100644 index 000000000000..c94936273c1f --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/Exceptions.kt @@ -0,0 +1,12 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.devkit.workspaceModel.metaModel + +import com.intellij.psi.PsiElement + +class MetaModelBuilderException(errorMessage: String, val psiToHighlight: PsiElement?) : RuntimeException(errorMessage) + +class WorkspaceEntityInheritsEntitySourceException(entityFqn: String) : + IllegalStateException("$entityFqn extends WorkspaceEntity and EntitySource at the same time, which is prohibited.") + +class WorkspaceEntityMultipleInheritanceException(entityFqn: String, supers: Set) : + IllegalStateException("$entityFqn extends multiple @Abstract entities, which is prohibited: ${supers.joinToString(", ")}.") diff --git a/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/Utils.kt b/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/Utils.kt index a029f529a109..fa48e0c65fe3 100644 --- a/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/Utils.kt +++ b/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/Utils.kt @@ -4,18 +4,12 @@ package com.intellij.devkit.workspaceModel.metaModel import com.intellij.psi.PsiElement import com.intellij.workspaceModel.codegen.deft.meta.ValueType -class IncorrectObjInterfaceException(errorMessage: String) : RuntimeException(errorMessage) - -class WorkspaceEntityInheritsEntitySourceException(entityFqn: String) : - IllegalStateException("$entityFqn extends WorkspaceEntity and EntitySource at the same time, which is prohibited.") - -class WorkspaceEntityMultipleInheritanceException(entityFqn: String, supers: Set) : - IllegalStateException("$entityFqn extends multiple @Abstract entities, which is prohibited: ${supers.joinToString(", ")}.") - interface ObjMetaElementWithPsi { val sourcePsi: PsiElement? } -fun unsupportedType(type: String?): ValueType<*> { - throw IncorrectObjInterfaceException("Unsupported type '$type'") +fun unsupportedType(type: String?, psiToHighlight: PsiElement? = null): ValueType<*> { + throw MetaModelBuilderException("Unsupported type '$type'", psiToHighlight) } + +class MetaProblem(val message: String, val psiToHighlight: PsiElement?) diff --git a/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/WorkspaceMetaModelProvider.kt b/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/WorkspaceMetaModelProvider.kt index dcc17340acba..469fc0218895 100644 --- a/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/WorkspaceMetaModelProvider.kt +++ b/plugins/devkit/intellij.devkit.workspaceModel/src/metaModel/WorkspaceMetaModelProvider.kt @@ -11,5 +11,5 @@ interface WorkspaceMetaModelProvider { module: Module, processAbstractTypes: Boolean, isTestSourceFolder: Boolean, - ): List + ): Pair, List> } diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/AbstractEntityCodeGenTest.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/AbstractEntityCodeGenTest.kt index be46e770da81..844bfd1d7788 100644 --- a/plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/AbstractEntityCodeGenTest.kt +++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/AbstractEntityCodeGenTest.kt @@ -103,7 +103,7 @@ abstract class AbstractEntityCodeGenTest : CodeGenerationTestBase() { } fun testBothLinksAreChildren() { - doTestAndCheckErrorMessage("Both fields MainEntity#secondaryEntities and SecondaryEntity#mainEntity are marked as child. Probably @Parent annotation is missing from one of the properties.") + doTestAndCheckErrorMessage("Failed to generate code for secondaryEntities (MainEntity): Both fields MainEntity#secondaryEntities and SecondaryEntity#mainEntity are marked as child. Probably @Parent annotation is missing from one of the properties.") } fun testChildrenShouldBeNullable() { @@ -119,11 +119,11 @@ abstract class AbstractEntityCodeGenTest : CodeGenerationTestBase() { } fun testInheritanceEntityAndSource() { - doTestAndCheckErrorMessage("com.intellij.workspaceModel.test.api.IllegalEntity extends WorkspaceEntity and EntitySource at the same time, which is prohibited.") + doTestAndCheckErrorMessage("Failed to collect metadata: com.intellij.workspaceModel.test.api.IllegalEntity should not extend WorkspaceEntity and EntitySource at the same time") } fun testInheritanceMultiple() { - doTestAndCheckErrorMessage("com.intellij.workspaceModel.test.api.MultipleInheritanceEntity extends multiple @Abstract entities, which is prohibited: AbstractEntity3, AnotherAbstractEntity.") + doTestAndCheckErrorMessage("Failed to collect metadata: com.intellij.workspaceModel.test.api.MultipleInheritanceEntity should not extend multiple @Abstract entities: AbstractEntity3, AnotherAbstractEntity") } fun testInheritanceNonAbstract() {