[devkit] IDEA-298703 improve naming, add a check for backing fields in kotlin

GitOrigin-RevId: 5e74319e790c4246b64e6d5f57c51c6930dbf19f
This commit is contained in:
Elena Lyulina
2023-08-20 02:21:34 +02:00
committed by intellij-monorepo-bot
parent da2793fc89
commit 4701e0fd21
42 changed files with 158 additions and 93 deletions

View File

@@ -1,15 +1,17 @@
<html>
<body>
Reports assignments of application services to static final fields/properties.
Reports assignments of application services to static final fields / immutable properties.
<p><b>Static final fields (Java) or immutable properties with backing fields (Kotlin)</b></p>
<p>
Such assignments contribute to global state and make it impossible to tear down an application and set up another one in tests,
Such services' assignments contribute to global state and make it impossible to tear down an application and set up another one in tests,
therefore, repeated tests in the same process may fail.
The only exception is an explicit constructor call to store dummy/default instances.
</p>
The only exception is an explicit constructor call to store dummy/default instances.</p>
<p>
The recommended way to avoid storing services is to retrieve a service locally or to wrap it in <code>java.util.function.Supplier</code>.
The recommended way to avoid storing services is to retrieve a service locally.
Alternatively, one can wrap it in <code>java.util.function.Supplier</code> (Java, Kotlin)
or convert the property to a function (Kotlin).
</p>
<p><b>Example:</b></p>
<p><b>Example (Java):</b></p>
<pre><code lang="java">
// Incorrect way
private static final ManagingFS ourInstance = ApplicationManager.getApplication().getService(ManagingFS.class);
@@ -24,6 +26,6 @@ Reports assignments of application services to static final fields/properties.
// Exception
private static final UniqueVFilePathBuilder DUMMY_BUILDER = new UniqueVFilePathBuilder()
</code></pre>
<p><small>New in 2023.2</small>
<p><small>New in 2023.3</small>
</body>
</html>

View File

@@ -367,11 +367,11 @@
enabledByDefault="false" level="WARNING"
implementationClass="org.jetbrains.idea.devkit.inspections.CancellationCheckInLoopsInspection"/>
<localInspection language="UAST" shortName="ApplicationServiceAsStaticFinalField" groupPathKey="inspections.group.path"
<localInspection language="UAST" shortName="ApplicationServiceAsStaticFinalFieldOrProperty" groupPathKey="inspections.group.path"
projectType="INTELLIJ_PLUGIN"
key="inspections.application.service.as.static.final.field.display.name" groupKey="inspections.group.code"
key="inspections.application.service.as.static.final.field.or.property.display.name" groupKey="inspections.group.code"
enabledByDefault="false" level="WARNING"
implementationClass="org.jetbrains.idea.devkit.inspections.ApplicationServiceAsStaticFinalFieldInspection"/>
implementationClass="org.jetbrains.idea.devkit.inspections.ApplicationServiceAsStaticFinalFieldOrPropertyInspection"/>
<localInspection language="UAST" enabledByDefault="false" level="ERROR"
projectType="INTELLIJ_PLUGIN"
@@ -538,9 +538,9 @@
implementationClass="org.jetbrains.idea.devkit.inspections.ServiceLevelExtractorForJVM"/>
<lightServiceMustBeFinalErrorMessageProvider language="JVM"
implementationClass="org.jetbrains.idea.devkit.inspections.LightServiceMustBeFinalErrorMessageProvider"/>
<appServiceAsStaticFinalFieldQuickFixProvider
<appServiceAsStaticFinalFieldOrPropertyProvider
language="JAVA"
implementationClass="org.jetbrains.idea.devkit.inspections.quickfix.JavaAppServiceAsStaticFinalFieldFixProvider"/>
implementationClass="org.jetbrains.idea.devkit.inspections.quickfix.JavaAppServiceAsStaticFinalFieldOrPropertyProvider"/>
</extensions>
<extensionPoints>
@@ -584,10 +584,10 @@
dynamic="true">
<with attribute="implementationClass" implements="org.jetbrains.idea.devkit.inspections.ErrorMessageProvider"/>
</extensionPoint>
<extensionPoint qualifiedName="DevKit.lang.appServiceAsStaticFinalFieldQuickFixProvider"
<extensionPoint qualifiedName="DevKit.lang.appServiceAsStaticFinalFieldOrPropertyProvider"
beanClass="com.intellij.lang.LanguageExtensionPoint"
dynamic="true">
<with attribute="implementationClass" implements="org.jetbrains.idea.devkit.inspections.quickfix.AppServiceAsStaticFinalFieldFixProvider"/>
<with attribute="implementationClass" implements="org.jetbrains.idea.devkit.inspections.quickfix.AppServiceAsStaticFinalFieldOrPropertyProvider"/>
</extensionPoint>
<extensionPoint qualifiedName="DevKit.lang.uElementAsPsiCheckProvider"
beanClass="com.intellij.lang.LanguageExtensionPoint"

View File

@@ -657,7 +657,7 @@ inspection.cancellation.check.in.loops.display.name=Cancellation check in loops
inspection.cancellation.check.in.loops.message=Cancellation check ''{0}'' should be the first statement in a loop body
inspection.insert.cancellation.check.fix.message=Insert cancellation check
inspections.application.service.as.static.final.field.display.name=Application service assigned to a static final field/property
inspections.application.service.as.static.final.field.or.property.display.name=Application service assigned to a static final field / immutable property
inspections.application.service.as.static.final.field.message=Application service must not be assigned to a static final field
inspections.wrap.application.service.in.supplier.quick.fix.message=Wrap application service in 'java.util.function.Supplier'

View File

@@ -2,17 +2,15 @@
package org.jetbrains.idea.devkit.inspections
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.codeInspection.registerUProblem
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.util.PsiTypesUtil
import com.intellij.uast.UastHintedVisitorAdapter
import org.jetbrains.idea.devkit.DevKitBundle
import org.jetbrains.idea.devkit.inspections.quickfix.AppServiceAsStaticFinalFieldQuickFixProviders
import org.jetbrains.idea.devkit.inspections.quickfix.AppServiceAsStaticFinalFieldOrPropertyProviders
import org.jetbrains.uast.*
import org.jetbrains.uast.visitor.AbstractUastNonRecursiveVisitor
class ApplicationServiceAsStaticFinalFieldInspection : DevKitUastInspectionBase() {
class ApplicationServiceAsStaticFinalFieldOrPropertyInspection : DevKitUastInspectionBase() {
override fun buildInternalVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
@@ -30,12 +28,10 @@ class ApplicationServiceAsStaticFinalFieldInspection : DevKitUastInspectionBase(
if (serviceLevel == null || !serviceLevel.isApp()) return true
val sourcePsi = node.sourcePsi ?: return true
val fixes = AppServiceAsStaticFinalFieldQuickFixProviders.forLanguage(holder.file.language)?.getFixes(sourcePsi) ?: emptyList()
holder.registerUProblem(
node,
DevKitBundle.message("inspections.application.service.as.static.final.field.message"),
*fixes.toTypedArray()
)
val anchor = node.uastAnchor?.sourcePsi ?: return true
val provider = AppServiceAsStaticFinalFieldOrPropertyProviders.forLanguage(holder.file.language) ?: return true
provider.registerProblem(holder, sourcePsi, anchor)
return true
}

View File

@@ -3,9 +3,9 @@ package org.jetbrains.idea.devkit.inspections.quickfix
import com.intellij.codeInsight.FileModificationService
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.LocalQuickFixOnPsiElement
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.lang.LanguageExtension
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.CachedSingletonsRegistry
@@ -26,23 +26,29 @@ import org.jetbrains.idea.devkit.DevKitBundle
import java.util.function.Supplier
private val EP_NAME = ExtensionPointName.create<AppServiceAsStaticFinalFieldFixProvider>(
"DevKit.lang.appServiceAsStaticFinalFieldQuickFixProvider"
private val EP_NAME = ExtensionPointName.create<AppServiceAsStaticFinalFieldOrPropertyProvider>(
"DevKit.lang.appServiceAsStaticFinalFieldOrPropertyProvider"
)
internal object AppServiceAsStaticFinalFieldQuickFixProviders : LanguageExtension<AppServiceAsStaticFinalFieldFixProvider>(EP_NAME.name)
internal object AppServiceAsStaticFinalFieldOrPropertyProviders : LanguageExtension<AppServiceAsStaticFinalFieldOrPropertyProvider>(EP_NAME.name)
@IntellijInternalApi
@ApiStatus.Internal
interface AppServiceAsStaticFinalFieldFixProvider {
fun getFixes(psiElement: PsiElement): List<LocalQuickFix>
interface AppServiceAsStaticFinalFieldOrPropertyProvider {
fun registerProblem(holder: ProblemsHolder, sourcePsi: PsiElement, anchor: PsiElement)
}
private class JavaAppServiceAsStaticFinalFieldFixProvider : AppServiceAsStaticFinalFieldFixProvider {
private class JavaAppServiceAsStaticFinalFieldOrPropertyProvider : AppServiceAsStaticFinalFieldOrPropertyProvider {
override fun registerProblem(holder: ProblemsHolder, sourcePsi: PsiElement, anchor: PsiElement) {
if (sourcePsi !is PsiField) return
override fun getFixes(psiElement: PsiElement): List<LocalQuickFix> {
return if (psiElement is PsiField) listOf(JavaWrapInSupplierQuickFix(psiElement)) else emptyList()
return holder.registerProblem(
anchor,
DevKitBundle.message("inspections.application.service.as.static.final.field.message"),
JavaWrapInSupplierQuickFix(sourcePsi),
)
}
}

View File

@@ -3,12 +3,11 @@ package org.jetbrains.idea.devkit.inspections
import com.intellij.testFramework.TestDataPath
import org.jetbrains.idea.devkit.DevkitJavaTestsUtil
import kotlin.io.path.*
@TestDataPath("\$CONTENT_ROOT/testData/inspections/applicationServiceAsStaticFinalField")
class ApplicationServiceAsStaticFinalFieldInspectionTest : ApplicationServiceAsStaticFinalFieldInspectionTestBase() {
@TestDataPath("/inspections/applicationServiceAsStaticFinalFieldOrProperty")
class ApplicationServiceAsStaticFinalFieldOrPropertyInspectionTest : ApplicationServiceAsStaticFinalFieldOrPropertyInspectionTestBase() {
override fun getBasePath() = DevkitJavaTestsUtil.TESTDATA_PATH + "inspections/applicationServiceAsStaticFinalField"
override fun getBasePath() = DevkitJavaTestsUtil.TESTDATA_PATH + "inspections/applicationServiceAsStaticFinalFieldOrProperty"
override fun getFileExtension(): String = "java"

View File

@@ -22,7 +22,7 @@ public class DevkitInspectionsRegistrationCheckTest extends BasePlatformTestCase
List.of("ExtensionClassShouldBeFinalAndNonPublic",
"ActionPresentationInstantiatedInCtor",
"CancellationCheckInLoops",
"ApplicationServiceAsStaticFinalField",
"ApplicationServiceAsStaticFinalFieldOrProperty",
"ThreadingConcurrency",
"TokenSetInParserDefinition",
"CallingMethodShouldBeRequiresBlockingContext",

View File

@@ -4,10 +4,10 @@ package org.jetbrains.idea.devkit.inspections.quickfix
import com.intellij.testFramework.TestDataPath
import org.jetbrains.idea.devkit.DevKitBundle
import org.jetbrains.idea.devkit.DevkitJavaTestsUtil
import org.jetbrains.idea.devkit.inspections.ApplicationServiceAsStaticFinalFieldInspectionTestBase
import org.jetbrains.idea.devkit.inspections.ApplicationServiceAsStaticFinalFieldOrPropertyInspectionTestBase
@TestDataPath("\$CONTENT_ROOT/testData/inspections/wrapInSupplierFix")
class WrapInSupplierFixTest : ApplicationServiceAsStaticFinalFieldInspectionTestBase() {
class WrapInSupplierFixTest : ApplicationServiceAsStaticFinalFieldOrPropertyInspectionTestBase() {
override fun getBasePath() = DevkitJavaTestsUtil.TESTDATA_PATH + "inspections/wrapInSupplierFix"

View File

@@ -0,0 +1,36 @@
import serviceDeclarations.RegisteredApplicationService
// -------- top-level declarations ---------
// no backing fields
val myAppService1: RegisteredApplicationService
get() = RegisteredApplicationService.getInstance()
// with a backing field
val <warning descr="Application service must not be assigned to a static immutable property with a backing field">myAppService2</warning>: RegisteredApplicationService = RegisteredApplicationService.getInstance()
// -------- companion object declarations ---------
class MyClass {
companion object {
// no backing fields
val myAppService1: RegisteredApplicationService
get() = RegisteredApplicationService.getInstance()
// with a backing field
val <warning descr="Application service must not be assigned to a static immutable property with a backing field">myAppService2</warning>: RegisteredApplicationService = RegisteredApplicationService.getInstance()
}
}
// -------- object declarations ---------
object MyObject {
// no backing fields
val myAppService1: RegisteredApplicationService
get() = RegisteredApplicationService.getInstance()
// with a backing field
val <warning descr="Application service must not be assigned to a static immutable property with a backing field">myAppService2</warning>: RegisteredApplicationService = RegisteredApplicationService.getInstance()
}

View File

@@ -4,7 +4,7 @@ import serviceDeclarations.RegisteredApplicationService
// explicit constructor call
val myAppService1 = RegisteredApplicationService()
val <warning descr="Application service must not be assigned to a static final field">myAppService2</warning> = RegisteredApplicationService.getInstance()
val <warning descr="Application service must not be assigned to a static immutable property with a backing field">myAppService2</warning> = RegisteredApplicationService.getInstance()
// -------- companion object declarations ---------
@@ -14,7 +14,7 @@ class MyClass {
// explicit constructor call
val myAppService1 = RegisteredApplicationService()
val <warning descr="Application service must not be assigned to a static final field">myAppService2</warning> = RegisteredApplicationService.getInstance()
val <warning descr="Application service must not be assigned to a static immutable property with a backing field">myAppService2</warning> = RegisteredApplicationService.getInstance()
}
}
@@ -25,6 +25,6 @@ object MyObject {
// explicit constructor call
val myAppService1 = RegisteredApplicationService()
val <warning descr="Application service must not be assigned to a static final field">myAppService2</warning> = RegisteredApplicationService.getInstance()
val <warning descr="Application service must not be assigned to a static immutable property with a backing field">myAppService2</warning> = RegisteredApplicationService.getInstance()
}

View File

@@ -2,11 +2,11 @@ import serviceDeclarations.*
// -------- top-level declarations ---------
val <warning descr="Application service must not be assigned to a static final field">myAppService1</warning> = LightServiceAppAndProjectLevelAnnotation.getInstance()
val <warning descr="Application service must not be assigned to a static immutable property with a backing field">myAppService1</warning> = LightServiceAppAndProjectLevelAnnotation.getInstance()
val <warning descr="Application service must not be assigned to a static final field">myAppService2</warning> = LightServiceAppLevelAnnotation.getInstance()
val <warning descr="Application service must not be assigned to a static immutable property with a backing field">myAppService2</warning> = LightServiceAppLevelAnnotation.getInstance()
val <warning descr="Application service must not be assigned to a static final field">myAppService4</warning> = LightServiceEmptyAnnotation.getInstance()
val <warning descr="Application service must not be assigned to a static immutable property with a backing field">myAppService4</warning> = LightServiceEmptyAnnotation.getInstance()
// non final
var myAppService5 = LightServiceAppAndProjectLevelAnnotation.getInstance()
@@ -23,11 +23,11 @@ class MyClass(val appService: LightServiceAppLevelAnnotation) {
val myAppService5 = LightServiceAppAndProjectLevelAnnotation.getInstance()
companion object {
val <warning descr="Application service must not be assigned to a static final field">myAppService1</warning> = LightServiceAppAndProjectLevelAnnotation.getInstance()
val <warning descr="Application service must not be assigned to a static immutable property with a backing field">myAppService1</warning> = LightServiceAppAndProjectLevelAnnotation.getInstance()
val <warning descr="Application service must not be assigned to a static final field">myAppService2</warning> = LightServiceAppLevelAnnotation.getInstance()
val <warning descr="Application service must not be assigned to a static immutable property with a backing field">myAppService2</warning> = LightServiceAppLevelAnnotation.getInstance()
val <warning descr="Application service must not be assigned to a static final field">myAppService4</warning> = LightServiceEmptyAnnotation.getInstance()
val <warning descr="Application service must not be assigned to a static immutable property with a backing field">myAppService4</warning> = LightServiceEmptyAnnotation.getInstance()
// non final
var myAppService5 = LightServiceAppAndProjectLevelAnnotation.getInstance()
@@ -41,11 +41,11 @@ class MyClass(val appService: LightServiceAppLevelAnnotation) {
// -------- object declarations ---------
object MyObject {
val <warning descr="Application service must not be assigned to a static final field">myAppService1</warning> = LightServiceAppAndProjectLevelAnnotation.getInstance()
val <warning descr="Application service must not be assigned to a static immutable property with a backing field">myAppService1</warning> = LightServiceAppAndProjectLevelAnnotation.getInstance()
val <warning descr="Application service must not be assigned to a static final field">myAppService2</warning> = LightServiceAppLevelAnnotation.getInstance()
val <warning descr="Application service must not be assigned to a static immutable property with a backing field">myAppService2</warning> = LightServiceAppLevelAnnotation.getInstance()
val <warning descr="Application service must not be assigned to a static final field">myAppService4</warning> = LightServiceEmptyAnnotation.getInstance()
val <warning descr="Application service must not be assigned to a static immutable property with a backing field">myAppService4</warning> = LightServiceEmptyAnnotation.getInstance()
// non final
var myAppService5 = LightServiceAppAndProjectLevelAnnotation.getInstance()

View File

@@ -2,9 +2,9 @@ import serviceDeclarations.RegisteredApplicationService
import serviceDeclarations.RegisteredProjectService
// -------- top-level declarations ---------
val <warning descr="Application service must not be assigned to a static final field">myAppService1</warning> = RegisteredApplicationService.getInstance()
val <warning descr="Application service must not be assigned to a static immutable property with a backing field">myAppService1</warning> = RegisteredApplicationService.getInstance()
val <warning descr="Application service must not be assigned to a static final field">myAppService2</warning> = RegisteredApplicationService.getInstance()
val <warning descr="Application service must not be assigned to a static immutable property with a backing field">myAppService2</warning> = RegisteredApplicationService.getInstance()
// non-final
var myAppService3 = RegisteredApplicationService.getInstance()
@@ -20,9 +20,9 @@ class MyClass(val appService: RegisteredApplicationService) {
val myAppService = RegisteredApplicationService.getInstance()
companion object {
val <warning descr="Application service must not be assigned to a static final field">myAppService1</warning> = RegisteredApplicationService.getInstance()
val <warning descr="Application service must not be assigned to a static immutable property with a backing field">myAppService1</warning> = RegisteredApplicationService.getInstance()
val <warning descr="Application service must not be assigned to a static final field">myAppService2</warning> = RegisteredApplicationService.getInstance()
val <warning descr="Application service must not be assigned to a static immutable property with a backing field">myAppService2</warning> = RegisteredApplicationService.getInstance()
// non-final
var myAppService3 = RegisteredApplicationService.getInstance()
@@ -36,9 +36,9 @@ class MyClass(val appService: RegisteredApplicationService) {
// -------- object declarations ---------
object MyObject {
val <warning descr="Application service must not be assigned to a static final field">myAppService1</warning> = RegisteredApplicationService.getInstance()
val <warning descr="Application service must not be assigned to a static immutable property with a backing field">myAppService1</warning> = RegisteredApplicationService.getInstance()
val <warning descr="Application service must not be assigned to a static final field">myAppService2</warning> = RegisteredApplicationService.getInstance()
val <warning descr="Application service must not be assigned to a static immutable property with a backing field">myAppService2</warning> = RegisteredApplicationService.getInstance()
// non-final
var myAppService3 = RegisteredApplicationService.getInstance()

View File

@@ -13,7 +13,7 @@ class MyService {
companion object {
@MyAnnotation
val <warning descr="Application service must not be assigned to a static final field">companionObjectAppService<caret></warning> = ApplicationManager.getApplication().getService(MyService::class.java)
val <warning descr="Application service must not be assigned to a static immutable property with a backing field">companionObjectAppService<caret></warning> = ApplicationManager.getApplication().getService(MyService::class.java)
// to test naming conflicts

View File

@@ -12,7 +12,7 @@ class MyService {
object MyObject {
@MyAnnotation
val <warning descr="Application service must not be assigned to a static final field">objectAppService<caret></warning> = ApplicationManager.getApplication().getService(MyService::class.java)
val <warning descr="Application service must not be assigned to a static immutable property with a backing field">objectAppService<caret></warning> = ApplicationManager.getApplication().getService(MyService::class.java)
// to test naming conflicts
val objectAppServiceSupplier = 0

View File

@@ -6,7 +6,7 @@ import com.intellij.openapi.components.Service
import inspections.wrapInSupplierFix.MyAnnotation
@MyAnnotation
val <warning descr="Application service must not be assigned to a static final field">topLevelAppService<caret></warning>: MyService = ApplicationManager.getApplication().getService(MyService::class.java)
val <warning descr="Application service must not be assigned to a static immutable property with a backing field">topLevelAppService<caret></warning>: MyService = ApplicationManager.getApplication().getService(MyService::class.java)
// to test naming conflicts

View File

@@ -2,13 +2,13 @@
package org.jetbrains.idea.devkit.kotlin.inspections
import com.intellij.testFramework.TestDataPath
import org.jetbrains.idea.devkit.inspections.ApplicationServiceAsStaticFinalFieldInspectionTestBase
import org.jetbrains.idea.devkit.inspections.ApplicationServiceAsStaticFinalFieldOrPropertyInspectionTestBase
import org.jetbrains.idea.devkit.kotlin.DevkitKtTestsUtil
@TestDataPath("\$CONTENT_ROOT/testData/inspections/applicationServiceAsStaticFinalField")
class KtApplicationServiceAsStaticFinalFieldInspectionTest : ApplicationServiceAsStaticFinalFieldInspectionTestBase() {
@TestDataPath("/inspections/applicationServiceAsStaticFinalFieldOrProperty")
class KtApplicationServiceAsStaticFinalFieldOrPropertyInspectionTest : ApplicationServiceAsStaticFinalFieldOrPropertyInspectionTestBase() {
override fun getBasePath() = DevkitKtTestsUtil.TESTDATA_PATH + "inspections/applicationServiceAsStaticFinalField"
override fun getBasePath() = DevkitKtTestsUtil.TESTDATA_PATH + "inspections/applicationServiceAsStaticFinalFieldOrProperty"
override fun getFileExtension(): String = "kt"
@@ -27,4 +27,8 @@ class KtApplicationServiceAsStaticFinalFieldInspectionTest : ApplicationServiceA
fun testLightServices() {
doHighlightTest()
}
fun testBackingFields() {
doHighlightTest()
}
}

View File

@@ -3,11 +3,11 @@ package org.jetbrains.idea.devkit.kotlin.inspections.quickfix
import com.intellij.testFramework.TestDataPath
import org.jetbrains.idea.devkit.DevKitBundle
import org.jetbrains.idea.devkit.inspections.ApplicationServiceAsStaticFinalFieldInspectionTestBase
import org.jetbrains.idea.devkit.inspections.ApplicationServiceAsStaticFinalFieldOrPropertyInspectionTestBase
import org.jetbrains.idea.devkit.kotlin.DevkitKtTestsUtil
@TestDataPath("\$CONTENT_ROOT/testData/inspections/wrapInSupplierFix")
class KtWrapInSupplierFixTest : ApplicationServiceAsStaticFinalFieldInspectionTestBase() {
class KtWrapInSupplierFixTest : ApplicationServiceAsStaticFinalFieldOrPropertyInspectionTestBase() {
override fun getBasePath() = DevkitKtTestsUtil.TESTDATA_PATH + "inspections/wrapInSupplierFix"
@@ -16,17 +16,6 @@ class KtWrapInSupplierFixTest : ApplicationServiceAsStaticFinalFieldInspectionTe
private val fixName = DevKitBundle.message("inspections.wrap.application.service.in.supplier.quick.fix.message")
override fun setUp() {
super.setUp()
myFixture.addClass(
"""
package kotlin.reflect;
public class KClass<T> {
public Class<T> java;
}
""")
}
fun testWrapTopLevelPropertyInSupplier() {
doFixTest(fixName)

View File

@@ -7,12 +7,12 @@ import kotlin.io.path.listDirectoryEntries
import kotlin.io.path.name
import kotlin.io.path.relativeTo
abstract class ApplicationServiceAsStaticFinalFieldInspectionTestBase : LightDevKitInspectionFixTestBase() {
abstract class ApplicationServiceAsStaticFinalFieldOrPropertyInspectionTestBase : LightDevKitInspectionFixTestBase() {
override fun setUp() {
super.setUp()
addPlatformClasses()
myFixture.enableInspections(ApplicationServiceAsStaticFinalFieldInspection())
myFixture.enableInspections(ApplicationServiceAsStaticFinalFieldOrPropertyInspection())
}
private fun addPlatformClasses() {
@@ -115,6 +115,16 @@ abstract class ApplicationServiceAsStaticFinalFieldInspectionTestBase : LightDev
}
""".trimIndent()
)
myFixture.addClass(
"""
package kotlin.reflect;
public class KClass<T> {
public Class<T> java;
}
""".trimIndent()
)
}
private fun getServiceDeclarationPaths(namePrefix: String = ""): Array<String> {
@@ -141,5 +151,4 @@ abstract class ApplicationServiceAsStaticFinalFieldInspectionTestBase : LightDev
val resultName = testName + suffix?.let { "_$it" }.orEmpty()
return "${resultName}.$fileExtension" to "${resultName}_after.$fileExtension"
}
}

View File

@@ -55,8 +55,8 @@
implementationClass="org.jetbrains.idea.devkit.kotlin.inspections.ServiceLevelExtractorForKotlin"/>
<lightServiceMustBeFinalErrorMessageProvider language="kotlin"
implementationClass="org.jetbrains.idea.devkit.kotlin.inspections.LightServiceMustNotBeOpenErrorMessageProvider"/>
<appServiceAsStaticFinalFieldQuickFixProvider language="kotlin"
implementationClass="org.jetbrains.idea.devkit.kotlin.inspections.KtAppServiceAsStaticFinalFieldFixProvider"/>
<appServiceAsStaticFinalFieldOrPropertyProvider language="kotlin"
implementationClass="org.jetbrains.idea.devkit.kotlin.inspections.KtAppServiceAsStaticFinalFieldOrPropertyProvider"/>
<uElementAsPsiCheckProvider language="kotlin"
implementationClass="org.jetbrains.idea.devkit.kotlin.inspections.KtUElementAsPsiCheckProvider"/>
</extensions>

View File

@@ -22,4 +22,6 @@ inspections.move.prohibited.declarations.to.top.level.fix.text=Move prohibited d
inspection.light.service.must.not.be.open.message=Light service must not be open
inspection.extension.class.should.not.be.open.text=Extension class should not be open
inspection.extension.class.should.not.be.open.text=Extension class should not be open
inspections.application.service.as.static.immutable.property.with.backing.field.message=Application service must not be assigned to a static immutable property with a backing field

View File

@@ -2,19 +2,22 @@
package org.jetbrains.idea.devkit.kotlin.inspections
import com.intellij.codeInspection.IntentionWrapper
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.application.CachedSingletonsRegistry
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiEditorUtil
import com.intellij.psi.util.parentOfType
import org.jetbrains.idea.devkit.inspections.quickfix.AppServiceAsStaticFinalFieldFixProvider
import org.jetbrains.idea.devkit.inspections.quickfix.AppServiceAsStaticFinalFieldOrPropertyProvider
import org.jetbrains.idea.devkit.inspections.quickfix.WrapInSupplierQuickFix
import org.jetbrains.idea.devkit.kotlin.DevKitKotlinBundle
import org.jetbrains.kotlin.analysis.api.KtAllowAnalysisFromWriteAction
import org.jetbrains.kotlin.analysis.api.KtAllowAnalysisOnEdt
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.analysis.api.lifetime.allowAnalysisFromWriteAction
import org.jetbrains.kotlin.analysis.api.lifetime.allowAnalysisOnEdt
import org.jetbrains.kotlin.analysis.api.symbols.KtPropertySymbol
import org.jetbrains.kotlin.analysis.api.types.KtNonErrorClassType
import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggester
import org.jetbrains.kotlin.idea.base.codeInsight.KotlinNameSuggestionProvider
@@ -29,13 +32,32 @@ import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import java.util.function.Supplier
private class KtAppServiceAsStaticFinalFieldOrPropertyProvider : AppServiceAsStaticFinalFieldOrPropertyProvider {
private class KtAppServiceAsStaticFinalFieldFixProvider : AppServiceAsStaticFinalFieldFixProvider {
override fun registerProblem(holder: ProblemsHolder, sourcePsi: PsiElement, anchor: PsiElement) {
if (sourcePsi !is KtProperty) return
override fun getFixes(psiElement: PsiElement): List<LocalQuickFix> {
return if (psiElement is KtProperty) {
listOf(KtWrapInSupplierQuickFix(psiElement), IntentionWrapper(ConvertPropertyToFunctionIntention()))
} else emptyList()
if (!sourcePsi.hasBackingField()) return
holder.registerProblem(
anchor,
DevKitKotlinBundle.message("inspections.application.service.as.static.immutable.property.with.backing.field.message"),
ProblemHighlightType.WARNING,
IntentionWrapper(ConvertPropertyToFunctionIntention()),
KtWrapInSupplierQuickFix(sourcePsi),
)
}
@OptIn(KtAllowAnalysisOnEdt::class)
private fun KtProperty.hasBackingField(): Boolean {
allowAnalysisOnEdt {
val property = this
analyze(property) {
val propertySymbol = property.getVariableSymbol() as? KtPropertySymbol ?: return false
return propertySymbol.hasBackingField
}
}
}
}
@@ -77,7 +99,7 @@ private class KtWrapInSupplierQuickFix(ktProperty: KtProperty) : WrapInSupplierQ
}
override fun changeElementInitializerToSupplierCall(project: Project, element: KtProperty, supplierElement: KtProperty) {
val receiverName = when(val container = supplierElement.containingClassOrObject) {
val receiverName = when (val container = supplierElement.containingClassOrObject) {
is KtClass -> container.name
is KtObjectDeclaration -> if (container.isCompanion()) container.containingClassOrObject!!.name else container.name
else -> null