From 738a0877e393a97a1cda1cc2c5a6ab3499bc8481 Mon Sep 17 00:00:00 2001 From: Kirill Bochkarev Date: Mon, 27 Oct 2025 17:52:33 +0000 Subject: [PATCH] [workspace model] IJPL-201515 Workspace Inspections - split workspace inspections into K1 and K2 - tests - include tests in Kotlin K2 tests Merge-request: IJ-MR-162676 Merged-by: Kirill Bochkarev GitOrigin-RevId: e161e7c95e330fa478ece69e4c252981349e97bb --- ...evkitInspectionsRegistrationCheckTest.java | 4 +- .../BUILD.bazel | 1 + .../intellij.devkit.workspaceModel.iml | 1 + .../k1/BUILD.bazel | 2 + .../k1/intellij.devkit.workspaceModel.k1.iml | 1 + .../intellij.devkit.workspaceModel.k1.xml | 19 +++ .../WorkspaceCodeAbsentInspection.kt | 22 +++ .../WorkspaceImplObsoleteInspection.kt | 19 +++ .../WorkspaceCodeAbsentInspectionTest.kt | 14 ++ ...rkspaceEntityMutableFieldInspectionTest.kt | 9 ++ .../WorkspaceImplObsoleteInspectionTest.kt | 14 ++ .../WorkspaceInheritanceInspectionTest.kt | 27 ++++ .../k2/BUILD.bazel | 2 + .../k2/intellij.devkit.workspaceModel.k2.iml | 1 + .../intellij.devkit.workspaceModel.k2.xml | 19 +++ .../WorkspaceCodeAbsentInspection.kt | 29 ++++ .../WorkspaceImplObsoleteInspection.kt | 21 +++ .../WorkspaceCodeAbsentInspectionTest.kt | 14 ++ ...rkspaceEntityMutableFieldInspectionTest.kt | 9 ++ .../WorkspaceImplObsoleteInspectionTest.kt | 14 ++ .../WorkspaceInheritanceInspectionTest.kt | 9 ++ .../WorkspaceImplObsolete.html | 2 +- .../WorkspaceInheritance.html | 11 ++ .../intellij.devkit.workspaceModel.xml | 27 ++-- .../DevKitWorkspaceModelBundle.properties | 16 ++- .../src/WorkspaceImplAbsentInspection.kt | 72 ---------- .../src/WorkspaceImplObsoleteInspection.kt | 64 --------- .../src/WorkspaceKtClassExtentions.kt | 89 ------------- .../WorkspaceCodeAbsentInspectionBase.kt | 126 ++++++++++++++++++ .../WorkspaceEntityMutableFieldInspection.kt | 10 +- .../WorkspaceImplObsoleteInspectionBase.kt | 91 +++++++++++++ .../WorkspaceInheritanceInspection.kt | 61 +++++++++ .../inspections/WorkspaceInspectionBase.kt | 10 ++ .../inspections/WorkspaceKtClassExtentions.kt | 111 +++++++++++++++ .../entityImplementation/entity.kt | 29 ++++ .../entitySourceFakeMetadata/entity.kt | 35 +++++ .../entitySourceMetadata/entity.kt | 49 +++++++ .../inspections/inheritance/entity.kt | 54 ++++++++ .../notWorkspaceAbstract/entity.kt | 16 +++ .../inspections/notWorkspaceClasses/entity.kt | 29 ++++ .../obsoleteImplementation/entity.kt | 33 +++++ .../inspections/varFieldForbidden/entity.kt | 2 +- .../WorkspaceCodeAbsentInspectionBaseTest.kt | 24 ++++ .../WorkspaceEntityInspectionBase.kt | 35 ----- ...ceEntityMutableFieldInspectionBaseTest.kt} | 9 +- ...WorkspaceImplObsoleteInspectionBaseTest.kt | 16 +++ .../WorkspaceInheritanceInspectionBaseTest.kt | 25 ++++ .../WorkspaceInspectionBaseTest.kt | 79 +++++++++++ 48 files changed, 1083 insertions(+), 293 deletions(-) create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/k1/src/inspections/WorkspaceCodeAbsentInspection.kt create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/k1/src/inspections/WorkspaceImplObsoleteInspection.kt create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/k1/test/inspections/WorkspaceCodeAbsentInspectionTest.kt create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/k1/test/inspections/WorkspaceEntityMutableFieldInspectionTest.kt create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/k1/test/inspections/WorkspaceImplObsoleteInspectionTest.kt create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/k1/test/inspections/WorkspaceInheritanceInspectionTest.kt create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/k2/src/inspections/WorkspaceCodeAbsentInspection.kt create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/k2/src/inspections/WorkspaceImplObsoleteInspection.kt create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/k2/test/inspections/WorkspaceCodeAbsentInspectionTest.kt create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/k2/test/inspections/WorkspaceEntityMutableFieldInspectionTest.kt create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/k2/test/inspections/WorkspaceImplObsoleteInspectionTest.kt create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/k2/test/inspections/WorkspaceInheritanceInspectionTest.kt create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/resources/inspectionDescriptions/WorkspaceInheritance.html delete mode 100644 plugins/devkit/intellij.devkit.workspaceModel/src/WorkspaceImplAbsentInspection.kt delete mode 100644 plugins/devkit/intellij.devkit.workspaceModel/src/WorkspaceImplObsoleteInspection.kt delete mode 100644 plugins/devkit/intellij.devkit.workspaceModel/src/WorkspaceKtClassExtentions.kt create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/src/inspections/WorkspaceCodeAbsentInspectionBase.kt rename plugins/devkit/intellij.devkit.workspaceModel/src/{ => inspections}/WorkspaceEntityMutableFieldInspection.kt (83%) create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/src/inspections/WorkspaceImplObsoleteInspectionBase.kt create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/src/inspections/WorkspaceInheritanceInspection.kt create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/src/inspections/WorkspaceInspectionBase.kt create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/src/inspections/WorkspaceKtClassExtentions.kt create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/entityImplementation/entity.kt create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/entitySourceFakeMetadata/entity.kt create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/entitySourceMetadata/entity.kt create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/inheritance/entity.kt create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/notWorkspaceAbstract/entity.kt create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/notWorkspaceClasses/entity.kt create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/obsoleteImplementation/entity.kt create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/inspections/WorkspaceCodeAbsentInspectionBaseTest.kt delete mode 100644 plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/inspections/WorkspaceEntityInspectionBase.kt rename plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/inspections/{WorkspaceEntityMutableFieldInspectionTest.kt => WorkspaceEntityMutableFieldInspectionBaseTest.kt} (69%) create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/inspections/WorkspaceImplObsoleteInspectionBaseTest.kt create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/inspections/WorkspaceInheritanceInspectionBaseTest.kt create mode 100644 plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/inspections/WorkspaceInspectionBaseTest.kt diff --git a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/DevkitInspectionsRegistrationCheckTest.java b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/DevkitInspectionsRegistrationCheckTest.java index e65a8e631b5f..093f8ae5c985 100644 --- a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/DevkitInspectionsRegistrationCheckTest.java +++ b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/DevkitInspectionsRegistrationCheckTest.java @@ -12,7 +12,7 @@ import java.util.stream.Stream; public class DevkitInspectionsRegistrationCheckTest extends BasePlatformTestCase { - private static final int EXPECTED_INSPECTIONS_NUMBER = 88; + private static final int EXPECTED_INSPECTIONS_NUMBER = 87; /** * Inspections that are finished and intentionally disabled. @@ -24,7 +24,7 @@ public class DevkitInspectionsRegistrationCheckTest extends BasePlatformTestCase ).sorted().toList(); /** - * Inspections which implementation is in + * Inspections which implementation is in progress * or are finished but not battle-tested yet and may require improvements/polishing. */ private static final List WIP_INSPECTIONS = diff --git a/plugins/devkit/intellij.devkit.workspaceModel/BUILD.bazel b/plugins/devkit/intellij.devkit.workspaceModel/BUILD.bazel index 005043f2cdd4..543944982255 100644 --- a/plugins/devkit/intellij.devkit.workspaceModel/BUILD.bazel +++ b/plugins/devkit/intellij.devkit.workspaceModel/BUILD.bazel @@ -66,6 +66,7 @@ jvm_library( "//platform/core-api:core", "//platform/workspace/jps", "//java/java-impl:impl", + "//plugins/kotlin/base/psi", ] ) ### auto-generated section `build intellij.devkit.workspaceModel` end \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/intellij.devkit.workspaceModel.iml b/plugins/devkit/intellij.devkit.workspaceModel/intellij.devkit.workspaceModel.iml index 3ed21dcce313..25fce83d4086 100644 --- a/plugins/devkit/intellij.devkit.workspaceModel/intellij.devkit.workspaceModel.iml +++ b/plugins/devkit/intellij.devkit.workspaceModel/intellij.devkit.workspaceModel.iml @@ -63,5 +63,6 @@ + \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k1/BUILD.bazel b/plugins/devkit/intellij.devkit.workspaceModel/k1/BUILD.bazel index d2d403d6acde..24c5d83c7deb 100644 --- a/plugins/devkit/intellij.devkit.workspaceModel/k1/BUILD.bazel +++ b/plugins/devkit/intellij.devkit.workspaceModel/k1/BUILD.bazel @@ -39,6 +39,7 @@ jvm_library( "//java/java-psi-impl:psi-impl", "//platform/util", "//platform/core-api:core", + "//platform/analysis-api:analysis", ] ) @@ -72,6 +73,7 @@ jvm_library( "//platform/testFramework", "//platform/testFramework:testFramework_test_lib", "//plugins/kotlin/base/test:test_test_lib", + "//platform/analysis-api:analysis", ] ) ### auto-generated section `build intellij.devkit.workspaceModel.k1` end diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k1/intellij.devkit.workspaceModel.k1.iml b/plugins/devkit/intellij.devkit.workspaceModel/k1/intellij.devkit.workspaceModel.k1.iml index bc3db7f6be96..895d352b37fa 100644 --- a/plugins/devkit/intellij.devkit.workspaceModel/k1/intellij.devkit.workspaceModel.k1.iml +++ b/plugins/devkit/intellij.devkit.workspaceModel/k1/intellij.devkit.workspaceModel.k1.iml @@ -45,5 +45,6 @@ + \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k1/resources/intellij.devkit.workspaceModel.k1.xml b/plugins/devkit/intellij.devkit.workspaceModel/k1/resources/intellij.devkit.workspaceModel.k1.xml index 3a7d850aef62..bde5762b7459 100644 --- a/plugins/devkit/intellij.devkit.workspaceModel/k1/resources/intellij.devkit.workspaceModel.k1.xml +++ b/plugins/devkit/intellij.devkit.workspaceModel/k1/resources/intellij.devkit.workspaceModel.k1.xml @@ -5,9 +5,28 @@ + messages.DevKitWorkspaceModelBundle + + + \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k1/src/inspections/WorkspaceCodeAbsentInspection.kt b/plugins/devkit/intellij.devkit.workspaceModel/k1/src/inspections/WorkspaceCodeAbsentInspection.kt new file mode 100644 index 000000000000..9110ad281d22 --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/k1/src/inspections/WorkspaceCodeAbsentInspection.kt @@ -0,0 +1,22 @@ +// 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.k1.inspections + +import com.intellij.devkit.workspaceModel.inspections.WorkspaceCodeAbsentInspectionBase +import com.intellij.openapi.util.IntellijInternalApi +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.annotations.VisibleForTesting +import org.jetbrains.kotlin.idea.base.projectStructure.moduleInfo +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtClassOrObject + +@VisibleForTesting +@IntellijInternalApi +@ApiStatus.Internal +class WorkspaceCodeAbsentInspection : WorkspaceCodeAbsentInspectionBase() { + override fun belongToSameModule(ktClass: KtClassOrObject, otherKtClass: KtClassOrObject): Boolean = + ktClass.moduleInfo == otherKtClass.moduleInfo + + override fun getModuleSearchScope(ktClass: KtClassOrObject): GlobalSearchScope = + ktClass.moduleInfo.contentScope +} \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k1/src/inspections/WorkspaceImplObsoleteInspection.kt b/plugins/devkit/intellij.devkit.workspaceModel/k1/src/inspections/WorkspaceImplObsoleteInspection.kt new file mode 100644 index 000000000000..5b264324f7bd --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/k1/src/inspections/WorkspaceImplObsoleteInspection.kt @@ -0,0 +1,19 @@ +// 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.k1.inspections + +import com.intellij.devkit.workspaceModel.inspections.WorkspaceImplObsoleteInspectionBase +import com.intellij.openapi.util.IntellijInternalApi +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.annotations.VisibleForTesting +import org.jetbrains.kotlin.idea.base.projectStructure.moduleInfo +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtClassOrObject + +@VisibleForTesting +@IntellijInternalApi +@ApiStatus.Internal +class WorkspaceImplObsoleteInspection : WorkspaceImplObsoleteInspectionBase() { + override fun getModuleSearchScope(ktClass: KtClassOrObject): GlobalSearchScope = + ktClass.moduleInfo.contentScope +} \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k1/test/inspections/WorkspaceCodeAbsentInspectionTest.kt b/plugins/devkit/intellij.devkit.workspaceModel/k1/test/inspections/WorkspaceCodeAbsentInspectionTest.kt new file mode 100644 index 000000000000..3187b93e4bb7 --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/k1/test/inspections/WorkspaceCodeAbsentInspectionTest.kt @@ -0,0 +1,14 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +@file:OptIn(IntellijInternalApi::class) + +package com.intellij.devkit.workspaceModel.k1.inspections + +import com.intellij.devkit.workspaceModel.inspections.WorkspaceCodeAbsentInspectionBaseTest +import com.intellij.openapi.util.IntellijInternalApi + +class WorkspaceCodeAbsentInspectionTest : WorkspaceCodeAbsentInspectionBaseTest() { + override fun setUp() { + super.setUp() + myFixture.enableInspections(WorkspaceCodeAbsentInspection()) + } +} \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k1/test/inspections/WorkspaceEntityMutableFieldInspectionTest.kt b/plugins/devkit/intellij.devkit.workspaceModel/k1/test/inspections/WorkspaceEntityMutableFieldInspectionTest.kt new file mode 100644 index 000000000000..4f9ae9b8216b --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/k1/test/inspections/WorkspaceEntityMutableFieldInspectionTest.kt @@ -0,0 +1,9 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +@file:OptIn(IntellijInternalApi::class) + +package com.intellij.devkit.workspaceModel.k1.inspections + +import com.intellij.devkit.workspaceModel.inspections.WorkspaceEntityMutableFieldInspectionBaseTest +import com.intellij.openapi.util.IntellijInternalApi + +class WorkspaceEntityMutableFieldInspectionTest : WorkspaceEntityMutableFieldInspectionBaseTest() \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k1/test/inspections/WorkspaceImplObsoleteInspectionTest.kt b/plugins/devkit/intellij.devkit.workspaceModel/k1/test/inspections/WorkspaceImplObsoleteInspectionTest.kt new file mode 100644 index 000000000000..7283b10ae3da --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/k1/test/inspections/WorkspaceImplObsoleteInspectionTest.kt @@ -0,0 +1,14 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +@file:OptIn(IntellijInternalApi::class) + +package com.intellij.devkit.workspaceModel.k1.inspections + +import com.intellij.devkit.workspaceModel.inspections.WorkspaceImplObsoleteInspectionBaseTest +import com.intellij.openapi.util.IntellijInternalApi + +class WorkspaceImplObsoleteInspectionTest : WorkspaceImplObsoleteInspectionBaseTest() { + override fun setUp() { + super.setUp() + myFixture.enableInspections(WorkspaceImplObsoleteInspection()) + } +} \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k1/test/inspections/WorkspaceInheritanceInspectionTest.kt b/plugins/devkit/intellij.devkit.workspaceModel/k1/test/inspections/WorkspaceInheritanceInspectionTest.kt new file mode 100644 index 000000000000..c2c9a7b30042 --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/k1/test/inspections/WorkspaceInheritanceInspectionTest.kt @@ -0,0 +1,27 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +@file:OptIn(IntellijInternalApi::class) + +package com.intellij.devkit.workspaceModel.k1.inspections + +import com.intellij.devkit.workspaceModel.inspections.WorkspaceInheritanceInspection +import com.intellij.devkit.workspaceModel.inspections.WorkspaceInspectionBaseTest +import com.intellij.openapi.util.IntellijInternalApi + +class WorkspaceInheritanceInspectionTest : WorkspaceInspectionBaseTest() { + override fun setUp() { + super.setUp() + myFixture.enableInspections(WorkspaceInheritanceInspection()) + } + + fun testInheritance() { + doTest() + } + + fun testNotWorkspaceAbstract() { + doTest() + } + + fun testNotWorkspaceClasses() { + doTest() + } +} \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k2/BUILD.bazel b/plugins/devkit/intellij.devkit.workspaceModel/k2/BUILD.bazel index d77f323a94e7..af712a58aeb7 100644 --- a/plugins/devkit/intellij.devkit.workspaceModel/k2/BUILD.bazel +++ b/plugins/devkit/intellij.devkit.workspaceModel/k2/BUILD.bazel @@ -41,6 +41,7 @@ jvm_library( "//platform/core-impl", "//java/openapi:java", "//plugins/kotlin/base/psi", + "//platform/analysis-api:analysis", ] ) @@ -75,6 +76,7 @@ jvm_library( "//platform/testFramework", "//platform/testFramework:testFramework_test_lib", "//plugins/kotlin/base/test:test_test_lib", + "//platform/analysis-api:analysis", ] ) ### auto-generated section `build intellij.devkit.workspaceModel.k2` end diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k2/intellij.devkit.workspaceModel.k2.iml b/plugins/devkit/intellij.devkit.workspaceModel/k2/intellij.devkit.workspaceModel.k2.iml index 5192c3c6fce9..1a2dde5c8213 100644 --- a/plugins/devkit/intellij.devkit.workspaceModel/k2/intellij.devkit.workspaceModel.k2.iml +++ b/plugins/devkit/intellij.devkit.workspaceModel/k2/intellij.devkit.workspaceModel.k2.iml @@ -46,5 +46,6 @@ + \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k2/resources/intellij.devkit.workspaceModel.k2.xml b/plugins/devkit/intellij.devkit.workspaceModel/k2/resources/intellij.devkit.workspaceModel.k2.xml index 655a8881a737..a13f59f396e2 100644 --- a/plugins/devkit/intellij.devkit.workspaceModel/k2/resources/intellij.devkit.workspaceModel.k2.xml +++ b/plugins/devkit/intellij.devkit.workspaceModel/k2/resources/intellij.devkit.workspaceModel.k2.xml @@ -5,9 +5,28 @@ + messages.DevKitWorkspaceModelBundle + + + \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k2/src/inspections/WorkspaceCodeAbsentInspection.kt b/plugins/devkit/intellij.devkit.workspaceModel/k2/src/inspections/WorkspaceCodeAbsentInspection.kt new file mode 100644 index 000000000000..8fdc2993ee8e --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/k2/src/inspections/WorkspaceCodeAbsentInspection.kt @@ -0,0 +1,29 @@ +// 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.inspections + +import com.intellij.devkit.workspaceModel.inspections.WorkspaceCodeAbsentInspectionBase +import com.intellij.openapi.util.IntellijInternalApi +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.annotations.VisibleForTesting +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.analysis.api.getModule +import org.jetbrains.kotlin.idea.base.projectStructure.getKaModule +import org.jetbrains.kotlin.idea.base.projectStructure.moduleInfo +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtClassOrObject + +@VisibleForTesting +@IntellijInternalApi +@ApiStatus.Internal +class WorkspaceCodeAbsentInspection : WorkspaceCodeAbsentInspectionBase() { + override fun belongToSameModule(ktClass: KtClassOrObject, otherKtClass: KtClassOrObject): Boolean = + analyze(ktClass) { + getModule(ktClass) == getModule(otherKtClass) + } + + override fun getModuleSearchScope(ktClass: KtClassOrObject): GlobalSearchScope = + analyze(ktClass) { + getModule(ktClass).contentScope + } +} \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k2/src/inspections/WorkspaceImplObsoleteInspection.kt b/plugins/devkit/intellij.devkit.workspaceModel/k2/src/inspections/WorkspaceImplObsoleteInspection.kt new file mode 100644 index 000000000000..e85c63181d5c --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/k2/src/inspections/WorkspaceImplObsoleteInspection.kt @@ -0,0 +1,21 @@ +// 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.inspections + +import com.intellij.devkit.workspaceModel.inspections.WorkspaceImplObsoleteInspectionBase +import com.intellij.openapi.util.IntellijInternalApi +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.annotations.VisibleForTesting +import org.jetbrains.kotlin.analysis.api.analyze +import org.jetbrains.kotlin.analysis.api.getModule +import org.jetbrains.kotlin.psi.KtClassOrObject + +@VisibleForTesting +@IntellijInternalApi +@ApiStatus.Internal +class WorkspaceImplObsoleteInspection : WorkspaceImplObsoleteInspectionBase() { + override fun getModuleSearchScope(ktClass: KtClassOrObject): GlobalSearchScope = + analyze(ktClass) { + getModule(ktClass).contentScope + } +} \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k2/test/inspections/WorkspaceCodeAbsentInspectionTest.kt b/plugins/devkit/intellij.devkit.workspaceModel/k2/test/inspections/WorkspaceCodeAbsentInspectionTest.kt new file mode 100644 index 000000000000..c6fbb26fe43e --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/k2/test/inspections/WorkspaceCodeAbsentInspectionTest.kt @@ -0,0 +1,14 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +@file:OptIn(IntellijInternalApi::class) + +package com.intellij.devkit.workspaceModel.k2.inspections + +import com.intellij.devkit.workspaceModel.inspections.WorkspaceCodeAbsentInspectionBaseTest +import com.intellij.openapi.util.IntellijInternalApi + +class WorkspaceCodeAbsentInspectionTest : WorkspaceCodeAbsentInspectionBaseTest() { + override fun setUp() { + super.setUp() + myFixture.enableInspections(WorkspaceCodeAbsentInspection()) + } +} \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k2/test/inspections/WorkspaceEntityMutableFieldInspectionTest.kt b/plugins/devkit/intellij.devkit.workspaceModel/k2/test/inspections/WorkspaceEntityMutableFieldInspectionTest.kt new file mode 100644 index 000000000000..541f849583f9 --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/k2/test/inspections/WorkspaceEntityMutableFieldInspectionTest.kt @@ -0,0 +1,9 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +@file:OptIn(IntellijInternalApi::class) + +package com.intellij.devkit.workspaceModel.k2.inspections + +import com.intellij.devkit.workspaceModel.inspections.WorkspaceEntityMutableFieldInspectionBaseTest +import com.intellij.openapi.util.IntellijInternalApi + +class WorkspaceEntityMutableFieldInspectionTest : WorkspaceEntityMutableFieldInspectionBaseTest() \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k2/test/inspections/WorkspaceImplObsoleteInspectionTest.kt b/plugins/devkit/intellij.devkit.workspaceModel/k2/test/inspections/WorkspaceImplObsoleteInspectionTest.kt new file mode 100644 index 000000000000..119a3889a360 --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/k2/test/inspections/WorkspaceImplObsoleteInspectionTest.kt @@ -0,0 +1,14 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +@file:OptIn(IntellijInternalApi::class) + +package com.intellij.devkit.workspaceModel.k2.inspections + +import com.intellij.devkit.workspaceModel.inspections.WorkspaceImplObsoleteInspectionBaseTest +import com.intellij.openapi.util.IntellijInternalApi + +class WorkspaceImplObsoleteInspectionTest : WorkspaceImplObsoleteInspectionBaseTest() { + override fun setUp() { + super.setUp() + myFixture.enableInspections(WorkspaceImplObsoleteInspection()) + } +} \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/k2/test/inspections/WorkspaceInheritanceInspectionTest.kt b/plugins/devkit/intellij.devkit.workspaceModel/k2/test/inspections/WorkspaceInheritanceInspectionTest.kt new file mode 100644 index 000000000000..5f33d4337e6a --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/k2/test/inspections/WorkspaceInheritanceInspectionTest.kt @@ -0,0 +1,9 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +@file:OptIn(IntellijInternalApi::class) + +package com.intellij.devkit.workspaceModel.k2.inspections + +import com.intellij.devkit.workspaceModel.inspections.WorkspaceInheritanceInspectionBaseTest +import com.intellij.openapi.util.IntellijInternalApi + +class WorkspaceInheritanceInspectionTest : WorkspaceInheritanceInspectionBaseTest() \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/resources/inspectionDescriptions/WorkspaceImplObsolete.html b/plugins/devkit/intellij.devkit.workspaceModel/resources/inspectionDescriptions/WorkspaceImplObsolete.html index 738e34dcbe1c..95824b036747 100644 --- a/plugins/devkit/intellij.devkit.workspaceModel/resources/inspectionDescriptions/WorkspaceImplObsolete.html +++ b/plugins/devkit/intellij.devkit.workspaceModel/resources/inspectionDescriptions/WorkspaceImplObsolete.html @@ -1,6 +1,6 @@ -Reports existence of the obsolete implementation for the entity. +Reports the existence of the obsolete implementation for the entity.

Verifies that existing implementation for entities has the same API version as described at com.intellij.platform.workspace.storage.CodeGeneratorVersions from dependencies.

diff --git a/plugins/devkit/intellij.devkit.workspaceModel/resources/inspectionDescriptions/WorkspaceInheritance.html b/plugins/devkit/intellij.devkit.workspaceModel/resources/inspectionDescriptions/WorkspaceInheritance.html new file mode 100644 index 000000000000..532417bb859a --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/resources/inspectionDescriptions/WorkspaceInheritance.html @@ -0,0 +1,11 @@ + + +Reports problems related to workspace entity inheritance. +

+ Verifies that an entity inherits from only one and only @Abstract workspace entity. +

+

+ Verifies that an entity does not inherit fromWorkspaceEntity and EntitySource at the same time. +

+ + \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/resources/intellij.devkit.workspaceModel.xml b/plugins/devkit/intellij.devkit.workspaceModel/resources/intellij.devkit.workspaceModel.xml index fddc533e0097..4049021e0a3d 100644 --- a/plugins/devkit/intellij.devkit.workspaceModel/resources/intellij.devkit.workspaceModel.xml +++ b/plugins/devkit/intellij.devkit.workspaceModel/resources/intellij.devkit.workspaceModel.xml @@ -23,25 +23,16 @@ groupPathKey="inspections.group.path" groupKey="inspections.group.workspace.model" runForWholeFile="true" enabledByDefault="true" level="WARNING" - implementationClass="com.intellij.devkit.workspaceModel.WorkspaceImplObsoleteInspection" - key="inspection.workspace.obsolete.model.display.name"/> - - + diff --git a/plugins/devkit/intellij.devkit.workspaceModel/resources/messages/DevKitWorkspaceModelBundle.properties b/plugins/devkit/intellij.devkit.workspaceModel/resources/messages/DevKitWorkspaceModelBundle.properties index 6818c778a5a7..ff715f3cb81a 100644 --- a/plugins/devkit/intellij.devkit.workspaceModel/resources/messages/DevKitWorkspaceModelBundle.properties +++ b/plugins/devkit/intellij.devkit.workspaceModel/resources/messages/DevKitWorkspaceModelBundle.properties @@ -2,13 +2,19 @@ action.WorkspaceModelGeneration.text=Generate Workspace Model Implementation inspections.group.path=Plugin DevKit inspections.group.workspace.model=Workspace model inspection.workspace.obsolete.model.display.name=Obsolete version of entity implementation +inspection.workspace.impl.generation.display.name=Generate implementation inspection.workspace.msg.obsolete.implementation=Obsolete entity implementation inspection.workspace.msg.regenerate.implementation=Regenerate implementation -inspection.workspace.impl.generation.display.name=Generate implementation inspection.workspace.msg.generate.implementation=Generate implementation -inspection.workspace.msg.collect.class.metadata=Collect class metadata inspection.workspace.msg.absent.implementation=Absent entity implementation -inspection.workspace.mutable.field.display.name=Unsupported 'var' field in entity +inspection.workspace.msg.absent.source.metadata=Absent EntitySource metadata +inspection.workspace.msg.absent.parent.source.metadata=Absent parent EntitySource metadata: {0} +inspection.workspace.msg.entity.and.source.inheritance=Cannot inherit EntitySource and WorkspaceEntity at the same time +inspection.workspace.msg.non.abstract.inheritance=Entities can only inherit '@Abstract' entities +inspection.workspace.msg.multiple.inheritance=Multiple inheritance is not supported in workspace entities +inspection.workspace.msg.user.implementation=Entity implementation has to be generated with the dedicated action +inspection.workspace.mutable.field.display.name=Unsupported 'var' field in an entity +inspection.workspace.inheritance.display.name=Workspace inheritance inspection.workspace.msg.change.field.to.val=Change to 'val' progress.title.generating.code=Generating code @@ -25,3 +31,7 @@ notification.workspace.code.generation.not.available=Workspace code generation n notification.workspace.code.generation.not.available.message=Entity code generation is not supported for plugins yet action.WorkspaceModelGenerateAllModulesAction.text=Generate Workspace Model Implementation for All Modules action.WorkspaceEntitiesJsonClipboard.text=Dump Workspace Entities to Clipboard in JSON Format +action.WorkspaceEntitiesJsonLog.text=Dump Workspace Entities to Log in JSON Format +action.WorkspaceEntitiesJsonLogFile.text=Dump Workspace Entities to Log File in JSON Format +progress.title.dumping.workspace.entities.json.to.clipboard=Dumping workspace entities JSON to clipboard +notification.title.cannot.find.log.directory=Cannot find the log directory diff --git a/plugins/devkit/intellij.devkit.workspaceModel/src/WorkspaceImplAbsentInspection.kt b/plugins/devkit/intellij.devkit.workspaceModel/src/WorkspaceImplAbsentInspection.kt deleted file mode 100644 index f2ed21455a96..000000000000 --- a/plugins/devkit/intellij.devkit.workspaceModel/src/WorkspaceImplAbsentInspection.kt +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.devkit.workspaceModel - -import com.intellij.codeInspection.LocalInspectionTool -import com.intellij.codeInspection.LocalQuickFixOnPsiElement -import com.intellij.codeInspection.ProblemHighlightType -import com.intellij.codeInspection.ProblemsHolder -import com.intellij.openapi.project.Project -import com.intellij.openapi.roots.ProjectRootManager -import com.intellij.platform.workspace.storage.WorkspaceEntity -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiFile -import com.intellij.psi.search.GlobalSearchScope -import com.intellij.workspaceModel.codegen.engine.SKIPPED_TYPES -import org.jetbrains.kotlin.idea.stubindex.KotlinClassShortNameIndex -import org.jetbrains.kotlin.psi.KtClass -import org.jetbrains.kotlin.psi.KtVisitorVoid - -internal class WorkspaceImplGenerationInspection: LocalInspectionTool() { - - override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = object : KtVisitorVoid() { - override fun visitClass(klass: KtClass) { - val superTypeFqn = klass.getWorkspaceModelSuperType() - if (superTypeFqn == null) { - return - } - - val highlightType: ProblemHighlightType - val descriptionTemplate: String - if (superTypeFqn == WorkspaceEntity::class.qualifiedName) { //is WorkspaceEntity implementation - if (!klass.isInterface()) return - if (klass.name in SKIPPED_TYPES) return - if (klass.isAbstractEntity()) return - if (klass.name == "Builder") return - val foundImplClasses = KotlinClassShortNameIndex["${klass.name}Impl", klass.project, GlobalSearchScope.allScope(klass.project)] - if (foundImplClasses.isEmpty()) { - descriptionTemplate = DevKitWorkspaceModelBundle.message("inspection.workspace.msg.absent.implementation") - highlightType = ProblemHighlightType.GENERIC_ERROR_OR_WARNING - } else { - descriptionTemplate = DevKitWorkspaceModelBundle.message("inspection.workspace.msg.regenerate.implementation") - highlightType = ProblemHighlightType.INFORMATION - } - } else { - descriptionTemplate = DevKitWorkspaceModelBundle.message("inspection.workspace.msg.collect.class.metadata") - highlightType = ProblemHighlightType.INFORMATION - } - - holder.registerProblem( - klass.nameIdentifier!!, descriptionTemplate, highlightType, - GenerateWorkspaceModelFix(klass.nameIdentifier!!) - ) - } - } -} - -private class GenerateWorkspaceModelFix(psiElement: PsiElement) : LocalQuickFixOnPsiElement(psiElement) { - override fun getText() = DevKitWorkspaceModelBundle.message("inspection.workspace.msg.generate.implementation") - - override fun getFamilyName() = name - - override fun invoke(project: Project, psiFile: PsiFile, startElement: PsiElement, endElement: PsiElement) { - if (!isIntellijProjectOrRegistryKeyIsSet(project)) { - generationNotAvailableNotification(project) - return - } - val projectFileIndex = ProjectRootManager.getInstance(project).fileIndex - val module = projectFileIndex.getModuleForFile(psiFile.virtualFile) - WorkspaceModelGenerator.getInstance(project).generate(module!!) - } - - override fun startInWriteAction(): Boolean = false -} \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/src/WorkspaceImplObsoleteInspection.kt b/plugins/devkit/intellij.devkit.workspaceModel/src/WorkspaceImplObsoleteInspection.kt deleted file mode 100644 index b1f8248290a6..000000000000 --- a/plugins/devkit/intellij.devkit.workspaceModel/src/WorkspaceImplObsoleteInspection.kt +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.devkit.workspaceModel - -import com.intellij.codeInspection.LocalInspectionTool -import com.intellij.codeInspection.LocalQuickFixOnPsiElement -import com.intellij.codeInspection.ProblemsHolder -import com.intellij.openapi.diagnostic.logger -import com.intellij.openapi.project.Project -import com.intellij.openapi.roots.ProjectRootManager -import com.intellij.platform.workspace.storage.CodeGeneratorVersions -import com.intellij.psi.JavaPsiFacade -import com.intellij.psi.PsiElement -import com.intellij.psi.PsiFile -import com.intellij.psi.PsiLiteralExpression -import com.intellij.psi.search.GlobalSearchScope -import org.jetbrains.kotlin.idea.stubindex.KotlinClassShortNameIndex -import org.jetbrains.kotlin.psi.KtClass -import org.jetbrains.kotlin.psi.KtVisitorVoid - -private val LOG = logger() - -internal class WorkspaceImplObsoleteInspection: LocalInspectionTool() { - - override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = object : KtVisitorVoid() { - override fun visitClass(klass: KtClass) { - if (!klass.isWorkspaceEntity()) return - val targetApiVersion = calculateTargetApiVersion(klass.resolveScope, klass.project) - if (targetApiVersion == null) { - LOG.info("Can't evaluate target API version for ${klass.name}") - return - } - if (klass.name == "Builder") return - val foundImplClasses = KotlinClassShortNameIndex.get("${klass.name}Impl", klass.project, GlobalSearchScope.allScope(klass.project)) - if (foundImplClasses.isEmpty()) return - val implClass = foundImplClasses.first() - val apiVersion = (implClass as? KtClass)?.getApiVersion() - if (apiVersion == targetApiVersion) return - holder.registerProblem(klass.nameIdentifier!!, DevKitWorkspaceModelBundle.message("inspection.workspace.msg.obsolete.implementation"), - RegenerateWorkspaceModelFix(klass.nameIdentifier!!)) - } - } - - private fun calculateTargetApiVersion(scope: GlobalSearchScope, project: Project): Int? { - val generatorVersionsClass = JavaPsiFacade.getInstance(project).findClass(CodeGeneratorVersions::class.java.name, scope) ?: return null - val versionField = generatorVersionsClass.findFieldByName("API_VERSION_INTERNAL", false) ?: return null - return (versionField.initializer as? PsiLiteralExpression)?.value as? Int - } -} - -private class RegenerateWorkspaceModelFix(psiElement: PsiElement) : LocalQuickFixOnPsiElement(psiElement) { - override fun getText() = DevKitWorkspaceModelBundle.message("inspection.workspace.msg.regenerate.implementation") - - override fun getFamilyName() = name - - override fun invoke(project: Project, psiFile: PsiFile, startElement: PsiElement, endElement: PsiElement) { - if (!isIntellijProjectOrRegistryKeyIsSet(project)) { - generationNotAvailableNotification(project) - return - } - val projectFileIndex = ProjectRootManager.getInstance(project).fileIndex - val module = projectFileIndex.getModuleForFile(psiFile.virtualFile) - WorkspaceModelGenerator.getInstance(project).generate(module!!) - } -} \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/src/WorkspaceKtClassExtentions.kt b/plugins/devkit/intellij.devkit.workspaceModel/src/WorkspaceKtClassExtentions.kt deleted file mode 100644 index 47af7362ca96..000000000000 --- a/plugins/devkit/intellij.devkit.workspaceModel/src/WorkspaceKtClassExtentions.kt +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.devkit.workspaceModel - -import com.intellij.platform.workspace.storage.* -import com.intellij.platform.workspace.storage.annotations.Abstract -import org.jetbrains.kotlin.KtNodeTypes -import org.jetbrains.kotlin.idea.references.mainReference -import org.jetbrains.kotlin.parsing.parseNumericLiteral -import org.jetbrains.kotlin.psi.KtClass -import org.jetbrains.kotlin.psi.KtConstantExpression -import org.jetbrains.kotlin.psi.KtSuperTypeListEntry -import org.jetbrains.kotlin.psi.KtUserType -import java.util.* - -private val workspaceModelClasses: List = listOfNotNull( - WorkspaceEntity::class.qualifiedName, - EntitySource::class.qualifiedName, - SymbolicEntityId::class.qualifiedName -) - -/** - * Finds which of the [workspaceModelClasses] inherits this KtClass. - * - * @return parent class fully qualified name if inherits or null - */ -internal fun KtClass.getWorkspaceModelSuperType(): String? { - val superTypeList = LinkedList() - superTypeList.addAll(superTypeListEntries) - while (!superTypeList.isEmpty()) { - val superType = superTypeList.pop() - val resolvedKtClass = (superType.typeReference?.typeElement as? KtUserType)?.referenceExpression?.mainReference?.resolve() as? KtClass - ?: continue - val resolvedKtClassFqn = resolvedKtClass.fqName?.asString() - if (workspaceModelClasses.contains(resolvedKtClassFqn)) return resolvedKtClassFqn - resolvedKtClass.superTypeListEntries.forEach { superTypeList.push(it) } - } - return null -} - -internal fun KtClass.isWorkspaceEntity(): Boolean { - if (!isInterface()) return false - val superTypeFqn = getWorkspaceModelSuperType() ?: return false - return superTypeFqn == WorkspaceEntity::class.qualifiedName -} - -internal fun KtClass.isAbstractEntity(): Boolean { - val annotationName = Abstract::class.simpleName - return annotationEntries.any { it.shortName?.identifier == annotationName } -} - -internal fun KtClass.getApiVersion(): Int? { - val annotationName = GeneratedCodeApiVersion::class.simpleName - val annotation = annotationEntries.find { it.shortName?.identifier == annotationName } - if (annotation == null) { - error("$name should contain $annotationName") - } - if (annotation.valueArguments.size != 1) { - error("Annotation $annotationName at $name should contain only one argument") - } - val argumentExpression = annotation.valueArguments[0].getArgumentExpression() as? KtConstantExpression - if (argumentExpression == null) { - error("Annotation parameter should be int constant") - } - val elementType = argumentExpression.node.elementType - if (elementType == KtNodeTypes.INTEGER_CONSTANT) { - return parseNumericLiteral(argumentExpression.text, elementType)?.toInt() - } - return null -} - -internal fun KtClass.getImplVersion(): Int? { - val annotationName = GeneratedCodeImplVersion::class.simpleName - val annotation = annotationEntries.find { it.shortName?.identifier == annotationName } - if (annotation == null) { - error("$name should contain $annotationName") - } - if (annotation.valueArguments.size != 1) { - error("Annotation $annotationName at $name should contain only one argument") - } - val argumentExpression = annotation.valueArguments[0].getArgumentExpression() as? KtConstantExpression - if (argumentExpression == null) { - error("Annotation parameter should be int constant") - } - val elementType = argumentExpression.node.elementType - if (elementType == KtNodeTypes.INTEGER_CONSTANT) { - return parseNumericLiteral(argumentExpression.text, elementType)?.toInt() - } - return null -} \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/src/inspections/WorkspaceCodeAbsentInspectionBase.kt b/plugins/devkit/intellij.devkit.workspaceModel/src/inspections/WorkspaceCodeAbsentInspectionBase.kt new file mode 100644 index 000000000000..5d593a2cfe45 --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/src/inspections/WorkspaceCodeAbsentInspectionBase.kt @@ -0,0 +1,126 @@ +// 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.inspections + +import com.intellij.codeInspection.LocalQuickFixOnPsiElement +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.devkit.workspaceModel.DevKitWorkspaceModelBundle +import com.intellij.devkit.workspaceModel.WorkspaceModelGenerator +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ProjectRootManager +import com.intellij.platform.workspace.storage.metadata.impl.MetadataStorageBase +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.workspaceModel.codegen.engine.SKIPPED_TYPES +import org.jetbrains.kotlin.idea.base.psi.KotlinPsiHeuristics +import org.jetbrains.kotlin.idea.stubindex.KotlinClassShortNameIndex +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtVisitorVoid + +abstract class WorkspaceCodeAbsentInspectionBase : WorkspaceInspectionBase() { + protected abstract fun belongToSameModule(ktClass: KtClassOrObject, otherKtClass: KtClassOrObject): Boolean + + private fun entitySourceIsPresentInMetadata(ktClass: KtClassOrObject): Boolean { + val jvmName = KotlinPsiHeuristics.getJvmName(ktClass) ?: return true + val searchScope = getModuleSearchScope(ktClass) + val moduleMetadata = KotlinClassShortNameIndex["MetadataStorageImpl", ktClass.project, searchScope] + val metadataFile: PsiFile = moduleMetadata.firstOrNull { metadataObject -> + belongToSameModule(metadataObject, ktClass) && metadataObject.getMatchingSuperTypes { superType -> + superType.fqName?.asString() == MetadataStorageBase::class.qualifiedName + }.any() + }?.containingFile ?: return false + // String.escapeDollar codegen/impl/metadata/StringExtensions.kt + val workspaceMetaName = jvmName.replace("$", "\\$") + // TODO: improve + val jvmNameOccurrence = metadataFile.getFileDocument().getImmutableCharSequence().indexOf(workspaceMetaName) + return jvmNameOccurrence != -1 + } + + private fun findSuperSourceAbsentInMetadata(ktClass: KtClassOrObject, visited: MutableSet): String? { + for (superType in ktClass.superTypeListEntries) { + val resolvedSuper = superType.typeReference?.resolveToKtClass() ?: continue + if (!visited.add(resolvedSuper)) continue + if (resolvedSuper.isWorkspaceEntitySource()) { + if (!entitySourceIsPresentInMetadata(resolvedSuper)) return resolvedSuper.name + val absentSuper = findSuperSourceAbsentInMetadata(resolvedSuper, visited) + if (absentSuper != null) return absentSuper + } + } + return null + } + + private fun processWorkspaceEntityDeclaration(klass: KtClass, holder: ProblemsHolder) { + val psiElementForHighlighting = klass.getPsiElementForHighlighting() ?: return + val highlightType: ProblemHighlightType + val descriptionTemplate: String + + if (klass.name in SKIPPED_TYPES) return + if (klass.isAbstractEntity()) return + val foundImpl = findWorkspaceEntityImplementation(klass, getModuleSearchScope(klass)) + if (foundImpl == null) { + descriptionTemplate = DevKitWorkspaceModelBundle.message("inspection.workspace.msg.absent.implementation") + highlightType = ProblemHighlightType.GENERIC_ERROR_OR_WARNING + } + else { + descriptionTemplate = DevKitWorkspaceModelBundle.message("inspection.workspace.msg.regenerate.implementation") + highlightType = ProblemHighlightType.INFORMATION + } + + holder.registerProblem( + psiElementForHighlighting, descriptionTemplate, highlightType, + GenerateWorkspaceModelFix(psiElementForHighlighting) + ) + } + + private fun processEntitySource(klass: KtClassOrObject, holder: ProblemsHolder) { + val psiElementForHighlighting = klass.getPsiElementForHighlighting() ?: return + val highlightType: ProblemHighlightType + val descriptionTemplate: String + + if (!entitySourceIsPresentInMetadata(klass)) { + descriptionTemplate = DevKitWorkspaceModelBundle.message("inspection.workspace.msg.absent.source.metadata") + highlightType = ProblemHighlightType.GENERIC_ERROR_OR_WARNING + } + else { + val absentSuper = findSuperSourceAbsentInMetadata(klass, mutableSetOf()) ?: return + descriptionTemplate = DevKitWorkspaceModelBundle.message("inspection.workspace.msg.absent.parent.source.metadata", absentSuper) + highlightType = ProblemHighlightType.GENERIC_ERROR_OR_WARNING + } + + holder.registerProblem( + psiElementForHighlighting, descriptionTemplate, highlightType, + GenerateWorkspaceModelFix(psiElementForHighlighting) + ) + } + + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): KtVisitorVoid = object : KtVisitorVoid() { + override fun visitClassOrObject(klass: KtClassOrObject) { + val superTypesFqns = klass.getWorkspaceSupers() + if (superTypesFqns.isEmpty()) { + return + } + + if (klass is KtClass && klass.isWorkspaceEntityDeclaration()) { + processWorkspaceEntityDeclaration(klass, holder) + } + else if (klass.isWorkspaceEntitySource()) { + processEntitySource(klass, holder) + } + } + } + + private class GenerateWorkspaceModelFix(psiElement: PsiElement) : LocalQuickFixOnPsiElement(psiElement) { + override fun getText() = DevKitWorkspaceModelBundle.message("inspection.workspace.msg.generate.implementation") + + override fun getFamilyName() = name + + override fun invoke(project: Project, psiFile: PsiFile, startElement: PsiElement, endElement: PsiElement) { + val projectFileIndex = ProjectRootManager.getInstance(project).fileIndex + val module = projectFileIndex.getModuleForFile(psiFile.virtualFile) + WorkspaceModelGenerator.getInstance(project).generate(module!!) + } + + override fun startInWriteAction(): Boolean = false + } +} \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/src/WorkspaceEntityMutableFieldInspection.kt b/plugins/devkit/intellij.devkit.workspaceModel/src/inspections/WorkspaceEntityMutableFieldInspection.kt similarity index 83% rename from plugins/devkit/intellij.devkit.workspaceModel/src/WorkspaceEntityMutableFieldInspection.kt rename to plugins/devkit/intellij.devkit.workspaceModel/src/inspections/WorkspaceEntityMutableFieldInspection.kt index 492e5218f2cc..cdabecfc99df 100644 --- a/plugins/devkit/intellij.devkit.workspaceModel/src/WorkspaceEntityMutableFieldInspection.kt +++ b/plugins/devkit/intellij.devkit.workspaceModel/src/inspections/WorkspaceEntityMutableFieldInspection.kt @@ -1,7 +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 +// 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.inspections import com.intellij.codeInspection.* +import com.intellij.devkit.workspaceModel.DevKitWorkspaceModelBundle import com.intellij.openapi.project.Project import com.intellij.openapi.util.IntellijInternalApi import org.jetbrains.annotations.ApiStatus @@ -15,10 +16,9 @@ import org.jetbrains.kotlin.psi.KtVisitorVoid @IntellijInternalApi @ApiStatus.Internal class WorkspaceEntityMutableFieldInspection: LocalInspectionTool() { - override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = object : KtVisitorVoid() { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): KtVisitorVoid = object : KtVisitorVoid() { override fun visitClass(klass: KtClass) { - if (!klass.isWorkspaceEntity()) return - if (klass.name == "Builder") return + if (!klass.isWorkspaceEntityDeclaration()) return klass.getProperties().forEach { property -> if (property.isVar) { holder.registerProblem(property, DevKitWorkspaceModelBundle.message("inspection.workspace.mutable.field.display.name"), diff --git a/plugins/devkit/intellij.devkit.workspaceModel/src/inspections/WorkspaceImplObsoleteInspectionBase.kt b/plugins/devkit/intellij.devkit.workspaceModel/src/inspections/WorkspaceImplObsoleteInspectionBase.kt new file mode 100644 index 000000000000..61cad9b06b79 --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/src/inspections/WorkspaceImplObsoleteInspectionBase.kt @@ -0,0 +1,91 @@ +// 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.inspections + +import com.intellij.codeInspection.LocalQuickFixOnPsiElement +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.devkit.workspaceModel.DevKitWorkspaceModelBundle +import com.intellij.devkit.workspaceModel.WorkspaceModelGenerator +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ProjectRootManager +import com.intellij.platform.workspace.storage.CodeGeneratorVersions +import com.intellij.platform.workspace.storage.GeneratedCodeApiVersion +import com.intellij.platform.workspace.storage.GeneratedCodeImplVersion +import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiLiteralExpression +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.KtNodeTypes +import org.jetbrains.kotlin.parsing.parseNumericLiteral +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtConstantExpression +import org.jetbrains.kotlin.psi.KtVisitorVoid + +private val LOG = logger() + +abstract class WorkspaceImplObsoleteInspectionBase : WorkspaceInspectionBase() { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): KtVisitorVoid = object : KtVisitorVoid() { + override fun visitClass(klass: KtClass) { + val psiElementForHighlighting = klass.getPsiElementForHighlighting() ?: return + if (!klass.isWorkspaceEntityDeclaration()) return + val foundImplClass = findWorkspaceEntityImplementation(klass, getModuleSearchScope(klass)) + if (foundImplClass == null) return + + val targetApiVersion = calculateTargetApiVersion(klass.resolveScope, klass.project) + if (targetApiVersion == null) { + LOG.info("Can't evaluate target API version for ${klass.name}") + return + } + + + val implApiVersion = foundImplClass.getApiVersion() + if (implApiVersion == targetApiVersion) return + holder.registerProblem(psiElementForHighlighting, DevKitWorkspaceModelBundle.message("inspection.workspace.msg.obsolete.implementation"), + RegenerateWorkspaceModelFix(psiElementForHighlighting)) + } + } + + private fun calculateTargetApiVersion(scope: GlobalSearchScope, project: Project): Int? { + val generatorVersionsClass = JavaPsiFacade.getInstance(project).findClass(CodeGeneratorVersions::class.java.name, scope) ?: return null + val versionField = generatorVersionsClass.findFieldByName("API_VERSION_INTERNAL", false) ?: return null + return (versionField.initializer as? PsiLiteralExpression)?.value as? Int + } + + private class RegenerateWorkspaceModelFix(psiElement: PsiElement) : LocalQuickFixOnPsiElement(psiElement) { + override fun getText() = DevKitWorkspaceModelBundle.message("inspection.workspace.msg.regenerate.implementation") + + override fun getFamilyName() = name + + override fun invoke(project: Project, psiFile: PsiFile, startElement: PsiElement, endElement: PsiElement) { + val projectFileIndex = ProjectRootManager.getInstance(project).fileIndex + val module = projectFileIndex.getModuleForFile(psiFile.virtualFile) + WorkspaceModelGenerator.getInstance(project).generate(module!!) + } + } +} + +private fun KtClass.getGeneratedAnnotationVersion(annotationFqName: String): Int? { + val annotation = findAnnotation(annotationFqName) ?: return null + if (annotation.valueArguments.size != 1) { + LOG.warn("Annotation $annotationFqName at $name should contain exactly one argument") + return null + } + val argumentExpression = annotation.valueArguments[0].getArgumentExpression() as? KtConstantExpression + val elementType = argumentExpression?.node?.elementType + if (elementType != KtNodeTypes.INTEGER_CONSTANT) { + LOG.warn("Annotation parameter of $annotationFqName at $name should be an int constant") + return null + } + return parseNumericLiteral(argumentExpression.text, elementType)?.toInt() +} + +private fun KtClass.getApiVersion(): Int? { + val annotationFqName = GeneratedCodeApiVersion::class.qualifiedName!! + return getGeneratedAnnotationVersion(annotationFqName) +} + +private fun KtClass.getImplVersion(): Int? { + val annotationFqName = GeneratedCodeImplVersion::class.simpleName!! + return getGeneratedAnnotationVersion(annotationFqName) +} \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/src/inspections/WorkspaceInheritanceInspection.kt b/plugins/devkit/intellij.devkit.workspaceModel/src/inspections/WorkspaceInheritanceInspection.kt new file mode 100644 index 000000000000..b446adca2a93 --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/src/inspections/WorkspaceInheritanceInspection.kt @@ -0,0 +1,61 @@ +// 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.inspections + +import com.intellij.codeInspection.LocalInspectionTool +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.devkit.workspaceModel.DevKitWorkspaceModelBundle +import com.intellij.openapi.util.IntellijInternalApi +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.annotations.VisibleForTesting +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtVisitorVoid + +@VisibleForTesting +@IntellijInternalApi +@ApiStatus.Internal +class WorkspaceInheritanceInspection : LocalInspectionTool() { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): KtVisitorVoid = object : KtVisitorVoid() { + override fun visitClassOrObject(klass: KtClassOrObject) { + val psiForHighlighting = klass.getPsiElementForHighlighting() ?: return + if (klass !is KtClass) return + if (!klass.isWorkspaceEntity()) return + if (klass.isWorkspaceEntityImplementation()) return + if (klass.isWorkspaceEntitySource()) { + holder.registerProblem(psiForHighlighting, + DevKitWorkspaceModelBundle.message("inspection.workspace.msg.entity.and.source.inheritance"), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING) + return + } + + if (!klass.isInterface()) { + holder.registerProblem(psiForHighlighting, + DevKitWorkspaceModelBundle.message("inspection.workspace.msg.user.implementation"), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING) + return + } + + val visited = mutableSetOf() + val superEntities = mutableSetOf() + for (superType in klass.superTypeListEntries) { + val resolvedSuper = superType.typeReference?.resolveToKtClass() ?: continue + if (!visited.add(resolvedSuper)) continue + if (!resolvedSuper.isWorkspaceEntity()) continue + if (resolvedSuper.isAbstractEntity()) { + superEntities.add(resolvedSuper) + continue + } + holder.registerProblem(psiForHighlighting, + DevKitWorkspaceModelBundle.message("inspection.workspace.msg.non.abstract.inheritance"), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING) + return + } + if (superEntities.size > 1) { + holder.registerProblem(psiForHighlighting, + DevKitWorkspaceModelBundle.message("inspection.workspace.msg.multiple.inheritance"), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING) + } + } + } +} diff --git a/plugins/devkit/intellij.devkit.workspaceModel/src/inspections/WorkspaceInspectionBase.kt b/plugins/devkit/intellij.devkit.workspaceModel/src/inspections/WorkspaceInspectionBase.kt new file mode 100644 index 000000000000..ff1590f7faf4 --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/src/inspections/WorkspaceInspectionBase.kt @@ -0,0 +1,10 @@ +// 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.inspections + +import com.intellij.codeInspection.LocalInspectionTool +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.psi.KtClassOrObject + +abstract class WorkspaceInspectionBase : LocalInspectionTool() { + protected abstract fun getModuleSearchScope(ktClass: KtClassOrObject): GlobalSearchScope +} \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/src/inspections/WorkspaceKtClassExtentions.kt b/plugins/devkit/intellij.devkit.workspaceModel/src/inspections/WorkspaceKtClassExtentions.kt new file mode 100644 index 000000000000..84c964e9892f --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/src/inspections/WorkspaceKtClassExtentions.kt @@ -0,0 +1,111 @@ +// 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.inspections + +import com.intellij.platform.workspace.storage.EntitySource +import com.intellij.platform.workspace.storage.SymbolicEntityId +import com.intellij.platform.workspace.storage.WorkspaceEntity +import com.intellij.platform.workspace.storage.annotations.Abstract +import com.intellij.platform.workspace.storage.impl.WorkspaceEntityBase +import com.intellij.psi.PsiElement +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.util.CachedValueProvider +import com.intellij.psi.util.CachedValuesManager +import com.intellij.psi.util.PsiModificationTracker +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.idea.stubindex.KotlinClassShortNameIndex +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.containingClass +import java.util.* +import java.util.function.Predicate + +private val workspaceModelClasses: List = listOfNotNull( + WorkspaceEntity::class.qualifiedName, + EntitySource::class.qualifiedName, + SymbolicEntityId::class.qualifiedName, + WorkspaceEntity.Builder::class.qualifiedName, + WorkspaceEntityBase::class.qualifiedName +) + +/** + * Finds which [workspaceModelClasses] this KtClassOrObject inherits and caches the result. + * + * @return set of parent classes' fully qualified names + */ +internal fun KtClassOrObject.getWorkspaceSupers(): Set { + return CachedValuesManager.getCachedValue(this) { + val workspaceSupersSequence = getMatchingSuperTypes { ktClass -> + workspaceModelClasses.contains(ktClass.fqName?.asString()) + } + val workspaceSupers = workspaceSupersSequence.mapNotNull { it.fqName?.asString() }.toSet() + CachedValueProvider.Result(workspaceSupers, PsiModificationTracker.MODIFICATION_COUNT) + } +} + +internal fun KtTypeReference.resolveToKtClass(): KtClass? { + val resolvedReference = (typeElement as? KtUserType)?.referenceExpression?.mainReference?.resolve() + return when (resolvedReference) { + is KtClass -> resolvedReference + is KtConstructor<*> -> resolvedReference.containingClass() + else -> null + } +} + +internal fun KtClassOrObject.getMatchingSuperTypes(predicate: Predicate): Sequence = sequence { + val visited = mutableSetOf() + val superTypeList = LinkedList() + superTypeList.addAll(superTypeListEntries) + while (!superTypeList.isEmpty()) { + val superType = superTypeList.pop() + val resolvedKtClass = superType.typeReference?.resolveToKtClass() ?: continue + if (!visited.add(resolvedKtClass)) continue + if (predicate.test(resolvedKtClass)) yield(resolvedKtClass) + resolvedKtClass.superTypeListEntries.forEach { superTypeList.push(it) } + } +} + +internal fun findWorkspaceEntityImplementation(entityClass: KtClass, searchScope: GlobalSearchScope): KtClass? { + val thisFqName = entityClass.fqName?.asString() ?: return null + val foundImplClasses = KotlinClassShortNameIndex["${entityClass.name}Impl", entityClass.project, searchScope] + val foundEntityImpls = foundImplClasses.filter { it is KtClass && it.isWorkspaceEntityImplementation() } + val foundImpl = foundEntityImpls.find { someImpl -> someImpl.getMatchingSuperTypes { it.fqName?.asString() == thisFqName }.any() } + return foundImpl as? KtClass +} + +internal fun KtClass.isWorkspaceEntity(): Boolean { + return getWorkspaceSupers().contains(WorkspaceEntity::class.qualifiedName) +} + +/** + * Check that a class is an **interface** that extends WorkspaceEntity, but not WorkspaceEntity.Builder, meaning that it is a declaration of + * an entity. + */ +internal fun KtClass.isWorkspaceEntityDeclaration(): Boolean { + if (!isInterface()) return false + val workspaceSupers = getWorkspaceSupers() + return workspaceSupers.contains(WorkspaceEntity::class.qualifiedName) && + !workspaceSupers.contains(WorkspaceEntity.Builder::class.qualifiedName) +} + +internal fun KtClass.isWorkspaceEntityImplementation(): Boolean { + val workspaceSupers = getWorkspaceSupers() + return workspaceSupers.contains(WorkspaceEntity::class.qualifiedName) && workspaceSupers.contains(WorkspaceEntityBase::class.qualifiedName) +} + +internal fun KtClassOrObject.isWorkspaceEntitySource(): Boolean { + return getWorkspaceSupers().contains(EntitySource::class.qualifiedName) +} + +internal fun KtClass.findAnnotation(annotationFqName: String): KtAnnotationEntry? { + for (annotationEntry in annotationEntries) { + val annotationClass = annotationEntry.typeReference?.resolveToKtClass() ?: continue + if (annotationClass.fqName?.asString() == annotationFqName) return annotationEntry + } + return null +} + +internal fun KtClass.isAbstractEntity(): Boolean { + val annotationFqName = Abstract::class.qualifiedName!! + return findAnnotation(annotationFqName) != null +} + +internal fun KtClassOrObject.getPsiElementForHighlighting(): PsiElement? = nameIdentifier ?: firstChild \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/entityImplementation/entity.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/entityImplementation/entity.kt new file mode 100644 index 000000000000..9a11a636ed03 --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/entityImplementation/entity.kt @@ -0,0 +1,29 @@ +package com.intellij.workspaceModel.test.api + +import com.intellij.platform.workspace.storage.WorkspaceEntity +import com.intellij.platform.workspace.storage.impl.WorkspaceEntityBase + +interface EntityWithoutImplementation : WorkspaceEntity { + val property: String + val isValid: Boolean +} + +interface EntityWithFakeImplementation : WorkspaceEntity { + val property: String + val isValid: Boolean +} + +internal class EntityWithFakeImplementationImpl() : EntityWithFakeImplementation { + override val property: String = "" + override val isValid: Boolean = false +} + +interface EntityWithImplementation : WorkspaceEntity { + val property: String + val isValid: Boolean +} + +internal class EntityWithImplementationImpl() : EntityWithImplementation, WorkspaceEntityBase() { + override val property: String = "" + override val isValid: Boolean = false +} \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/entitySourceFakeMetadata/entity.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/entitySourceFakeMetadata/entity.kt new file mode 100644 index 000000000000..1531327aeb6d --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/entitySourceFakeMetadata/entity.kt @@ -0,0 +1,35 @@ +package com.intellij.workspaceModel.test.api + +import com.intellij.platform.workspace.storage.EntitySource + +// Fake MetadataStorageBase +abstract class MetadataStorageBase() { + @Suppress("UNUSED_PARAMETER") + protected fun addMetadataHash(typeFqn: String, metadataHash: Int) { + } + + protected abstract fun initializeMetadataHash() +} + +internal object MetadataStorageImpl : MetadataStorageBase() { + override fun initializeMetadataHash() { + addMetadataHash(typeFqn = "com.intellij.platform.workspace.storage.EntitySource", metadataHash = 0) + addMetadataHash(typeFqn = "com.intellij.workspaceModel.test.api.PresentObjectSource", metadataHash = 0) + addMetadataHash(typeFqn = "com.intellij.workspaceModel.test.api.PresentClassSource", metadataHash = 0) + addMetadataHash(typeFqn = "com.intellij.workspaceModel.test.api.PresentInterfaceSource", metadataHash = 0) + addMetadataHash(typeFqn = "com.intellij.workspaceModel.test.api.PresentInheritedPresentSource", metadataHash = 0) + addMetadataHash(typeFqn = "com.intellij.workspaceModel.test.api.OuterClass1\$PresentInnerEntitySource", metadataHash = 0) + } +} + +object PresentObjectSource : EntitySource + +class PresentClassSource : EntitySource + +interface PresentInterfaceSource : EntitySource + +class PresentInheritedPresentSource : PresentInterfaceSource + +class OuterClass1 { + class PresentInnerEntitySource : EntitySource +} diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/entitySourceMetadata/entity.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/entitySourceMetadata/entity.kt new file mode 100644 index 000000000000..fec8f1915a09 --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/entitySourceMetadata/entity.kt @@ -0,0 +1,49 @@ +package com.intellij.workspaceModel.test.api + +import com.intellij.platform.workspace.storage.EntitySource +import com.intellij.platform.workspace.storage.metadata.impl.MetadataStorageBase + +// Present in metadata + +internal object MetadataStorageImpl: MetadataStorageBase() { + override fun initializeMetadataHash() { + addMetadataHash(typeFqn = "com.intellij.platform.workspace.storage.EntitySource", metadataHash = 0) + addMetadataHash(typeFqn = "com.intellij.workspaceModel.test.api.PresentObjectSource", metadataHash = 0) + addMetadataHash(typeFqn = "com.intellij.workspaceModel.test.api.PresentClassSource", metadataHash = 0) + addMetadataHash(typeFqn = "com.intellij.workspaceModel.test.api.PresentInterfaceSource", metadataHash = 0) + addMetadataHash(typeFqn = "com.intellij.workspaceModel.test.api.PresentInheritedPresentSource", metadataHash = 0) + addMetadataHash(typeFqn = "com.intellij.workspaceModel.test.api.OuterClass1\$PresentInnerEntitySource", metadataHash = 0) + addMetadataHash(typeFqn = "com.intellij.workspaceModel.test.api.PresentInheritedAbsentSource", metadataHash = 0) + addMetadataHash(typeFqn = "com.intellij.workspaceModel.test.api.PresentRecursive1", metadataHash = 0) + addMetadataHash(typeFqn = "com.intellij.workspaceModel.test.api.PresentRecursive2", metadataHash = 0) + addMetadataHash(typeFqn = "com.intellij.workspaceModel.test.api.PresentRecursiveParentAbsent", metadataHash = 0) + } +} + +object PresentObjectSource : EntitySource + +class PresentClassSource : EntitySource + +interface PresentInterfaceSource : EntitySource + +class PresentInheritedPresentSource : PresentInterfaceSource + +class OuterClass1 { + class PresentInnerEntitySource : EntitySource +} + +// Absent in metadata + +object AbsentObjectSource : EntitySource + +class AbsentClassSource : EntitySource + +interface AbsentInterfaceSource : EntitySource + +class AbsentInheritedAbsentSource : AbsentInterfaceSource + +class PresentInheritedAbsentSource : AbsentInterfaceSource + +class OuterClass2 { + class AbsentInnerEntitySource : EntitySource +} \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/inheritance/entity.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/inheritance/entity.kt new file mode 100644 index 000000000000..f7815b7ef284 --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/inheritance/entity.kt @@ -0,0 +1,54 @@ +package com.intellij.workspaceModel.test.api + +import com.intellij.platform.workspace.storage.WorkspaceEntity +import com.intellij.platform.workspace.storage.EntitySource +import com.intellij.platform.workspace.storage.annotations.Abstract +import com.intellij.platform.workspace.storage.impl.WorkspaceEntityBase + +interface EntityWithFakeImplementation : WorkspaceEntity { + val property: String + val isValid: Boolean +} + +internal class EntityWithFakeImplementationImpl : EntityWithFakeImplementation { + override val property: String = "" + override val isValid: Boolean = false +} + +interface EntityWithImplementation : WorkspaceEntity { + val property: String + val isValid: Boolean +} + +internal class EntityWithImplementationImpl() : EntityWithImplementation, WorkspaceEntityBase() { + override val property: String = "" + override val isValid: Boolean = false +} + +interface OnlySource : EntitySource + +object AnotherOnlySource : EntitySource + +interface EntityAndSource : WorkspaceEntity, EntitySource { + val property: String +} + +interface SomeEntity : WorkspaceEntity + +interface InheritsNonAbstract : SomeEntity + +interface : SomeEntity + +@Abstract +interface AbstractEntity1 : WorkspaceEntity + +interface NonAbstractEntity1 : AbstractEntity1 + +@Abstract +interface AbstractEntity2 : WorkspaceEntity + +interface NonAbstractEntity2 : AbstractEntity2 + +interface NonAbstractEntity3 : AbstractEntity1, AbstractEntity2 + +interface NonAbstractEntity4 : AbstractEntity1, NonAbstractEntity2 \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/notWorkspaceAbstract/entity.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/notWorkspaceAbstract/entity.kt new file mode 100644 index 000000000000..e072e6c728eb --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/notWorkspaceAbstract/entity.kt @@ -0,0 +1,16 @@ +package com.intellij.workspaceModel.test.api + +import com.intellij.platform.workspace.storage.WorkspaceEntity + +@Target(AnnotationTarget.CLASS) +annotation class Abstract + +@Abstract +interface NotWorkspaceAbstract : WorkspaceEntity + +interface SomeEntity : NotWorkspaceAbstract + +@com.intellij.platform.workspace.storage.annotations.Abstract +interface WorkspaceAbstract : WorkspaceEntity + +interface AnotherEntity : WorkspaceAbstract \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/notWorkspaceClasses/entity.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/notWorkspaceClasses/entity.kt new file mode 100644 index 000000000000..1932a2192e8b --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/notWorkspaceClasses/entity.kt @@ -0,0 +1,29 @@ +package com.intellij.workspaceModel.test.api + +import com.intellij.platform.workspace.storage.impl.WorkspaceEntityBase + +interface WorkspaceEntity + +interface NotWorkspaceEntity : WorkspaceEntity { + var property: String +} + +interface AnotherNotWorkspaceEntity : WorkspaceEntity { + var flag: Boolean +} + +interface EntitySource + +object NotWorkspaceSource : EntitySource + +class AnotherNotWorkspaceSource : EntitySource + +interface NotWorkspaceMultipleInheritance : NotWorkspaceEntity, AnotherNotWorkspaceEntity, EntitySource + +@Target(AnnotationTarget.CLASS) +annotation class GeneratedCodeApiVersion(val version: Int) + +interface EntityWithOboleteImplementation : WorkspaceEntity + +@GeneratedCodeApiVersion(2) +internal class EntityWithOboleteImplementationImpl() : EntityWithOboleteImplementation, WorkspaceEntityBase() \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/obsoleteImplementation/entity.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/obsoleteImplementation/entity.kt new file mode 100644 index 000000000000..ae3bf59c083d --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/obsoleteImplementation/entity.kt @@ -0,0 +1,33 @@ +package com.intellij.workspaceModel.test.api + +import com.intellij.platform.workspace.storage.GeneratedCodeApiVersion +import com.intellij.platform.workspace.storage.WorkspaceEntity +import com.intellij.platform.workspace.storage.annotations.Abstract +import com.intellij.platform.workspace.storage.impl.WorkspaceEntityBase + +interface EntityWithFakeImplementation : WorkspaceEntity + +internal class EntityWithFakeImplementationImpl() : EntityWithFakeImplementation + +interface EntityWithOboleteImplementation : WorkspaceEntity + +@GeneratedCodeApiVersion(2) +internal class EntityWithOboleteImplementationImpl() : EntityWithOboleteImplementation, WorkspaceEntityBase() + +interface EntityWithCorrectImplementation : WorkspaceEntity + +@GeneratedCodeApiVersion(3) +internal class EntityWithCorrectImplementationImpl() : EntityWithCorrectImplementation, WorkspaceEntityBase() + +@Abstract +interface AbstractEntity : WorkspaceEntity + +interface AnotherEntityWithOboleteImplementation : AbstractEntity + +@GeneratedCodeApiVersion(2) +internal class AnotherEntityWithOboleteImplementationImpl() : AnotherEntityWithOboleteImplementation, WorkspaceEntityBase() + +interface AnotherEntityWithCorrectImplementation : WorkspaceEntity + +@GeneratedCodeApiVersion(3) +internal class AnotherEntityWithCorrectImplementationImpl() : AnotherEntityWithCorrectImplementation, WorkspaceEntityBase() \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/varFieldForbidden/entity.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/varFieldForbidden/entity.kt index 6b351ca1521f..ca63b689e7e0 100644 --- a/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/varFieldForbidden/entity.kt +++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/testData/inspections/varFieldForbidden/entity.kt @@ -4,5 +4,5 @@ import com.intellij.platform.workspace.storage.WorkspaceEntity interface MainEntity : WorkspaceEntity { val property: String - var isValid: Boolean + var isValid: Boolean } \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/inspections/WorkspaceCodeAbsentInspectionBaseTest.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/inspections/WorkspaceCodeAbsentInspectionBaseTest.kt new file mode 100644 index 000000000000..7a061543de71 --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/inspections/WorkspaceCodeAbsentInspectionBaseTest.kt @@ -0,0 +1,24 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +@file:OptIn(IntellijInternalApi::class) + +package com.intellij.devkit.workspaceModel.inspections + +import com.intellij.openapi.util.IntellijInternalApi + +abstract class WorkspaceCodeAbsentInspectionBaseTest : WorkspaceInspectionBaseTest() { + fun testEntityImplementation() { + doTest() + } + + fun testEntitySourceMetadata() { + doTest() + } + + fun testEntitySourceFakeMetadata() { + doTest() + } + + fun testNotWorkspaceClasses() { + doTest() + } +} \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/inspections/WorkspaceEntityInspectionBase.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/inspections/WorkspaceEntityInspectionBase.kt deleted file mode 100644 index be1bee205e3e..000000000000 --- a/plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/inspections/WorkspaceEntityInspectionBase.kt +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.intellij.devkit.workspaceModel.inspections - -import com.intellij.openapi.application.PluginPathManager -import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase - -abstract class WorkspaceEntityInspectionBase: LightJavaCodeInsightFixtureTestCase() { - private val TESTDATA_PATH = PluginPathManager.getPluginHomePathRelative("devkit") + "/intellij.devkit.workspaceModel/tests/testData/inspections/" - - override fun setUp() { - super.setUp() - myFixture.createFile("Obj.kt", """ - package com.intellij.platform.workspace.storage - - interface Obj""".trimIndent()) - myFixture.createFile("WorkspaceEntity.kt", """ - package com.intellij.platform.workspace.storage - - import com.intellij.platform.workspace.storage.Obj - - interface WorkspaceEntity : Obj""".trimIndent()) - } - - override fun getBasePath() = TESTDATA_PATH - - protected open fun doTest(fixName: String) { - val testName = getTestName(true) - val fileNameBefore = "$testName/entity.kt" - val fileNameAfter = "${testName}/entity_after.kt" - myFixture.testHighlighting(fileNameBefore) - val intention = myFixture.findSingleIntention(fixName) - myFixture.checkPreviewAndLaunchAction(intention) - myFixture.checkResultByFile(fileNameBefore, fileNameAfter, true) - } -} \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/inspections/WorkspaceEntityMutableFieldInspectionTest.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/inspections/WorkspaceEntityMutableFieldInspectionBaseTest.kt similarity index 69% rename from plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/inspections/WorkspaceEntityMutableFieldInspectionTest.kt rename to plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/inspections/WorkspaceEntityMutableFieldInspectionBaseTest.kt index 005dba715074..0f46bff4951d 100644 --- a/plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/inspections/WorkspaceEntityMutableFieldInspectionTest.kt +++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/inspections/WorkspaceEntityMutableFieldInspectionBaseTest.kt @@ -3,16 +3,19 @@ package com.intellij.devkit.workspaceModel.inspections -import com.intellij.devkit.workspaceModel.WorkspaceEntityMutableFieldInspection import com.intellij.openapi.util.IntellijInternalApi -class WorkspaceEntityMutableFieldInspectionTest: WorkspaceEntityInspectionBase() { +abstract class WorkspaceEntityMutableFieldInspectionBaseTest : WorkspaceInspectionBaseTest() { override fun setUp() { super.setUp() myFixture.enableInspections(WorkspaceEntityMutableFieldInspection()) } fun testVarFieldForbidden() { - doTest("Change to 'val'") + doTestWithQuickFix("Change to 'val'") + } + + fun testNotWorkspaceClasses() { + doTest() } } \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/inspections/WorkspaceImplObsoleteInspectionBaseTest.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/inspections/WorkspaceImplObsoleteInspectionBaseTest.kt new file mode 100644 index 000000000000..0f9d69eb92bf --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/inspections/WorkspaceImplObsoleteInspectionBaseTest.kt @@ -0,0 +1,16 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +@file:OptIn(IntellijInternalApi::class) + +package com.intellij.devkit.workspaceModel.inspections + +import com.intellij.openapi.util.IntellijInternalApi + +abstract class WorkspaceImplObsoleteInspectionBaseTest : WorkspaceInspectionBaseTest() { + fun testObsoleteImplementation() { + doTest() + } + + fun testNotWorkspaceClasses() { + doTest() + } +} \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/inspections/WorkspaceInheritanceInspectionBaseTest.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/inspections/WorkspaceInheritanceInspectionBaseTest.kt new file mode 100644 index 000000000000..ccb69c1e017e --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/inspections/WorkspaceInheritanceInspectionBaseTest.kt @@ -0,0 +1,25 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +@file:OptIn(IntellijInternalApi::class) + +package com.intellij.devkit.workspaceModel.inspections + +import com.intellij.openapi.util.IntellijInternalApi + +abstract class WorkspaceInheritanceInspectionBaseTest : WorkspaceInspectionBaseTest() { + override fun setUp() { + super.setUp() + myFixture.enableInspections(WorkspaceInheritanceInspection()) + } + + fun testInheritance() { + doTest() + } + + fun testNotWorkspaceAbstract() { + doTest() + } + + fun testNotWorkspaceClasses() { + doTest() + } +} \ No newline at end of file diff --git a/plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/inspections/WorkspaceInspectionBaseTest.kt b/plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/inspections/WorkspaceInspectionBaseTest.kt new file mode 100644 index 000000000000..255090d635db --- /dev/null +++ b/plugins/devkit/intellij.devkit.workspaceModel/tests/testSrc/inspections/WorkspaceInspectionBaseTest.kt @@ -0,0 +1,79 @@ +// 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.inspections + +import com.intellij.openapi.application.PluginPathManager +import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase +import org.intellij.lang.annotations.Language + +abstract class WorkspaceInspectionBaseTest : LightJavaCodeInsightFixtureTestCase() { + override fun getBasePath() = TESTDATA_PATH + + private fun getBeforeAndAfterFileNames(): Pair { + val testName = getTestName(true) + val fileNameBefore = "$testName/entity.kt" + val fileNameAfter = "${testName}/entity_after.kt" + return fileNameBefore to fileNameAfter + } + + protected fun addKotlinFile(relativePath: String, @Language("kotlin") fileText: String) { + myFixture.addFileToProject(relativePath, fileText) + } + + override fun setUp() { + super.setUp() + addKotlinFile("EntitySource.kt", """ + package com.intellij.platform.workspace.storage + interface EntitySource + """.trimIndent()) + addKotlinFile("Abstract.kt", """ + package com.intellij.platform.workspace.storage.annotations + @Target(AnnotationTarget.CLASS) + annotation class Abstract + """.trimIndent()) + addKotlinFile("generatedCodeCompatibility.kt", """ + package com.intellij.platform.workspace.storage + object CodeGeneratorVersions { + private const val API_VERSION_INTERNAL = 3 + } + @Target(AnnotationTarget.CLASS) + @Retention(AnnotationRetention.RUNTIME) + annotation class GeneratedCodeApiVersion(val version: Int) + """.trimIndent()) + addKotlinFile("WorkspaceEntity.kt", """ + package com.intellij.platform.workspace.storage + import com.intellij.platform.workspace.storage.annotations.Abstract + @Abstract + interface WorkspaceEntity + """.trimIndent()) + addKotlinFile("MetadataStorageBase.kt", """ + package com.intellij.platform.workspace.storage.metadata.impl + abstract class MetadataStorageBase() { + protected fun addMetadataHash(typeFqn: String, metadataHash: Int) {} + protected abstract fun initializeMetadataHash() + } + """.trimIndent()) + addKotlinFile("WorkspaceEntityBase.kt", """ + package com.intellij.platform.workspace.storage.impl + import com.intellij.platform.workspace.storage.WorkspaceEntity + abstract class WorkspaceEntityBase() : WorkspaceEntity + """.trimIndent()) + } + + protected fun doTestWithQuickFix(fixName: String) { + val (fileNameBefore, fileNameAfter) = getBeforeAndAfterFileNames() + myFixture.testHighlighting(fileNameBefore) + val quickFix = myFixture.getAllQuickFixes().find { it.text == fixName } + assertNotNull("Fix $fixName not found", quickFix) + myFixture.checkPreviewAndLaunchAction(quickFix!!) + myFixture.checkResultByFile(fileNameBefore, fileNameAfter, true) + } + + protected fun doTest() { + val (fileNameBefore, _) = getBeforeAndAfterFileNames() + myFixture.testHighlighting(fileNameBefore) + } + + companion object { + private val TESTDATA_PATH = PluginPathManager.getPluginHomePathRelative("devkit") + "/intellij.devkit.workspaceModel/tests/testData/inspections/" + } +} \ No newline at end of file