diff --git a/modules/spring-core/src/main/kotlin/com/explyt/spring/core/inspections/SpringConfigurationProxyBeanMethodsInspection.kt b/modules/spring-core/src/main/kotlin/com/explyt/spring/core/inspections/SpringConfigurationProxyBeanMethodsInspection.kt new file mode 100644 index 0000000..a2447b3 --- /dev/null +++ b/modules/spring-core/src/main/kotlin/com/explyt/spring/core/inspections/SpringConfigurationProxyBeanMethodsInspection.kt @@ -0,0 +1,121 @@ +/* + * Copyright © 2024 Explyt Ltd + * + * All rights reserved. + * + * This code and software are the property of Explyt Ltd and are protected by copyright and other intellectual property laws. + * + * You may use this code under the terms of the Explyt Source License Version 1.0 ("License"), if you accept its terms and conditions. + * + * By installing, downloading, accessing, using, or distributing this code, you agree to the terms and conditions of the License. + * If you do not agree to such terms and conditions, you must cease using this code and immediately delete all copies of it. + * + * You may obtain a copy of the License at: https://github.com/explyt/spring-plugin/blob/main/EXPLYT-SOURCE-LICENSE.md + * + * Unauthorized use of this code constitutes a violation of intellectual property rights and may result in legal action. + */ + +package com.explyt.spring.core.inspections + +import com.explyt.inspection.SpringBaseUastLocalInspectionTool +import com.explyt.spring.core.SpringCoreBundle +import com.explyt.spring.core.SpringCoreClasses +import com.explyt.spring.core.inspections.quickfix.AddAsMethodArgQuickFix +import com.explyt.util.ExplytAnnotationUtil.getUMetaAnnotation +import com.explyt.util.ExplytPsiUtil.findChildrenOfType +import com.explyt.util.ExplytPsiUtil.getHighlightRange +import com.explyt.util.ExplytPsiUtil.isMetaAnnotatedBy +import com.explyt.util.ExplytPsiUtil.toSourcePsi +import com.intellij.codeInsight.AnnotationUtil +import com.intellij.codeInspection.InspectionManager +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiIdentifier +import com.intellij.psi.PsiMethod +import org.jetbrains.uast.* + +class SpringConfigurationProxyBeanMethodsInspection : SpringBaseUastLocalInspectionTool() { + + override fun checkMethod( + uMethod: UMethod, + manager: InspectionManager, + isOnTheFly: Boolean + ): Array? { + val method = uMethod.javaPsi + val uClass = uMethod.getParentOfType() ?: return null + val surroundingClass: PsiClass = uClass.javaPsi + if (uClass.isStatic) return null + if (!method.isMetaAnnotatedBy(SpringCoreClasses.BEAN)) return null + var topClass: PsiClass? = surroundingClass + + while (topClass != null) { + val proxyBeanMethodsValue = uClass.getUMetaAnnotation(SpringCoreClasses.CONFIGURATION) + ?.javaPsi?.let { + AnnotationUtil.getBooleanAttributeValue(it, "proxyBeanMethods") + } + + if (proxyBeanMethodsValue == false) { + return findCallsToLocalBeans(method, topClass).asSequence() + .mapNotNull { + createProblemDescriptor( + manager, + SpringCoreBundle.message("explyt.spring.inspection.configuration.proxy.incorrect"), + it, + isOnTheFly + ) + }.toList().toTypedArray() + } else if (proxyBeanMethodsValue == null //not metaAnnotated by Configuration + && topClass.isMetaAnnotatedBy(SpringCoreClasses.COMPONENT) + ) { + return findCallsToLocalBeans(method, topClass).asSequence() + .mapNotNull { + createProblemDescriptor( + manager, + SpringCoreBundle.message("explyt.spring.inspection.configuration.light-bean.incorrect"), + it, + isOnTheFly + ) + }.toList().toTypedArray() + } + topClass = topClass.containingClass + } + return null + } + + private fun createProblemDescriptor( + manager: InspectionManager, + message: String, + callExpression: UCallExpression, + isOnTheFly: Boolean + ): ProblemDescriptor? { + val identifier = callExpression.methodIdentifier?.sourcePsi ?: return null + val fixes = listOfNotNull( + (identifier as? PsiIdentifier) + ?.let { AddAsMethodArgQuickFix(it) } + ).toTypedArray() + + return manager.createProblemDescriptor( + identifier, + identifier.getHighlightRange(), + message, + ProblemHighlightType.GENERIC_ERROR, + isOnTheFly, + *fixes + ) + } + + private fun findCallsToLocalBeans(psiMethod: PsiMethod, surroundingClass: PsiClass): List { + val beanMethods = surroundingClass.methods + .filter { it.isMetaAnnotatedBy(SpringCoreClasses.BEAN) } + .toSet() + + return psiMethod.toSourcePsi()?.findChildrenOfType()?.asSequence() + ?.mapNotNull { it.toUElement() as? UCallExpression } + ?.filter { beanMethods.contains(it.resolve()) } + ?.toList() + ?: emptyList() + } + +} \ No newline at end of file diff --git a/modules/spring-core/src/main/kotlin/com/explyt/spring/core/inspections/SpringInterfaceCacheAnnotationsInspection.kt b/modules/spring-core/src/main/kotlin/com/explyt/spring/core/inspections/SpringInterfaceCacheAnnotationsInspection.kt new file mode 100644 index 0000000..adff2ac --- /dev/null +++ b/modules/spring-core/src/main/kotlin/com/explyt/spring/core/inspections/SpringInterfaceCacheAnnotationsInspection.kt @@ -0,0 +1,107 @@ +/* + * Copyright © 2024 Explyt Ltd + * + * All rights reserved. + * + * This code and software are the property of Explyt Ltd and are protected by copyright and other intellectual property laws. + * + * You may use this code under the terms of the Explyt Source License Version 1.0 ("License"), if you accept its terms and conditions. + * + * By installing, downloading, accessing, using, or distributing this code, you agree to the terms and conditions of the License. + * If you do not agree to such terms and conditions, you must cease using this code and immediately delete all copies of it. + * + * You may obtain a copy of the License at: https://github.com/explyt/spring-plugin/blob/main/EXPLYT-SOURCE-LICENSE.md + * + * Unauthorized use of this code constitutes a violation of intellectual property rights and may result in legal action. + */ + +package com.explyt.spring.core.inspections + +import com.explyt.inspection.SpringBaseUastLocalInspectionTool +import com.explyt.spring.core.SpringCoreBundle +import com.explyt.spring.core.SpringCoreClasses +import com.explyt.spring.core.SpringCoreClasses.ANNOTATIONS_CACHE +import com.explyt.util.ExplytPsiUtil.isEqualOrInheritor +import com.explyt.util.ExplytPsiUtil.isMetaAnnotatedBy +import com.intellij.codeInspection.InspectionManager +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiJvmModifiersOwner +import com.intellij.psi.PsiNameIdentifierOwner +import org.jetbrains.uast.UClass +import org.jetbrains.uast.UDeclaration +import org.jetbrains.uast.UMethod +import org.jetbrains.uast.getContainingUClass + +class SpringInterfaceCacheAnnotationsInspection : SpringBaseUastLocalInspectionTool() { + + override fun checkMethod( + uMethod: UMethod, + manager: InspectionManager, + isOnTheFly: Boolean + ): Array? { + val uClass = uMethod.getContainingUClass() ?: return null + if (!uClass.isInterface) return null + val psiClass = uClass.javaPsi + if (isSubclassedBuSpring(psiClass)) return null + + return check( + uMethod, + manager, + isOnTheFly, + SpringCoreBundle.message("explyt.spring.inspection.cache.interface.method") + ) + } + + override fun checkClass( + uClass: UClass, + manager: InspectionManager, + isOnTheFly: Boolean + ): Array? { + if (uClass.isAnnotationType || !uClass.isInterface) return null + val psiClass = uClass.javaPsi + if (isSubclassedBuSpring(psiClass)) return null + + return check( + uClass, + manager, + isOnTheFly, + SpringCoreBundle.message("explyt.spring.inspection.cache.interface.class") + ) + } + + private fun check( + uElement: UDeclaration, + manager: InspectionManager, + isOnTheFly: Boolean, + errorMessage: String + ): Array? { + val psiElement = uElement.javaPsi as? PsiJvmModifiersOwner ?: return null + + if (!psiElement.isMetaAnnotatedBy(ANNOTATIONS_CACHE)) return null + val identifyingElement = (uElement.sourcePsi as? PsiNameIdentifierOwner)?.identifyingElement ?: return null + + return arrayOf( + manager.createProblemDescriptor( + identifyingElement, + errorMessage, + isOnTheFly, + emptyArray(), + ProblemHighlightType.GENERIC_ERROR_OR_WARNING + ) + ) + } + + private fun isSubclassedBuSpring(psiClass: PsiClass): Boolean { + + return psiClass.isEqualOrInheritor("org.springframework.data.repository.Repository") + || psiClass.isMetaAnnotatedBy( + listOf( + SpringCoreClasses.NETFLIX_FEIGN_CLIENT, + SpringCoreClasses.OPEN_FEIGN_CLIENT + ) + ) + } + +} \ No newline at end of file diff --git a/modules/spring-core/src/main/resources/inspectionDescriptions/SpringConfigurationProxyBeanMethodsInspection.html b/modules/spring-core/src/main/resources/inspectionDescriptions/SpringConfigurationProxyBeanMethodsInspection.html new file mode 100644 index 0000000..7191547 --- /dev/null +++ b/modules/spring-core/src/main/resources/inspectionDescriptions/SpringConfigurationProxyBeanMethodsInspection.html @@ -0,0 +1,39 @@ + + + Configuration Proxy Methods Inspection + +
+ Reports errors when used proxy methods are used incorrectly. +
+
+Configuration example:
+

+@Configuration(proxyBeanMethods = false)
+class ProxyMethodsConfiguration {
+  @Bean
+  BeanA beanA() {...}
+
+  @Bean
+  public BeanB beanB() {
+    return new BeanB(beanA()); // @Bean method is called directly in a @Configuration where proxyBeanMethods has been disabled (set to false)
+  }
+}
+
+
+ +Bean light-mode example:
+

+@Component
+class BeanLightComponent {
+  @Bean
+  public BeanA beanA() {...}
+
+  @Bean
+  public BeanB beanB() {
+    return new BeanB(beanA()); // Method annotated with @Bean is invoked directly
+  }
+}
+
+ + + diff --git a/modules/spring-core/src/main/resources/inspectionDescriptions/SpringInterfaceCacheAnnotationsInspection.html b/modules/spring-core/src/main/resources/inspectionDescriptions/SpringInterfaceCacheAnnotationsInspection.html new file mode 100644 index 0000000..6b81c56 --- /dev/null +++ b/modules/spring-core/src/main/resources/inspectionDescriptions/SpringInterfaceCacheAnnotationsInspection.html @@ -0,0 +1,20 @@ + + + Cache on interface inspection + + +

Reports @Cache annotations declared on interface

+ +

Example:

+"@Cache annotations shouldn't annotate interfaces" will be reported on 'MyInterface' +"@Cache annotations should annotate concrete methods" will be reported on 'myMethod' +

+  @CacheConfig
+  public interface MyInterface {
+    @CacheEvict
+    void myMethod();
+  }
+
+ + + \ No newline at end of file diff --git a/modules/spring-core/src/test/kotlin/com/explyt/spring/core/inspections/java/SpringConfigurationProxyBeanMethodsInspectionTest.kt b/modules/spring-core/src/test/kotlin/com/explyt/spring/core/inspections/java/SpringConfigurationProxyBeanMethodsInspectionTest.kt new file mode 100644 index 0000000..cea9514 --- /dev/null +++ b/modules/spring-core/src/test/kotlin/com/explyt/spring/core/inspections/java/SpringConfigurationProxyBeanMethodsInspectionTest.kt @@ -0,0 +1,31 @@ +/* + * Copyright © 2024 Explyt Ltd + * + * All rights reserved. + * + * This code and software are the property of Explyt Ltd and are protected by copyright and other intellectual property laws. + * + * You may use this code under the terms of the Explyt Source License Version 1.0 ("License"), if you accept its terms and conditions. + * + * By installing, downloading, accessing, using, or distributing this code, you agree to the terms and conditions of the License. + * If you do not agree to such terms and conditions, you must cease using this code and immediately delete all copies of it. + * + * You may obtain a copy of the License at: https://github.com/explyt/spring-plugin/blob/main/EXPLYT-SOURCE-LICENSE.md + * + * Unauthorized use of this code constitutes a violation of intellectual property rights and may result in legal action. + */ + +package com.explyt.spring.core.inspections.java + +import com.explyt.spring.test.ExplytInspectionJavaTestCase +import com.explyt.spring.test.TestLibrary +import org.jetbrains.kotlin.test.TestMetadata + +class SpringConfigurationProxyBeanMethodsInspectionTest : ExplytInspectionJavaTestCase() { + + override val libraries: Array = arrayOf(TestLibrary.springContext_6_0_7) + + @TestMetadata("configurationProxyMethods") + fun testConfigurationProxyMethods() = + doTest(com.explyt.spring.core.inspections.SpringConfigurationProxyBeanMethodsInspection()) +} \ No newline at end of file diff --git a/modules/spring-core/src/test/kotlin/com/explyt/spring/core/inspections/java/SpringInterfaceCacheAnnotationsInspectionTest.kt b/modules/spring-core/src/test/kotlin/com/explyt/spring/core/inspections/java/SpringInterfaceCacheAnnotationsInspectionTest.kt new file mode 100644 index 0000000..d26e91d --- /dev/null +++ b/modules/spring-core/src/test/kotlin/com/explyt/spring/core/inspections/java/SpringInterfaceCacheAnnotationsInspectionTest.kt @@ -0,0 +1,32 @@ +/* + * Copyright © 2024 Explyt Ltd + * + * All rights reserved. + * + * This code and software are the property of Explyt Ltd and are protected by copyright and other intellectual property laws. + * + * You may use this code under the terms of the Explyt Source License Version 1.0 ("License"), if you accept its terms and conditions. + * + * By installing, downloading, accessing, using, or distributing this code, you agree to the terms and conditions of the License. + * If you do not agree to such terms and conditions, you must cease using this code and immediately delete all copies of it. + * + * You may obtain a copy of the License at: https://github.com/explyt/spring-plugin/blob/main/EXPLYT-SOURCE-LICENSE.md + * + * Unauthorized use of this code constitutes a violation of intellectual property rights and may result in legal action. + */ + +package com.explyt.spring.core.inspections.java + +import com.explyt.spring.core.inspections.SpringInterfaceCacheAnnotationsInspection +import com.explyt.spring.test.ExplytInspectionJavaTestCase +import com.explyt.spring.test.TestLibrary +import org.jetbrains.kotlin.test.TestMetadata + +class SpringInterfaceCacheAnnotationsInspectionTest : ExplytInspectionJavaTestCase() { + + override val libraries: Array = arrayOf(TestLibrary.springBootAutoConfigure_3_1_1) + + @TestMetadata("cacheOnInterface") + fun testCacheOnInterface() = + doTest(SpringInterfaceCacheAnnotationsInspection()) +} \ No newline at end of file diff --git a/modules/spring-core/src/test/kotlin/com/explyt/spring/core/inspections/kotlin/SpringConfigurationProxyBeanMethodsInspectionTest.kt b/modules/spring-core/src/test/kotlin/com/explyt/spring/core/inspections/kotlin/SpringConfigurationProxyBeanMethodsInspectionTest.kt new file mode 100644 index 0000000..6e312ac --- /dev/null +++ b/modules/spring-core/src/test/kotlin/com/explyt/spring/core/inspections/kotlin/SpringConfigurationProxyBeanMethodsInspectionTest.kt @@ -0,0 +1,31 @@ +/* + * Copyright © 2024 Explyt Ltd + * + * All rights reserved. + * + * This code and software are the property of Explyt Ltd and are protected by copyright and other intellectual property laws. + * + * You may use this code under the terms of the Explyt Source License Version 1.0 ("License"), if you accept its terms and conditions. + * + * By installing, downloading, accessing, using, or distributing this code, you agree to the terms and conditions of the License. + * If you do not agree to such terms and conditions, you must cease using this code and immediately delete all copies of it. + * + * You may obtain a copy of the License at: https://github.com/explyt/spring-plugin/blob/main/EXPLYT-SOURCE-LICENSE.md + * + * Unauthorized use of this code constitutes a violation of intellectual property rights and may result in legal action. + */ + +package com.explyt.spring.core.inspections.kotlin + +import com.explyt.spring.core.inspections.SpringConfigurationProxyBeanMethodsInspection +import com.explyt.spring.test.ExplytInspectionKotlinTestCase +import com.explyt.spring.test.TestLibrary +import org.jetbrains.kotlin.test.TestMetadata + +class SpringConfigurationProxyBeanMethodsInspectionTest : ExplytInspectionKotlinTestCase() { + + override val libraries: Array = arrayOf(TestLibrary.springContext_6_0_7) + + @TestMetadata("configurationProxyMethods") + fun testConfigurationProxyMethods() = doTest(SpringConfigurationProxyBeanMethodsInspection()) +} \ No newline at end of file diff --git a/modules/spring-core/src/test/kotlin/com/explyt/spring/core/inspections/kotlin/SpringInterfaceCacheAnnotationsInspectionTest.kt b/modules/spring-core/src/test/kotlin/com/explyt/spring/core/inspections/kotlin/SpringInterfaceCacheAnnotationsInspectionTest.kt new file mode 100644 index 0000000..7508dc0 --- /dev/null +++ b/modules/spring-core/src/test/kotlin/com/explyt/spring/core/inspections/kotlin/SpringInterfaceCacheAnnotationsInspectionTest.kt @@ -0,0 +1,32 @@ +/* + * Copyright © 2024 Explyt Ltd + * + * All rights reserved. + * + * This code and software are the property of Explyt Ltd and are protected by copyright and other intellectual property laws. + * + * You may use this code under the terms of the Explyt Source License Version 1.0 ("License"), if you accept its terms and conditions. + * + * By installing, downloading, accessing, using, or distributing this code, you agree to the terms and conditions of the License. + * If you do not agree to such terms and conditions, you must cease using this code and immediately delete all copies of it. + * + * You may obtain a copy of the License at: https://github.com/explyt/spring-plugin/blob/main/EXPLYT-SOURCE-LICENSE.md + * + * Unauthorized use of this code constitutes a violation of intellectual property rights and may result in legal action. + */ + +package com.explyt.spring.core.inspections.kotlin + +import com.explyt.spring.core.inspections.SpringInterfaceCacheAnnotationsInspection +import com.explyt.spring.test.ExplytInspectionKotlinTestCase +import com.explyt.spring.test.TestLibrary +import org.jetbrains.kotlin.test.TestMetadata + +class SpringInterfaceCacheAnnotationsInspectionTest : ExplytInspectionKotlinTestCase() { + + override val libraries: Array = arrayOf(TestLibrary.springBootAutoConfigure_3_1_1) + + @TestMetadata("cacheOnInterface") + fun testCacheOnInterface() = + doTest(SpringInterfaceCacheAnnotationsInspection()) +}