Skip to content

Commit

Permalink
Spring Support: Inspection for final Spring-annotated classes/functions
Browse files Browse the repository at this point in the history
 #KT-11098 Fixed
  • Loading branch information
asedunov committed Mar 24, 2016
1 parent 161d11d commit 72a17b0
Show file tree
Hide file tree
Showing 30 changed files with 582 additions and 12 deletions.
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ New features:
- Kotlin Education Plugin (for IDEA 2016)
- [KT-9752](https://youtrack.jetbrains.com/issue/KT-9752) More usable file chooser for "Move declaration to another file"
- [KT-9697](https://youtrack.jetbrains.com/issue/KT-9697) Move method to companion object and back
- [KT-11098](https://youtrack.jetbrains.com/issue/KT-11098) Inspection on final classes/functions annotated with Spring @Configuration/@Component/@Bean

General issues fixed:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -775,7 +775,13 @@ fun main(args: Array<String>) {
}

testGroup("idea/idea-ultimate/tests", "idea/testData") {
testClass<AbstractInspectionTest>("UltimateInspectionTestGenerated") {
model("ultimateInspections", pattern = "^(inspections\\.test)$", singleClass = true)
}

testClass<AbstractQuickFixTest>("UltimateQuickFixTestGenerated") {
model("ultimateQuickFixes", pattern = "^([\\w\\-_]+)\\.kt$", filenameStartsLowerCase = true)
}
}

testGroup("idea/tests", "compiler/testData") {
Expand Down
1 change: 1 addition & 0 deletions idea/idea-ultimate/idea-ultimate.iml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/resources" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<html>
<body>
This inspection reports final Kotlin classes and functions annotated with Spring @Component, @Configuration or @Bean annotations
</body>
</html>
8 changes: 7 additions & 1 deletion idea/idea-ultimate/src/META-INF/kotlin-spring.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">

<localInspection implementationClass="org.jetbrains.kotlin.idea.spring.inspections.KotlinFinalClassOrFunSpringInspection"
displayName="Final Kotlin class or function with Spring annotation"
groupBundle="resources.messages.SpringBundle"
groupKey="inspection.group.code"
groupPath="Spring,Spring Core"
enabledByDefault="true"
level="WARNING"/>
</extensions>
</idea-plugin>
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.jetbrains.kotlin.idea.spring.inspections

import com.intellij.codeInsight.highlighting.HighlightUsagesDescriptionLocation
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.project.Project
import com.intellij.psi.ElementDescriptionUtil
import com.intellij.psi.PsiElementVisitor
import com.intellij.spring.constants.SpringAnnotationsConstants
import com.intellij.spring.model.jam.stereotype.SpringComponent
import com.intellij.spring.model.jam.stereotype.SpringConfiguration
import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.asJava.toLightMethods
import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection
import org.jetbrains.kotlin.idea.spring.isAnnotatedWith
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.isInheritable
import org.jetbrains.kotlin.psi.psiUtil.isOverridable

class KotlinFinalClassOrFunSpringInspection : AbstractKotlinInspection() {
class QuickFix<T: KtModifierListOwner>(private val element: T) : LocalQuickFix {
override fun getName(): String {
return "Make ${ElementDescriptionUtil.getElementDescription(element, HighlightUsagesDescriptionLocation.INSTANCE)} open"
}

override fun getFamilyName() = "Make declaration open"

override fun applyFix(project: Project, problemDescriptor: ProblemDescriptor) {
(element as? KtNamedDeclaration)?.containingClassOrObject?.addModifier(KtTokens.OPEN_KEYWORD)
element.addModifier(KtTokens.OPEN_KEYWORD)
}
}

private fun getMessage(declaration: KtNamedDeclaration): String? {
when (declaration) {
is KtClass -> {
val lightClass = declaration.toLightClass() ?: return null
when {
SpringConfiguration.META.getJamElement(lightClass) != null -> return "@Configuration class should be declared open"
SpringComponent.META.getJamElement(lightClass) != null -> return "@Component class should be declared open"
}
}

is KtNamedFunction -> {
val lightMethod = declaration.toLightMethods().firstOrNull() ?: return null
if (lightMethod.isAnnotatedWith(SpringAnnotationsConstants.JAVA_SPRING_BEAN)) return "@Bean function should be declared open"
}
}
return null
}

override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object: KtVisitorVoid() {
override fun visitNamedDeclaration(declaration: KtNamedDeclaration) {
when (declaration) {
is KtClass -> if (declaration.isInheritable()) return
is KtNamedFunction -> if (declaration.isOverridable()) return
else -> return
}

val message = getMessage(declaration) ?: return

holder.registerProblem(
declaration.nameIdentifier ?: declaration,
message,
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
QuickFix(declaration)
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.jetbrains.kotlin.idea.spring

import com.intellij.codeInsight.AnnotationUtil
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.psi.PsiModifierListOwner
import com.intellij.spring.model.jam.utils.JamAnnotationTypeUtil

internal fun PsiModifierListOwner.isAnnotatedWith(annotationFqName: String): Boolean {
val module = ModuleUtilCore.findModuleForPsiElement(this) ?: return false
return JamAnnotationTypeUtil.getInstance(module)
.getAnnotationTypesWithChildren(annotationFqName)
.mapNotNull { it.qualifiedName }
.any { AnnotationUtil.isAnnotated(this, it, true) }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.jetbrains.kotlin.idea.codeInsight;

import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;

import java.io.File;
import java.util.regex.Pattern;

/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("idea/testData/ultimateInspections")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class UltimateInspectionTestGenerated extends AbstractInspectionTest {
public void testAllFilesPresentInUltimateInspections() throws Exception {
KotlinTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("idea/testData/ultimateInspections"), Pattern.compile("^(inspections\\.test)$"));
}

@TestMetadata("spring/finalSpringAnnotatedDeclaration/inspectionData/inspections.test")
public void testSpring_finalSpringAnnotatedDeclaration_inspectionData_Inspections_test() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/ultimateInspections/spring/finalSpringAnnotatedDeclaration/inspectionData/inspections.test");
doTest(fileName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.jetbrains.kotlin.idea.quickfix;

import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;

import java.io.File;
import java.util.regex.Pattern;

/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("idea/testData/ultimateQuickFixes")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class UltimateQuickFixTestGenerated extends AbstractQuickFixTest {
public void testAllFilesPresentInUltimateQuickFixes() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/ultimateQuickFixes"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
}

@TestMetadata("idea/testData/ultimateQuickFixes/spring")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Spring extends AbstractQuickFixTest {
public void testAllFilesPresentInSpring() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/ultimateQuickFixes/spring"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
}

@TestMetadata("idea/testData/ultimateQuickFixes/spring/finalSpringAnnotatedDeclaration")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class FinalSpringAnnotatedDeclaration extends AbstractQuickFixTest {
public void testAllFilesPresentInFinalSpringAnnotatedDeclaration() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/ultimateQuickFixes/spring/finalSpringAnnotatedDeclaration"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
}

@TestMetadata("classWithComponentRuntime.kt")
public void testClassWithComponentRuntime() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/ultimateQuickFixes/spring/finalSpringAnnotatedDeclaration/classWithComponentRuntime.kt");
doTest(fileName);
}

@TestMetadata("classWithConfigurationRuntime.kt")
public void testClassWithConfigurationRuntime() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/ultimateQuickFixes/spring/finalSpringAnnotatedDeclaration/classWithConfigurationRuntime.kt");
doTest(fileName);
}

@TestMetadata("classWithCustomConfigurationRuntime.kt")
public void testClassWithCustomConfigurationRuntime() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/ultimateQuickFixes/spring/finalSpringAnnotatedDeclaration/classWithCustomConfigurationRuntime.kt");
doTest(fileName);
}

@TestMetadata("funWithBeanFinalClassRuntime.kt")
public void testFunWithBeanFinalClassRuntime() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/ultimateQuickFixes/spring/finalSpringAnnotatedDeclaration/funWithBeanFinalClassRuntime.kt");
doTest(fileName);
}

@TestMetadata("funWithBeanOpenClassRuntime.kt")
public void testFunWithBeanOpenClassRuntime() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/ultimateQuickFixes/spring/finalSpringAnnotatedDeclaration/funWithBeanOpenClassRuntime.kt");
doTest(fileName);
}

@TestMetadata("funWithCustomBeanFinalClassRuntime.kt")
public void testFunWithCustomBeanFinalClassRuntime() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/ultimateQuickFixes/spring/finalSpringAnnotatedDeclaration/funWithCustomBeanFinalClassRuntime.kt");
doTest(fileName);
}

@TestMetadata("funWithCustomBeanOpenClassRuntime.kt")
public void testFunWithCustomBeanOpenClassRuntime() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/ultimateQuickFixes/spring/finalSpringAnnotatedDeclaration/funWithCustomBeanOpenClassRuntime.kt");
doTest(fileName);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,12 @@ class SpringTestFixtureExtension() : TestFixtureExtension {
override fun tearDown() {
try {
// clear existing SpringFacet configuration before running next test
module?.let { SpringFacet.getInstance(it) }?.let {
it.removeFileSets()
FacetUtil.deleteFacet(it)
module?.let { module ->
SpringFacet.getInstance(module)?.let { facet ->
facet.removeFileSets()
FacetUtil.deleteFacet(facet)
}
ConfigLibraryUtil.removeLibrary(module, "spring" + SpringFramework.FRAMEWORK_4_2_0.version)
}
}
finally {
Expand Down
Loading

0 comments on commit 72a17b0

Please sign in to comment.