From 85d07e5f6d011e20910d999a50bc6e1e955ebea6 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Tue, 17 May 2016 18:19:46 +0300 Subject: [PATCH] Spring Support: Implement Spring @Autowired inspection #KT-12278 Fixed #KT-12147 Fixed #KT-12366 Fixed #KT-12122 Fixed --- ChangeLog.md | 4 + .../kotlin/asJava/KtLightAnnotation.kt | 3 +- .../jetbrains/kotlin/asJava/KtLightField.kt | 2 + .../kotlin/asJava/KtLightIdentifier.kt | 19 +- .../kotlin/asJava/lightClassUtils.kt | 7 +- .../inspections/AbstractKotlinInspection.kt | 20 +- .../codeInsight/AbstractInspectionTest.kt | 7 + .../idea/quickfix/AbstractQuickFixTest.java | 6 + .../.idea/dictionaries/Alexey_Sedunov.xml | 9 + ultimate/resources/META-INF/kotlin-spring.xml | 8 + .../SpringKotlinAutowiring.html | 1 + .../SpringKotlinAutowiringInspection.kt | 177 ++++++++++++++++++ .../references/KtSpringQualifierReference.kt | 25 ++- .../autowiring/ambiguousConstructors.kt | 60 ++++++ .../autowiring/inspectionData/expected.xml | 119 ++++++++++++ .../inspectionData/inspections.test | 3 + .../autowiring/invalidAutowiredByType.kt | 30 +++ .../autowiring/invalidAutowiredQualifier.kt | 39 ++++ .../autowiring/invalidAutowiredResource.kt | 33 ++++ .../multipleAutowiredConstructors.kt | 37 ++++ .../autowiring/parameterlessFunction.kt | 16 ++ .../spring/addQualifierAnnotation/.inspection | 1 + .../addQualifierAnnotation/ambiguousBean.kt | 27 +++ .../ambiguousBean.kt.after | 27 +++ .../UltimateQuickFixTestGenerated.java | 97 ---------- .../tests/SpringTestFixtureExtension.kt | 8 + .../AbstractSpringInspectionTest.kt | 11 ++ .../SpringInspectionTestGenerated.java} | 28 +-- .../quickfixes/AbstractSpringQuickFixTest.kt | 10 + .../SpringQuickFixTestGenerated.java | 103 ++++++++++ .../jetbrains/kotlin/tests/GenerateTests.kt | 12 +- 31 files changed, 820 insertions(+), 129 deletions(-) create mode 100644 ultimate/.idea/dictionaries/Alexey_Sedunov.xml create mode 100644 ultimate/resources/inspectionDescriptions/SpringKotlinAutowiring.html create mode 100644 ultimate/src/org/jetbrains/kotlin/idea/spring/inspections/SpringKotlinAutowiringInspection.kt create mode 100644 ultimate/testData/inspections/spring/autowiring/ambiguousConstructors.kt create mode 100644 ultimate/testData/inspections/spring/autowiring/inspectionData/expected.xml create mode 100644 ultimate/testData/inspections/spring/autowiring/inspectionData/inspections.test create mode 100644 ultimate/testData/inspections/spring/autowiring/invalidAutowiredByType.kt create mode 100644 ultimate/testData/inspections/spring/autowiring/invalidAutowiredQualifier.kt create mode 100644 ultimate/testData/inspections/spring/autowiring/invalidAutowiredResource.kt create mode 100644 ultimate/testData/inspections/spring/autowiring/multipleAutowiredConstructors.kt create mode 100644 ultimate/testData/inspections/spring/autowiring/parameterlessFunction.kt create mode 100644 ultimate/testData/quickFixes/spring/addQualifierAnnotation/.inspection create mode 100644 ultimate/testData/quickFixes/spring/addQualifierAnnotation/ambiguousBean.kt create mode 100644 ultimate/testData/quickFixes/spring/addQualifierAnnotation/ambiguousBean.kt.after delete mode 100644 ultimate/tests/org/jetbrains/kotlin/idea/quickfix/UltimateQuickFixTestGenerated.java create mode 100644 ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/inspections/AbstractSpringInspectionTest.kt rename ultimate/tests/org/jetbrains/kotlin/idea/{codeInsight/UltimateInspectionTestGenerated.java => spring/tests/inspections/SpringInspectionTestGenerated.java} (58%) create mode 100644 ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/quickfixes/AbstractSpringQuickFixTest.kt create mode 100644 ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/quickfixes/SpringQuickFixTestGenerated.java diff --git a/ChangeLog.md b/ChangeLog.md index a696e21e10c..b45c114c03b 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -26,6 +26,7 @@ New features: - [KT-12092](https://youtrack.jetbrains.com/issue/KT-12092) Implement bean references in @Qualifier annotations - [KT-12175](https://youtrack.jetbrains.com/issue/KT-12175) Don't enforce empty line after one-line constructor - Added "Decompile" button to Kotlin bytecode toolwindow +- [KT-12278](https://youtrack.jetbrains.com/issue/KT-12278) Implement Spring @Autowired inspection Issues fixed: @@ -53,6 +54,9 @@ Issues fixed: - [KT-12067](https://youtrack.jetbrains.com/issue/KT-12067) Deadlock in Kotlin debugger is fixed - [KT-12070](https://youtrack.jetbrains.com/issue/KT-12070) Add empty line in error message of Maven and Gradle configuration - [KT-12091](https://youtrack.jetbrains.com/issue/KT-12091) Fixed unstable behavior of Spring line markers +- [KT-12147](https://youtrack.jetbrains.com/issue/KT-12147) Fixed exception on analyzing object declaration with @Component annotation +- [KT-12366](https://youtrack.jetbrains.com/issue/KT-12366) Fixed exception on analyzing class declaration upon annotation typing +- [KT-12122](https://youtrack.jetbrains.com/issue/KT-12122) Fixed line marker popup on functions with @Qualifier-annotated parameters #### Debugger diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KtLightAnnotation.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KtLightAnnotation.kt index 4e545b54361..0762909fab2 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KtLightAnnotation.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KtLightAnnotation.kt @@ -72,9 +72,10 @@ class KtLightAnnotation( } override fun getReference() = references.singleOrNull() - override fun getReferences() = ReferenceProvidersRegistry.getReferencesFromProviders(delegate, PsiReferenceService.Hints.NO_HINTS) + override fun getReferences() = originalExpression?.references ?: PsiReference.EMPTY_ARRAY override fun getLanguage() = KotlinLanguage.INSTANCE override fun getNavigationElement() = originalExpression + override fun getTextRange() = originalExpression?.textRange ?: TextRange.EMPTY_RANGE } inner class LightArrayInitializerValue(private val delegate: PsiArrayInitializerMemberValue) : PsiArrayInitializerMemberValue by delegate { diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KtLightField.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KtLightField.kt index 93a3fe4290e..8b0544cbc7a 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KtLightField.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KtLightField.kt @@ -52,6 +52,8 @@ sealed class KtLightFieldImpl( override fun getContainingClass() = containingClass + override fun getContainingFile() = containingClass.containingFile + override fun getType() = clsDelegate.type override fun getTypeElement() = clsDelegate.typeElement diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KtLightIdentifier.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KtLightIdentifier.kt index 35850f0c582..c6266ddcb04 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KtLightIdentifier.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KtLightIdentifier.kt @@ -17,19 +17,32 @@ package org.jetbrains.kotlin.asJava import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiCompiledElement import com.intellij.psi.PsiElement import com.intellij.psi.PsiNameIdentifierOwner import com.intellij.psi.impl.light.LightIdentifier import org.jetbrains.kotlin.psi.KtNamedDeclaration +import org.jetbrains.kotlin.psi.KtPrimaryConstructor +import org.jetbrains.kotlin.psi.KtSecondaryConstructor +import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject class KtLightIdentifier( private val lightOwner: PsiNameIdentifierOwner, private val ktDeclaration: KtNamedDeclaration? -) : LightIdentifier(lightOwner.manager, ktDeclaration?.name ?: "") { +) : LightIdentifier(lightOwner.manager, ktDeclaration?.name ?: ""), PsiCompiledElement { val origin: PsiElement? - get() = ktDeclaration?.nameIdentifier + get() = when (ktDeclaration) { + is KtSecondaryConstructor -> ktDeclaration.getConstructorKeyword() + is KtPrimaryConstructor -> ktDeclaration.getConstructorKeyword() + ?: ktDeclaration.valueParameterList + ?: ktDeclaration.containingClassOrObject?.nameIdentifier + else -> ktDeclaration?.nameIdentifier + } + + override fun getMirror() = ((lightOwner as? KtLightElement<*, *>)?.clsDelegate as? PsiNameIdentifierOwner)?.nameIdentifier override fun isPhysical() = true override fun getParent() = lightOwner - override fun getTextRange() = ktDeclaration?.nameIdentifier?.textRange ?: TextRange.EMPTY_RANGE + override fun getContainingFile() = lightOwner.containingFile + override fun getTextRange() = origin?.textRange ?: TextRange.EMPTY_RANGE } \ No newline at end of file diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt index 099a135099f..eec912339c1 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt @@ -109,7 +109,12 @@ fun KtTypeParameter.toPsiTypeParameters(): List { // Returns original declaration if given PsiElement is a Kotlin light element, and element itself otherwise val PsiElement.unwrapped: PsiElement? - get() = if (this is KtLightElement<*, *>) kotlinOrigin else this + get() = when { + this is KtLightElement<*, *> -> kotlinOrigin + this is KtLightIdentifier -> origin + this is KtLightAnnotation.LightExpressionValue -> originalExpression + else -> this + } val PsiElement.namedUnwrappedElement: PsiNamedElement? get() = unwrapped?.getNonStrictParentOfType() diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/inspections/AbstractKotlinInspection.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/inspections/AbstractKotlinInspection.kt index 20c7d3de023..f83e15a409e 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/inspections/AbstractKotlinInspection.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/inspections/AbstractKotlinInspection.kt @@ -63,16 +63,18 @@ private fun toSeverity(highlightDisplayLevel: HighlightDisplayLevel): Severity } @Suppress("unused") -fun Array.registerWithElementsUnwrapped(holder: ProblemsHolder, isOnTheFly: Boolean) { - forEach { +fun Array.registerWithElementsUnwrapped( + holder: ProblemsHolder, + isOnTheFly: Boolean, + quickFixSubstitutor: ((LocalQuickFix, PsiElement) -> LocalQuickFix?)? = null) { + forEach { problem -> @Suppress("UNCHECKED_CAST") - val descriptor = holder.manager.createProblemDescriptor( - it.psiElement.unwrapped ?: it.psiElement, - it.descriptionTemplate, - isOnTheFly, - it.fixes as? Array ?: LocalQuickFix.EMPTY_ARRAY, - it.highlightType - ) + val originalFixes = problem.fixes as? Array ?: LocalQuickFix.EMPTY_ARRAY + val newElement = problem.psiElement.unwrapped ?: return@forEach + val newFixes = quickFixSubstitutor?.let { subst -> + originalFixes.mapNotNull { subst(it, newElement) }.toTypedArray() + } ?: originalFixes + val descriptor = holder.manager.createProblemDescriptor(newElement, problem.descriptionTemplate, isOnTheFly, newFixes, problem.highlightType) holder.registerProblem(descriptor) } } \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractInspectionTest.kt b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractInspectionTest.kt index 8445928af99..9f111b4f54a 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractInspectionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractInspectionTest.kt @@ -24,6 +24,7 @@ import com.intellij.codeInspection.ex.InspectionManagerEx import com.intellij.codeInspection.ex.LocalInspectionToolWrapper import com.intellij.openapi.command.CommandProcessor import com.intellij.openapi.util.io.FileUtil +import com.intellij.psi.PsiFile import com.intellij.testFramework.IdeaTestUtil import com.intellij.testFramework.InspectionTestUtil import com.intellij.testFramework.LightProjectDescriptor @@ -51,6 +52,10 @@ abstract class AbstractInspectionTest : KotlinLightCodeInsightFixtureTestCase() super.tearDown() } + protected open fun configExtra(psiFiles: List, options: String) { + + } + protected fun doTest(path: String) { val optionsFile = File(path) val options = FileUtil.loadFile(optionsFile, true) @@ -110,6 +115,8 @@ abstract class AbstractInspectionTest : KotlinLightCodeInsightFixtureTestCase() fixtureClasses.forEach { TestFixtureExtension.loadFixture(it, myFixture.module) } + configExtra(psiFiles, options) + val scope = AnalysisScope(project, psiFiles.map { it.virtualFile!! }) scope.invalidate() diff --git a/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.java b/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.java index ccab3d7560c..33a3cffa729 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.java +++ b/idea/tests/org/jetbrains/kotlin/idea/quickfix/AbstractQuickFixTest.java @@ -124,6 +124,10 @@ public abstract class AbstractQuickFixTest extends KotlinLightQuickFixTestCase { }), LocalInspectionTool.class); } + protected void configExtra(String options) { + + } + private void doKotlinQuickFixTest(final String testName, final QuickFixTestCase quickFixTestCase) { String relativePath = notNull(quickFixTestCase.getBasePath(), "") + "/" + StringsKt.decapitalize(testName); final String testFullPath = quickFixTestCase.getTestDataPath().replace(File.separatorChar, '/') + relativePath; @@ -150,6 +154,8 @@ public abstract class AbstractQuickFixTest extends KotlinLightQuickFixTestCase { checkForUnexpectedActions(); + configExtra(fileText); + applyAction(contents, quickFixTestCase, testName, testFullPath); assertEmpty(expectedErrorMessage); diff --git a/ultimate/.idea/dictionaries/Alexey_Sedunov.xml b/ultimate/.idea/dictionaries/Alexey_Sedunov.xml new file mode 100644 index 00000000000..ccf943a2bac --- /dev/null +++ b/ultimate/.idea/dictionaries/Alexey_Sedunov.xml @@ -0,0 +1,9 @@ + + + + autowired + autowiring + springframework + + + \ No newline at end of file diff --git a/ultimate/resources/META-INF/kotlin-spring.xml b/ultimate/resources/META-INF/kotlin-spring.xml index cd5771e4f7d..75fe083ec70 100644 --- a/ultimate/resources/META-INF/kotlin-spring.xml +++ b/ultimate/resources/META-INF/kotlin-spring.xml @@ -30,6 +30,14 @@ enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.kotlin.idea.spring.inspections.SpringKotlinAutowiredMembersInspection"/> + diff --git a/ultimate/resources/inspectionDescriptions/SpringKotlinAutowiring.html b/ultimate/resources/inspectionDescriptions/SpringKotlinAutowiring.html new file mode 100644 index 00000000000..5af909dbe5b --- /dev/null +++ b/ultimate/resources/inspectionDescriptions/SpringKotlinAutowiring.html @@ -0,0 +1 @@ +Checks autowiring problems in a bean class. \ No newline at end of file diff --git a/ultimate/src/org/jetbrains/kotlin/idea/spring/inspections/SpringKotlinAutowiringInspection.kt b/ultimate/src/org/jetbrains/kotlin/idea/spring/inspections/SpringKotlinAutowiringInspection.kt new file mode 100644 index 00000000000..03a1237815d --- /dev/null +++ b/ultimate/src/org/jetbrains/kotlin/idea/spring/inspections/SpringKotlinAutowiringInspection.kt @@ -0,0 +1,177 @@ +package org.jetbrains.kotlin.idea.spring.inspections + +import com.intellij.codeInsight.AnnotationUtil +import com.intellij.codeInsight.FileModificationService +import com.intellij.codeInsight.lookup.LookupElement +import com.intellij.codeInsight.template.* +import com.intellij.codeInspection.LocalQuickFix +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.openapi.fileEditor.FileEditorManager +import com.intellij.openapi.fileEditor.OpenFileDescriptor +import com.intellij.openapi.project.Project +import com.intellij.psi.* +import com.intellij.psi.util.PropertyUtil +import com.intellij.spring.CommonSpringModel +import com.intellij.spring.SpringBundle +import com.intellij.spring.model.SpringBeanPointer +import com.intellij.spring.model.converters.SpringConverterUtil +import com.intellij.spring.model.highlighting.SpringAutowireUtil +import com.intellij.spring.model.highlighting.SpringJavaAutowiringInspection +import org.jetbrains.kotlin.asJava.* +import org.jetbrains.kotlin.idea.core.ShortenReferences +import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection +import org.jetbrains.kotlin.idea.inspections.registerWithElementsUnwrapped +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.* + +class SpringKotlinAutowiringInspection : AbstractKotlinInspection() { + // Based on SpringJavaAutowiringInspection.AddSpringBeanQualifierFix + class AddQualifierFix( + modifierListOwner: KtModifierListOwner, + private val beanPointers: Collection>, + private val annotationFqName: String + ) : LocalQuickFix { + private val elementPointer = modifierListOwner.createSmartPointer() + + override fun getName() = SpringBundle.message("SpringAutowiringInspection.add.qualifier.fix") + + override fun getFamilyName() = name + + private fun getQualifierNamesSuggestNamesExpression(expression: KtStringTemplateExpression): Expression { + return object : Expression() { + override fun calculateResult(context: ExpressionContext): Result? { + PsiDocumentManager.getInstance(context.project).commitAllDocuments() + return TextResult(expression.plainContent) + } + + override fun calculateQuickResult(context: ExpressionContext) = this.calculateResult(context) + + override fun calculateLookupItems(context: ExpressionContext): Array? { + PsiDocumentManager.getInstance(context.project).commitAllDocuments() + return beanPointers.sortedBy { it.name ?: "" }.mapNotNull { SpringConverterUtil.createCompletionVariant(it) }.toTypedArray() + } + } + } + + private fun createQualifierNameTemplate(expression: KtStringTemplateExpression): Template { + val builder = TemplateBuilderImpl(expression.containingFile) + builder.replaceRange( + expression.getContentRange().shiftRight(expression.startOffset), + getQualifierNamesSuggestNamesExpression(expression) + ) + return builder.buildInlineTemplate() + } + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val modifierListOwner = elementPointer.element ?: return + if(!FileModificationService.getInstance().preparePsiElementForWrite(modifierListOwner)) return + if (beanPointers.isEmpty()) return + + val defaultBeanName = with(beanPointers.first().name) { if (isNullOrBlank()) "Unknown" else this } + val entry = KtPsiFactory(project).createAnnotationEntry("@$annotationFqName(\"$defaultBeanName\")") + val addedEntry = modifierListOwner.addAnnotationEntry(entry) + + ShortenReferences.DEFAULT.process(addedEntry) + + val stringTemplate = addedEntry.valueArguments.first().getArgumentExpression() as? KtStringTemplateExpression ?: return + val virtualFile = modifierListOwner.containingFile.virtualFile!! + val editor = FileEditorManager.getInstance(project).openTextEditor(OpenFileDescriptor(project, virtualFile, 0), false)!! + PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.document) + TemplateManager.getInstance(project).startTemplate(editor, createQualifierNameTemplate(stringTemplate)) + } + } + + private val javaInspection by lazy { SpringJavaAutowiringInspection() } + + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) = object : KtVisitorVoid() { + // TODO: SpringJavaAutowiringInspection.checkAutowiredMethod() is not accessible here + private fun checkAutowiredMethod(psiMethod: PsiMethod, holder: ProblemsHolder, springModel: CommonSpringModel, required: Boolean) { + val resourceAnnotation = SpringAutowireUtil.getResourceAnnotation(psiMethod) + when { + resourceAnnotation != null -> { + val propertyType = PropertyUtil.getPropertyType(psiMethod) ?: return + SpringJavaAutowiringInspection.checkAutowiredPsiMember(psiMethod, propertyType, holder, springModel, required) + } + psiMethod.parameterList.parametersCount == 0 && + SpringAutowireUtil.isAutowiredByAnnotation(psiMethod) -> { + val nameIdentifier = psiMethod.nameIdentifier ?: return + val message = SpringBundle.message("bean.autowiring.by.type.no.parameter.for.autowired.method", + if (psiMethod.isConstructor) "constructor" else "method") + holder.registerProblem(nameIdentifier, message) + } + else -> { + for (parameter in psiMethod.parameterList.parameters) { + if (AnnotationUtil.isAnnotated(parameter, "org.springframework.beans.factory.annotation.Value", true)) continue + SpringJavaAutowiringInspection.checkAutowiredPsiMember(parameter, parameter.type, holder, springModel, required) + } + } + } + } + + private fun Array.registerAdjustedProblems() { + registerWithElementsUnwrapped(holder, isOnTheFly) { qf, element -> + // Can't access AddSpringBeanQualifierFix class directly + + val klass = qf.javaClass + if (!klass.name.endsWith("AddSpringBeanQualifierFix")) return@registerWithElementsUnwrapped qf + + try { + val fields = klass.declaredFields + @Suppress("UNCHECKED_CAST") + val beanPointers = fields[1].apply { isAccessible = true }.get(qf) as Collection> + val annotationFqName = fields[2].apply { isAccessible = true }.get(qf) as String + val modifierListOwner = element.getNonStrictParentOfType() + ?: return@registerWithElementsUnwrapped qf + AddQualifierFix(modifierListOwner, beanPointers, annotationFqName) + } + catch(e: Exception) { + return@registerWithElementsUnwrapped null + } + } + } + + private fun T.processLightMember( + action: T.(holder: ProblemsHolder, model: CommonSpringModel, required: Boolean) -> Unit + ) { + val model = SpringAutowireUtil.getProcessingSpringModel(containingClass) ?: return + val required = SpringAutowireUtil.isRequired(this) + val tmpHolder = ProblemsHolder(holder.manager, containingFile, isOnTheFly) + action(this, tmpHolder, model, required) + tmpHolder.resultsArray.registerAdjustedProblems() + } + + private fun PsiMethod.processLightMethod() { + if (!SpringAutowireUtil.isInjectionPoint(this)) return + processLightMember { holder, model, required -> checkAutowiredMethod(this, holder, model, required) } + } + + private fun PsiField.processLightField() { + if (!SpringAutowireUtil.isAutowiredByAnnotation(this)) return + processLightMember { holder, model, required -> + SpringJavaAutowiringInspection.checkAutowiredPsiMember(this, type, holder, model, required) + } + } + + private fun PsiClass.processLightClass() { + javaInspection.checkClass(this, holder.manager, isOnTheFly)?.registerAdjustedProblems() + } + + override fun visitNamedFunction(function: KtNamedFunction) { + function.toLightMethods().firstOrNull()?.processLightMethod() + } + + override fun visitProperty(property: KtProperty) { + for (lightElement in property.toLightElements()) { + when (lightElement) { + is KtLightMethod -> lightElement.processLightMethod() + is KtLightField -> lightElement.processLightField() + } + } + } + + override fun visitClassOrObject(classOrObject: KtClassOrObject) { + classOrObject.toLightClass()?.processLightClass() + } + } +} \ No newline at end of file diff --git a/ultimate/src/org/jetbrains/kotlin/idea/spring/references/KtSpringQualifierReference.kt b/ultimate/src/org/jetbrains/kotlin/idea/spring/references/KtSpringQualifierReference.kt index d24387b929e..73fe67d877c 100644 --- a/ultimate/src/org/jetbrains/kotlin/idea/spring/references/KtSpringQualifierReference.kt +++ b/ultimate/src/org/jetbrains/kotlin/idea/spring/references/KtSpringQualifierReference.kt @@ -23,21 +23,44 @@ import com.intellij.spring.model.converters.SpringConverterUtil import com.intellij.spring.model.highlighting.SpringAutowireUtil import com.intellij.spring.model.jam.qualifiers.SpringJamQualifier import com.intellij.spring.model.utils.SpringModelSearchers +import com.intellij.spring.references.SpringQualifierReference +import com.intellij.util.IncorrectOperationException +import org.jetbrains.kotlin.asJava.KtLightElement import org.jetbrains.kotlin.asJava.toLightAnnotation import org.jetbrains.kotlin.asJava.toLightElements import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.imports.importableFqName +import org.jetbrains.kotlin.idea.search.allScope import org.jetbrains.kotlin.idea.spring.springModel import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType +import org.jetbrains.kotlin.psi.psiUtil.plainContent import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import java.util.* // Based on com.intellij.spring.references.SpringQualifierReference -class KtSpringQualifierReference(element: KtStringTemplateExpression) : PsiReferenceBase.Poly(element, false) { +// It inherits from SpringQualifierReference to allow reuse of existing Java inspections which explicitly check for SpringQualifierReference +class KtSpringQualifierReference(element: KtStringTemplateExpression) : SpringQualifierReference(StringTemplatePsiLiteralWrapper(element)) { + class StringTemplatePsiLiteralWrapper( + val element: KtStringTemplateExpression + ) : PsiElement by element, PsiLiteralExpression, KtLightElement, PsiCompiledElement { + override fun getName() = null + override fun setName(newName: String) = throw IncorrectOperationException() + + override fun getValue() = element.plainContent + override fun getType() = PsiType.getJavaLangString(element.manager, element.project.allScope()) + + override val clsDelegate: KtStringTemplateExpression + get() = element + override val kotlinOrigin: KtStringTemplateExpression + get() = element + + override fun getMirror() = null + } + private fun getAnnotationFqName(): String? { val annotationEntry = element.getStrictParentOfType() ?: return null val context = annotationEntry.analyze(BodyResolveMode.PARTIAL) diff --git a/ultimate/testData/inspections/spring/autowiring/ambiguousConstructors.kt b/ultimate/testData/inspections/spring/autowiring/ambiguousConstructors.kt new file mode 100644 index 00000000000..859056336d7 --- /dev/null +++ b/ultimate/testData/inspections/spring/autowiring/ambiguousConstructors.kt @@ -0,0 +1,60 @@ +// WITH_RUNTIME + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Component +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration + +@Configuration +@ComponentScan +open class AmbiguousConstructors + +// Not reported if there is only one constructor + +@Component +open class SingleConstructor1(n: Int) + +@Component +open class SingleConstructor2 { + constructor(n: Int) +} + +// Not reported if no-arg constructor is present + +@Component +open class NoArgConstructor1() { + constructor(n: Int): this() + constructor(s: String): this() +} + +@Component +open class NoArgConstructor2 { + constructor() + constructor(n: Int) + constructor(s: String) +} + +// Not reported if @Autowired constructor is present +@Component +open class AutowiredConstructor1 { + @Autowired constructor(n: Int) + constructor(s: String) +} + +@Component +open class AutowiredConstructor2 @Autowired constructor(n: Int) { + constructor(s: String): this(0) +} + +// Reported + +@Component +open class AmbigiousConstructor1 { + constructor(n: Int) + constructor(s: String) +} + +@Component +open class AmbigiousConstructor2(n: Int) { + constructor(s: String): this(0) +} \ No newline at end of file diff --git a/ultimate/testData/inspections/spring/autowiring/inspectionData/expected.xml b/ultimate/testData/inspections/spring/autowiring/inspectionData/expected.xml new file mode 100644 index 00000000000..34615eaae4f --- /dev/null +++ b/ultimate/testData/inspections/spring/autowiring/inspectionData/expected.xml @@ -0,0 +1,119 @@ + + + ambiguousConstructors.kt + 52 + light_idea_test_case + + Autowiring for Bean Class (Kotlin) + Class doesn't contain matching constructor for autowiring + + + ambiguousConstructors.kt + 58 + light_idea_test_case + + Autowiring for Bean Class (Kotlin) + Class doesn't contain matching constructor for autowiring + + + + multipleAutowiredConstructors.kt + 30 + light_idea_test_case + + Autowiring for Bean Class (Kotlin) + Only one constructor can have @Autowired annotation + + + multipleAutowiredConstructors.kt + 31 + light_idea_test_case + + Autowiring for Bean Class (Kotlin) + Only one constructor can have @Autowired annotation + + + multipleAutowiredConstructors.kt + 35 + light_idea_test_case + + Autowiring for Bean Class (Kotlin) + Only one constructor can have @Autowired annotation + + + multipleAutowiredConstructors.kt + 36 + light_idea_test_case + + Autowiring for Bean Class (Kotlin) + Only one constructor can have @Autowired annotation + + + + parameterlessFunction.kt + 15 + light_idea_test_case + + Autowiring for Bean Class (Kotlin) + Autowired method without parameters + + + + invalidAutowiredResource.kt + 24 + light_idea_test_case + + Autowiring for Bean Class (Kotlin) + Could not autowire. Bean should be of 'invalidAutowiredResource.BarBean' type + + + invalidAutowiredResource.kt + 31 + light_idea_test_case + + Autowiring for Bean Class (Kotlin) + Cannot resolve bean 'fooBeannn' + + + + invalidAutowiredQualifier.kt + 23 + light_idea_test_case + + Autowiring for Bean Class (Kotlin) + Could not autowire. Qualified bean must be of 'BarBean' type. + + + invalidAutowiredQualifier.kt + 30 + light_idea_test_case + + Autowiring for Bean Class (Kotlin) + Cannot find bean with qualifier 'fooBeannn' + + + invalidAutowiredQualifier.kt + 37 + light_idea_test_case + + Autowiring for Bean Class (Kotlin) + Cannot find bean qualified with @Qualifier + + + + invalidAutowiredByType.kt + 23 + light_idea_test_case + + Autowiring for Bean Class (Kotlin) + Could not autowire. No beans of 'String' type found. + + + invalidAutowiredByType.kt + 29 + light_idea_test_case + + Autowiring for Bean Class (Kotlin) + <html><table><tr><td>Could not autowire. There is more than one bean of 'AbstractBarBean' type.</td></tr><tr><td><table><tr><td valign='top'>Beans:</td><td>abstractBarBean&nbsp;&nbsp; (invalidAutowiredByType.kt)<br>barBean1&nbsp;&nbsp; (invalidAutowiredByType.kt)<br>barBean2&nbsp;&nbsp; (invalidAutowiredByType.kt)<br></td></tr></table></td></tr></table></html> + + diff --git a/ultimate/testData/inspections/spring/autowiring/inspectionData/inspections.test b/ultimate/testData/inspections/spring/autowiring/inspectionData/inspections.test new file mode 100644 index 00000000000..f046004bc8e --- /dev/null +++ b/ultimate/testData/inspections/spring/autowiring/inspectionData/inspections.test @@ -0,0 +1,3 @@ +// INSPECTION_CLASS: org.jetbrains.kotlin.idea.spring.inspections.SpringKotlinAutowiringInspection +// FIXTURE_CLASS: org.jetbrains.kotlin.idea.spring.tests.SpringTestFixtureExtension +// CONFIGURE_SPRING_FILE_SET \ No newline at end of file diff --git a/ultimate/testData/inspections/spring/autowiring/invalidAutowiredByType.kt b/ultimate/testData/inspections/spring/autowiring/invalidAutowiredByType.kt new file mode 100644 index 00000000000..da5d5774f1e --- /dev/null +++ b/ultimate/testData/inspections/spring/autowiring/invalidAutowiredByType.kt @@ -0,0 +1,30 @@ + +// WITH_RUNTIME + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Component +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration +import org.springframework.beans.factory.annotation.Qualifier + +@Configuration +@ComponentScan +open class InvalidAutowiredByTypeQualifiers + +abstract class AbstractBarBean +@Component +open class BarBean1 : AbstractBarBean() +@Component +open class BarBean2 : AbstractBarBean() + +@Component +open class NoAutowiredCandidates() { + @Autowired + lateinit var bean: String +} + +@Component +open class MultipleAutowiredCandidates() { + @Autowired + lateinit var bean: AbstractBarBean +} \ No newline at end of file diff --git a/ultimate/testData/inspections/spring/autowiring/invalidAutowiredQualifier.kt b/ultimate/testData/inspections/spring/autowiring/invalidAutowiredQualifier.kt new file mode 100644 index 00000000000..999dfb23e2f --- /dev/null +++ b/ultimate/testData/inspections/spring/autowiring/invalidAutowiredQualifier.kt @@ -0,0 +1,39 @@ + +// WITH_RUNTIME + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Component +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration +import org.springframework.beans.factory.annotation.Qualifier + +@Configuration +@ComponentScan +open class MismatchedTypes + +@Component +open class FooBean + +@Component +open class BarBean + +@Component +open class MismatchedType() { + @Autowired + @Qualifier("fooBean") + lateinit var bean1: BarBean +} + +@Component +open class UnresolvedBeanRef() { + @Autowired + @Qualifier("fooBeannn") + lateinit var bean1: FooBean +} + +@Component +open class NamelessQualifier() { + @Autowired + @Qualifier + lateinit var bean1: FooBean +} \ No newline at end of file diff --git a/ultimate/testData/inspections/spring/autowiring/invalidAutowiredResource.kt b/ultimate/testData/inspections/spring/autowiring/invalidAutowiredResource.kt new file mode 100644 index 00000000000..27003a4b215 --- /dev/null +++ b/ultimate/testData/inspections/spring/autowiring/invalidAutowiredResource.kt @@ -0,0 +1,33 @@ + +// WITH_RUNTIME +// FULL_JDK + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Component +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration +import javax.annotation.Resource + +@Configuration +@ComponentScan +open class MismatchedTypes + +@Component +open class FooBean + +@Component +open class BarBean + +@Component +open class MismatchedType() { + @Autowired + @Resource(name = "fooBean") + lateinit var bean1: BarBean +} + +@Component +open class UnresolvedBeanRef() { + @Autowired + @Resource(name = "fooBeannn") + lateinit var bean1: FooBean +} \ No newline at end of file diff --git a/ultimate/testData/inspections/spring/autowiring/multipleAutowiredConstructors.kt b/ultimate/testData/inspections/spring/autowiring/multipleAutowiredConstructors.kt new file mode 100644 index 00000000000..ad2e2ec2970 --- /dev/null +++ b/ultimate/testData/inspections/spring/autowiring/multipleAutowiredConstructors.kt @@ -0,0 +1,37 @@ + +// WITH_RUNTIME + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Component +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration + +@Configuration +@ComponentScan +open class MultipleAutowiredConstructors + +// Not reported if required == false + +@Component +open class NonRequiredAutowired1 { + @Autowired(required = false) constructor(n: Int) + @Autowired constructor(s: String) +} + +@Component +open class NonRequiredAutowired2 @Autowired(required = false) constructor(n: Int) { + @Autowired constructor(s: String): this(0) +} + +// Reported + +@Component +open class BothAutowired1 { + @Autowired constructor(n: Int) + @Autowired constructor(s: String) +} + +@Component +open class BothAutowired2 @Autowired constructor(n: Int) { + @Autowired constructor(s: String): this(0) +} \ No newline at end of file diff --git a/ultimate/testData/inspections/spring/autowiring/parameterlessFunction.kt b/ultimate/testData/inspections/spring/autowiring/parameterlessFunction.kt new file mode 100644 index 00000000000..d57aff0e4e6 --- /dev/null +++ b/ultimate/testData/inspections/spring/autowiring/parameterlessFunction.kt @@ -0,0 +1,16 @@ + +// WITH_RUNTIME + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Component +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration + +@Configuration +@ComponentScan +open class ParameterlessFunctions + +@Component +open class ParameterlessFunction { + @Autowired fun foo() = 1 +} \ No newline at end of file diff --git a/ultimate/testData/quickFixes/spring/addQualifierAnnotation/.inspection b/ultimate/testData/quickFixes/spring/addQualifierAnnotation/.inspection new file mode 100644 index 00000000000..e188cf5be38 --- /dev/null +++ b/ultimate/testData/quickFixes/spring/addQualifierAnnotation/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.spring.inspections.SpringKotlinAutowiringInspection \ No newline at end of file diff --git a/ultimate/testData/quickFixes/spring/addQualifierAnnotation/ambiguousBean.kt b/ultimate/testData/quickFixes/spring/addQualifierAnnotation/ambiguousBean.kt new file mode 100644 index 00000000000..9c0833c2348 --- /dev/null +++ b/ultimate/testData/quickFixes/spring/addQualifierAnnotation/ambiguousBean.kt @@ -0,0 +1,27 @@ +// "Add qualifier" "true" +// FIXTURE_CLASS: org.jetbrains.kotlin.idea.spring.tests.SpringTestFixtureExtension +// CONFIGURE_SPRING_FILE_SET +// WITH_RUNTIME +// DISABLE-ERRORS + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Component +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration +import org.springframework.beans.factory.annotation.Qualifier + +@Configuration +@ComponentScan +open class InvalidAutowiredByTypeQualifiers + +abstract class BaseBarBean +@Component +open class BarBean1 : BaseBarBean() +@Component +open class BarBean2 : BaseBarBean() + +@Component +open class MultipleAutowiredCandidates() { + @Autowired + lateinit var bean: BaseBarBean +} \ No newline at end of file diff --git a/ultimate/testData/quickFixes/spring/addQualifierAnnotation/ambiguousBean.kt.after b/ultimate/testData/quickFixes/spring/addQualifierAnnotation/ambiguousBean.kt.after new file mode 100644 index 00000000000..bc306e9234b --- /dev/null +++ b/ultimate/testData/quickFixes/spring/addQualifierAnnotation/ambiguousBean.kt.after @@ -0,0 +1,27 @@ +// "Add qualifier" "true" +// FIXTURE_CLASS: org.jetbrains.kotlin.idea.spring.tests.SpringTestFixtureExtension +// CONFIGURE_SPRING_FILE_SET +// WITH_RUNTIME +// DISABLE-ERRORS + +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.stereotype.Component +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration +import org.springframework.beans.factory.annotation.Qualifier + +@Configuration +@ComponentScan +open class InvalidAutowiredByTypeQualifiers + +abstract class BaseBarBean +@Component +open class BarBean1 : BaseBarBean() +@Component +open class BarBean2 : BaseBarBean() + +@Component +open class MultipleAutowiredCandidates() { + @Qualifier("barBean1") @Autowired + lateinit var bean: BaseBarBean +} \ No newline at end of file diff --git a/ultimate/tests/org/jetbrains/kotlin/idea/quickfix/UltimateQuickFixTestGenerated.java b/ultimate/tests/org/jetbrains/kotlin/idea/quickfix/UltimateQuickFixTestGenerated.java deleted file mode 100644 index 90c0646dbe6..00000000000 --- a/ultimate/tests/org/jetbrains/kotlin/idea/quickfix/UltimateQuickFixTestGenerated.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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("ultimate/testData/quickFixes") -@TestDataPath("$PROJECT_ROOT") -@RunWith(JUnit3RunnerWithInners.class) -public class UltimateQuickFixTestGenerated extends AbstractQuickFixTest { - public void testAllFilesPresentInQuickFixes() throws Exception { - KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("ultimate/testData/quickFixes"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); - } - - @TestMetadata("ultimate/testData/quickFixes/spring") - @TestDataPath("$PROJECT_ROOT") - @RunWith(JUnit3RunnerWithInners.class) - public static class Spring extends AbstractQuickFixTest { - public void testAllFilesPresentInSpring() throws Exception { - KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("ultimate/testData/quickFixes/spring"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); - } - - @TestMetadata("ultimate/testData/quickFixes/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("ultimate/testData/quickFixes/spring/finalSpringAnnotatedDeclaration"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); - } - - @TestMetadata("classWithComponentRuntime.kt") - public void testClassWithComponentRuntime() throws Exception { - String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/quickFixes/spring/finalSpringAnnotatedDeclaration/classWithComponentRuntime.kt"); - doTest(fileName); - } - - @TestMetadata("classWithConfigurationRuntime.kt") - public void testClassWithConfigurationRuntime() throws Exception { - String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/quickFixes/spring/finalSpringAnnotatedDeclaration/classWithConfigurationRuntime.kt"); - doTest(fileName); - } - - @TestMetadata("classWithCustomConfigurationRuntime.kt") - public void testClassWithCustomConfigurationRuntime() throws Exception { - String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/quickFixes/spring/finalSpringAnnotatedDeclaration/classWithCustomConfigurationRuntime.kt"); - doTest(fileName); - } - - @TestMetadata("funWithBeanFinalClassRuntime.kt") - public void testFunWithBeanFinalClassRuntime() throws Exception { - String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/quickFixes/spring/finalSpringAnnotatedDeclaration/funWithBeanFinalClassRuntime.kt"); - doTest(fileName); - } - - @TestMetadata("funWithBeanOpenClassRuntime.kt") - public void testFunWithBeanOpenClassRuntime() throws Exception { - String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/quickFixes/spring/finalSpringAnnotatedDeclaration/funWithBeanOpenClassRuntime.kt"); - doTest(fileName); - } - - @TestMetadata("funWithCustomBeanFinalClassRuntime.kt") - public void testFunWithCustomBeanFinalClassRuntime() throws Exception { - String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/quickFixes/spring/finalSpringAnnotatedDeclaration/funWithCustomBeanFinalClassRuntime.kt"); - doTest(fileName); - } - - @TestMetadata("funWithCustomBeanOpenClassRuntime.kt") - public void testFunWithCustomBeanOpenClassRuntime() throws Exception { - String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/quickFixes/spring/finalSpringAnnotatedDeclaration/funWithCustomBeanOpenClassRuntime.kt"); - doTest(fileName); - } - } - } -} diff --git a/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/SpringTestFixtureExtension.kt b/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/SpringTestFixtureExtension.kt index 7faf6e9412d..e593ccd228b 100644 --- a/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/SpringTestFixtureExtension.kt +++ b/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/SpringTestFixtureExtension.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.idea.spring.tests import com.intellij.facet.impl.FacetUtil import com.intellij.openapi.module.Module import com.intellij.openapi.util.io.FileUtil +import com.intellij.psi.PsiFile import com.intellij.spring.facet.SpringFacet import com.intellij.spring.facet.SpringFileSet import com.intellij.testFramework.fixtures.CodeInsightTestFixture @@ -26,6 +27,7 @@ import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture import com.intellij.util.PathUtil import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil import org.jetbrains.kotlin.idea.test.TestFixtureExtension +import org.jetbrains.kotlin.test.InTextDirectivesUtils import org.jetbrains.kotlin.tests.ULTIMATE_TEST_ROOT import java.util.* @@ -89,4 +91,10 @@ fun SpringTestFixtureExtension.loadConfigByMainFilePath(testPath: String, fixtur val baseName = FileUtil.getNameWithoutExtension(mainFileName) val configFileName = if (baseName.endsWith("Xml")) "$baseName-config.xml" else mainFileName configureFileSet(fixture, listOf(PathUtil.toSystemIndependentName("${PathUtil.getParentPath(testPath)}/$configFileName"))) +} + +fun configureSpringFileSetByDirective(module: Module, directives: String, psiFiles: Collection) { + if (!InTextDirectivesUtils.isDirectiveDefined(directives, "// CONFIGURE_SPRING_FILE_SET")) return + val fileSet = SpringFacet.getInstance(module)!!.addFileSet("default", "default")!! + psiFiles.forEach { fileSet.addFile(it.virtualFile) } } \ No newline at end of file diff --git a/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/inspections/AbstractSpringInspectionTest.kt b/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/inspections/AbstractSpringInspectionTest.kt new file mode 100644 index 00000000000..183dab2287e --- /dev/null +++ b/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/inspections/AbstractSpringInspectionTest.kt @@ -0,0 +1,11 @@ +package org.jetbrains.kotlin.idea.spring.tests.inspections + +import com.intellij.psi.PsiFile +import org.jetbrains.kotlin.idea.codeInsight.AbstractInspectionTest +import org.jetbrains.kotlin.idea.spring.tests.configureSpringFileSetByDirective + +abstract class AbstractSpringInspectionTest : AbstractInspectionTest() { + override fun configExtra(psiFiles: List, options: String) { + configureSpringFileSetByDirective(myFixture.module, options, psiFiles) + } +} \ No newline at end of file diff --git a/ultimate/tests/org/jetbrains/kotlin/idea/codeInsight/UltimateInspectionTestGenerated.java b/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/inspections/SpringInspectionTestGenerated.java similarity index 58% rename from ultimate/tests/org/jetbrains/kotlin/idea/codeInsight/UltimateInspectionTestGenerated.java rename to ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/inspections/SpringInspectionTestGenerated.java index 93aaccdf0ba..96846678af9 100644 --- a/ultimate/tests/org/jetbrains/kotlin/idea/codeInsight/UltimateInspectionTestGenerated.java +++ b/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/inspections/SpringInspectionTestGenerated.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.jetbrains.kotlin.idea.codeInsight; +package org.jetbrains.kotlin.idea.spring.tests.inspections; import com.intellij.testFramework.TestDataPath; import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; @@ -27,28 +27,34 @@ import java.util.regex.Pattern; /** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ @SuppressWarnings("all") -@TestMetadata("ultimate/testData/inspections") +@TestMetadata("ultimate/testData/inspections/spring") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) -public class UltimateInspectionTestGenerated extends AbstractInspectionTest { - public void testAllFilesPresentInInspections() throws Exception { - KotlinTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("ultimate/testData/inspections"), Pattern.compile("^(inspections\\.test)$")); +public class SpringInspectionTestGenerated extends AbstractSpringInspectionTest { + public void testAllFilesPresentInSpring() throws Exception { + KotlinTestUtils.assertAllTestsPresentInSingleGeneratedClass(this.getClass(), new File("ultimate/testData/inspections/spring"), Pattern.compile("^(inspections\\.test)$")); } - @TestMetadata("spring/autowiredMembersInInvalidClass/inspectionData/inspections.test") - public void testSpring_autowiredMembersInInvalidClass_inspectionData_Inspections_test() throws Exception { + @TestMetadata("autowiredMembersInInvalidClass/inspectionData/inspections.test") + public void testAutowiredMembersInInvalidClass_inspectionData_Inspections_test() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/inspections/spring/autowiredMembersInInvalidClass/inspectionData/inspections.test"); doTest(fileName); } - @TestMetadata("spring/finalSpringAnnotatedDeclaration/inspectionData/inspections.test") - public void testSpring_finalSpringAnnotatedDeclaration_inspectionData_Inspections_test() throws Exception { + @TestMetadata("autowiring/inspectionData/inspections.test") + public void testAutowiring_inspectionData_Inspections_test() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/inspections/spring/autowiring/inspectionData/inspections.test"); + doTest(fileName); + } + + @TestMetadata("finalSpringAnnotatedDeclaration/inspectionData/inspections.test") + public void testFinalSpringAnnotatedDeclaration_inspectionData_Inspections_test() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/inspections/spring/finalSpringAnnotatedDeclaration/inspectionData/inspections.test"); doTest(fileName); } - @TestMetadata("spring/unconfiguredFacet/inspectionData/inspections.test") - public void testSpring_unconfiguredFacet_inspectionData_Inspections_test() throws Exception { + @TestMetadata("unconfiguredFacet/inspectionData/inspections.test") + public void testUnconfiguredFacet_inspectionData_Inspections_test() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/inspections/spring/unconfiguredFacet/inspectionData/inspections.test"); doTest(fileName); } diff --git a/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/quickfixes/AbstractSpringQuickFixTest.kt b/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/quickfixes/AbstractSpringQuickFixTest.kt new file mode 100644 index 00000000000..50c908efec4 --- /dev/null +++ b/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/quickfixes/AbstractSpringQuickFixTest.kt @@ -0,0 +1,10 @@ +package org.jetbrains.kotlin.idea.spring.tests.quickfixes + +import org.jetbrains.kotlin.idea.quickfix.AbstractQuickFixTest +import org.jetbrains.kotlin.idea.spring.tests.configureSpringFileSetByDirective + +abstract class AbstractSpringQuickFixTest : AbstractQuickFixTest() { + override fun configExtra(options: String) { + configureSpringFileSetByDirective(getModule(), options, listOf(getFile())) + } +} \ No newline at end of file diff --git a/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/quickfixes/SpringQuickFixTestGenerated.java b/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/quickfixes/SpringQuickFixTestGenerated.java new file mode 100644 index 00000000000..b698a33ef05 --- /dev/null +++ b/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/quickfixes/SpringQuickFixTestGenerated.java @@ -0,0 +1,103 @@ +/* + * 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.tests.quickfixes; + +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("ultimate/testData/quickFixes/spring") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class SpringQuickFixTestGenerated extends AbstractSpringQuickFixTest { + public void testAllFilesPresentInSpring() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("ultimate/testData/quickFixes/spring"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); + } + + @TestMetadata("ultimate/testData/quickFixes/spring/addQualifierAnnotation") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class AddQualifierAnnotation extends AbstractSpringQuickFixTest { + public void testAllFilesPresentInAddQualifierAnnotation() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("ultimate/testData/quickFixes/spring/addQualifierAnnotation"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); + } + + @TestMetadata("ambiguousBean.kt") + public void testAmbiguousBean() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/quickFixes/spring/addQualifierAnnotation/ambiguousBean.kt"); + doTest(fileName); + } + } + + @TestMetadata("ultimate/testData/quickFixes/spring/finalSpringAnnotatedDeclaration") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class FinalSpringAnnotatedDeclaration extends AbstractSpringQuickFixTest { + public void testAllFilesPresentInFinalSpringAnnotatedDeclaration() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("ultimate/testData/quickFixes/spring/finalSpringAnnotatedDeclaration"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true); + } + + @TestMetadata("classWithComponentRuntime.kt") + public void testClassWithComponentRuntime() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/quickFixes/spring/finalSpringAnnotatedDeclaration/classWithComponentRuntime.kt"); + doTest(fileName); + } + + @TestMetadata("classWithConfigurationRuntime.kt") + public void testClassWithConfigurationRuntime() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/quickFixes/spring/finalSpringAnnotatedDeclaration/classWithConfigurationRuntime.kt"); + doTest(fileName); + } + + @TestMetadata("classWithCustomConfigurationRuntime.kt") + public void testClassWithCustomConfigurationRuntime() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/quickFixes/spring/finalSpringAnnotatedDeclaration/classWithCustomConfigurationRuntime.kt"); + doTest(fileName); + } + + @TestMetadata("funWithBeanFinalClassRuntime.kt") + public void testFunWithBeanFinalClassRuntime() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/quickFixes/spring/finalSpringAnnotatedDeclaration/funWithBeanFinalClassRuntime.kt"); + doTest(fileName); + } + + @TestMetadata("funWithBeanOpenClassRuntime.kt") + public void testFunWithBeanOpenClassRuntime() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/quickFixes/spring/finalSpringAnnotatedDeclaration/funWithBeanOpenClassRuntime.kt"); + doTest(fileName); + } + + @TestMetadata("funWithCustomBeanFinalClassRuntime.kt") + public void testFunWithCustomBeanFinalClassRuntime() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/quickFixes/spring/finalSpringAnnotatedDeclaration/funWithCustomBeanFinalClassRuntime.kt"); + doTest(fileName); + } + + @TestMetadata("funWithCustomBeanOpenClassRuntime.kt") + public void testFunWithCustomBeanOpenClassRuntime() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/quickFixes/spring/finalSpringAnnotatedDeclaration/funWithCustomBeanOpenClassRuntime.kt"); + doTest(fileName); + } + } +} diff --git a/ultimate/tests/org/jetbrains/kotlin/tests/GenerateTests.kt b/ultimate/tests/org/jetbrains/kotlin/tests/GenerateTests.kt index 9c98b306e6a..700e79a8591 100644 --- a/ultimate/tests/org/jetbrains/kotlin/tests/GenerateTests.kt +++ b/ultimate/tests/org/jetbrains/kotlin/tests/GenerateTests.kt @@ -17,11 +17,11 @@ package org.jetbrains.kotlin.tests import org.jetbrains.kotlin.generators.tests.testGroup -import org.jetbrains.kotlin.idea.codeInsight.AbstractInspectionTest -import org.jetbrains.kotlin.idea.quickfix.AbstractQuickFixTest import org.jetbrains.kotlin.idea.spring.tests.findUsages.AbstractSpringFindUsagesTest import org.jetbrains.kotlin.idea.spring.tests.generate.AbstractGenerateSpringDependencyActionTest import org.jetbrains.kotlin.idea.spring.tests.gutter.AbstractSpringClassAnnotatorTest +import org.jetbrains.kotlin.idea.spring.tests.inspections.AbstractSpringInspectionTest +import org.jetbrains.kotlin.idea.spring.tests.quickfixes.AbstractSpringQuickFixTest import org.jetbrains.kotlin.idea.spring.tests.references.AbstractSpringReferenceCompletionHandlerTest import org.jetbrains.kotlin.idea.spring.tests.references.AbstractSpringReferenceCompletionTest import org.jetbrains.kotlin.idea.spring.tests.references.AbstractSpringReferenceNavigationTest @@ -31,12 +31,12 @@ fun main(args: Array) { System.setProperty("java.awt.headless", "true") testGroup("ultimate/tests", "ultimate/testData") { - testClass("UltimateInspectionTestGenerated") { - model("inspections", pattern = "^(inspections\\.test)$", singleClass = true) + testClass { + model("inspections/spring", pattern = "^(inspections\\.test)$", singleClass = true) } - testClass("UltimateQuickFixTestGenerated") { - model("quickFixes", pattern = "^([\\w\\-_]+)\\.kt$", filenameStartsLowerCase = true) + testClass { + model("quickFixes/spring", pattern = "^([\\w\\-_]+)\\.kt$", filenameStartsLowerCase = true) } testClass() {