diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index 2169d487803..a6c56fe8408 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -100,12 +100,10 @@ import org.jetbrains.kotlin.idea.highlighter.* import org.jetbrains.kotlin.idea.imports.AbstractJsOptimizeImportsTest import org.jetbrains.kotlin.idea.imports.AbstractJvmOptimizeImportsTest import org.jetbrains.kotlin.idea.index.AbstractKotlinTypeAliasByExpansionShortNameIndexTest +import org.jetbrains.kotlin.idea.inspections.AbstractHLInspectionTest import org.jetbrains.kotlin.idea.inspections.AbstractLocalInspectionTest import org.jetbrains.kotlin.idea.inspections.AbstractMultiFileLocalInspectionTest -import org.jetbrains.kotlin.idea.intentions.AbstractConcatenatedStringGeneratorTest -import org.jetbrains.kotlin.idea.intentions.AbstractIntentionTest -import org.jetbrains.kotlin.idea.intentions.AbstractIntentionTest2 -import org.jetbrains.kotlin.idea.intentions.AbstractMultiFileIntentionTest +import org.jetbrains.kotlin.idea.intentions.* import org.jetbrains.kotlin.idea.intentions.declarations.AbstractJoinLinesTest import org.jetbrains.kotlin.idea.internal.AbstractBytecodeToolWindowTest import org.jetbrains.kotlin.idea.kdoc.AbstractKDocHighlightingTest @@ -1108,6 +1106,16 @@ fun main(args: Array) { model("quickfix/modifiers", pattern = pattern, filenameStartsLowerCase = true, recursive = false) model("quickfix/override/typeMismatchOnOverride", pattern = pattern, filenameStartsLowerCase = true, recursive = false) } + + testClass { + val pattern = "^(inspections\\.test)$" + model("inspections/redundantUnitReturnType", pattern = pattern, singleClass = true) + } + + testClass { + val pattern = "^([\\w\\-_]+)\\.(kt|kts)$" + model("intentions/specifyTypeExplicitly", pattern = pattern) + } } testGroup("idea/idea-fir/tests", "idea/idea-completion/testData") { diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/applicators/ApplicabilityRanges.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/applicators/ApplicabilityRanges.kt new file mode 100644 index 00000000000..dbbfbf9e6bc --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/applicators/ApplicabilityRanges.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.fir.applicators + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.idea.fir.api.applicator.applicabilityTarget +import org.jetbrains.kotlin.psi.KtCallableDeclaration + +object ApplicabilityRanges { + val SELF = applicabilityTarget { it } + + val CALLABLE_RETURN_TYPE = applicabilityTarget { decalration -> + decalration.typeReference?.typeElement + } +} \ No newline at end of file diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/applicators/CallableReturnTypeUpdaterApplicator.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/applicators/CallableReturnTypeUpdaterApplicator.kt new file mode 100644 index 00000000000..ad447fde7af --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/applicators/CallableReturnTypeUpdaterApplicator.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.fir.applicators + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.idea.KotlinBundle +import org.jetbrains.kotlin.idea.fir.api.applicator.HLApplicatorInput +import org.jetbrains.kotlin.idea.fir.api.applicator.applicator +import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession +import org.jetbrains.kotlin.idea.frontend.api.components.KtTypeRendererOptions +import org.jetbrains.kotlin.idea.frontend.api.types.KtType +import org.jetbrains.kotlin.idea.frontend.api.types.isUnit +import org.jetbrains.kotlin.idea.util.application.runWriteAction +import org.jetbrains.kotlin.psi.KtCallableDeclaration +import org.jetbrains.kotlin.psi.KtFunction +import org.jetbrains.kotlin.psi.KtPsiFactory + +object CallableReturnTypeUpdaterApplicator { + val applicator = applicator { + familyName(KotlinBundle.message("fix.change.return.type.family")) + + applyTo { declaration, type, project -> + val newTypeRef = if (!declaration.isProcedure(type)) { + // TODO use longTypeRepresentation and the shorten + KtPsiFactory(project ?: declaration.project).createType(type.shortTypeRepresentation) + } else null + runWriteAction { + declaration.typeReference = newTypeRef + } + } + } + + private fun KtCallableDeclaration.isProcedure(type: Type) = + type.isUnit && this is KtFunction && hasBlockBody() + + class Type( + val isUnit: Boolean, + val longTypeRepresentation: String, + val shortTypeRepresentation: String + ) : HLApplicatorInput { + override fun isValidFor(psi: PsiElement): Boolean = true + + companion object { + fun KtAnalysisSession.createByKtType(ktType: KtType): Type = Type( + isUnit = ktType.isUnit, + longTypeRepresentation = ktType.render(KtTypeRendererOptions.DEFAULT), + shortTypeRepresentation = ktType.render(KtTypeRendererOptions.SHORT_NAMES), + ) + + val UNIT = Type(isUnit = true, longTypeRepresentation = "kotlin.Unit", shortTypeRepresentation = "Unit") + } + } +} diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/inspections/AddFunctionReturnTypeIntention.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/inspections/AddFunctionReturnTypeIntention.kt deleted file mode 100644 index ce5c37ccdaf..00000000000 --- a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/inspections/AddFunctionReturnTypeIntention.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package org.jetbrains.kotlin.idea.fir.inspections - -import com.intellij.openapi.editor.Editor -import org.jetbrains.kotlin.idea.frontend.api.KtAnalysisSession -import org.jetbrains.kotlin.idea.frontend.api.types.* -import org.jetbrains.kotlin.psi.KtNamedFunction -import org.jetbrains.kotlin.psi.KtPsiFactory - -class AddFunctionReturnTypeIntention : - AbstractHighLevelApiBasedIntention( - KtNamedFunction::class.java, - { "Specify type explicitly" } - ) { - override fun isApplicableByPsi(element: KtNamedFunction): Boolean = - element.typeReference == null && !element.hasBlockBody() - - override fun KtAnalysisSession.analyzeAndGetData(element: KtNamedFunction): TypeCandidate? { - val returnType = element.getReturnKtType() - val approximated = approximateTypeToUpperDenotable(returnType) ?: return null - return TypeCandidate(approximated.render()) - } - - private tailrec fun approximateTypeToUpperDenotable(type: KtType): KtDenotableType? = when (type) { - is KtNonDenotableType -> when (type) { - is KtFlexibleType -> approximateTypeToUpperDenotable(type.upperBound) - is KtIntersectionType -> null - } - is KtDenotableType -> type - else -> null - } - - - override fun applyTo(element: KtNamedFunction, data: TypeCandidate, editor: Editor?) { - element.typeReference = KtPsiFactory(element).createType(data.candidate) - } -} - -data class TypeCandidate(val candidate: String) \ No newline at end of file diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/inspections/declarations/HLRedundantUnitReturnTypeInspection.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/inspections/declarations/HLRedundantUnitReturnTypeInspection.kt new file mode 100644 index 00000000000..3981b5f3b08 --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/inspections/declarations/HLRedundantUnitReturnTypeInspection.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.fir.inspections.declarations + +import com.intellij.codeInspection.CleanupLocalInspectionTool +import com.intellij.codeInspection.ProblemHighlightType +import org.jetbrains.kotlin.idea.KotlinBundle +import org.jetbrains.kotlin.idea.fir.api.* +import org.jetbrains.kotlin.idea.fir.api.applicator.inputProvider +import org.jetbrains.kotlin.idea.fir.api.applicator.presentation +import org.jetbrains.kotlin.idea.fir.api.applicator.with +import org.jetbrains.kotlin.idea.fir.applicators.ApplicabilityRanges +import org.jetbrains.kotlin.idea.fir.applicators.CallableReturnTypeUpdaterApplicator +import org.jetbrains.kotlin.idea.frontend.api.types.isUnit +import org.jetbrains.kotlin.psi.KtNamedFunction + +internal class HLRedundantUnitReturnTypeInspection : + AbstractHLInspection( + KtNamedFunction::class + ), CleanupLocalInspectionTool { + + override val applicabilityRange = ApplicabilityRanges.CALLABLE_RETURN_TYPE + + override val applicator = CallableReturnTypeUpdaterApplicator.applicator.with { + isApplicableByPsi { callable -> + val function = callable as? KtNamedFunction ?: return@isApplicableByPsi false + function.hasBlockBody() && function.typeReference != null + } + familyAndActionName(KotlinBundle.lazyMessage("remove.explicit.type.specification")) + } + + override val inputProvider = inputProvider { function -> + when { + function.getFunctionSymbol().annotatedType.type.isUnit -> CallableReturnTypeUpdaterApplicator.Type.UNIT + else -> null + } + } + + override val presentation = presentation { + inspectionText(KotlinBundle.message("redundant.unit.return.type")) + highlightType(ProblemHighlightType.LIKE_UNUSED_SYMBOL) + } +} diff --git a/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/intentions/declarations/HLSpecifyExplicitTypeForCallableDeclarationIntention.kt b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/intentions/declarations/HLSpecifyExplicitTypeForCallableDeclarationIntention.kt new file mode 100644 index 00000000000..4203415e257 --- /dev/null +++ b/idea/idea-fir/src/org/jetbrains/kotlin/idea/fir/intentions/declarations/HLSpecifyExplicitTypeForCallableDeclarationIntention.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.fir.intentions.declarations + +import org.jetbrains.kotlin.idea.KotlinBundle +import org.jetbrains.kotlin.idea.fir.api.* +import org.jetbrains.kotlin.idea.fir.api.applicator.HLApplicabilityRange +import org.jetbrains.kotlin.idea.fir.api.applicator.applicabilityTarget +import org.jetbrains.kotlin.idea.fir.api.applicator.inputProvider +import org.jetbrains.kotlin.idea.fir.api.applicator.with +import org.jetbrains.kotlin.idea.fir.applicators.ApplicabilityRanges +import org.jetbrains.kotlin.idea.fir.applicators.CallableReturnTypeUpdaterApplicator +import org.jetbrains.kotlin.idea.frontend.api.types.* +import org.jetbrains.kotlin.psi.* + +class HLSpecifyExplicitTypeForCallableDeclarationIntention : + AbstractHLIntention( + KtCallableDeclaration::class + ) { + override val applicator = CallableReturnTypeUpdaterApplicator.applicator.with { + isApplicableByPsi { declaration: KtCallableDeclaration -> + if (declaration is KtConstructor<*> || declaration is KtFunctionLiteral) return@isApplicableByPsi false + declaration.typeReference == null && (declaration as? KtNamedFunction)?.hasBlockBody() != true + } + familyAndActionName(KotlinBundle.lazyMessage("specify.return.type.explicitly")) + } + override val applicabilityRange: HLApplicabilityRange = ApplicabilityRanges.SELF + + override val inputProvider = inputProvider { declaration -> + val returnType = declaration.getReturnKtType() + val denotableType = returnType.approximateToPublicDenotable() ?: return@inputProvider null + with(CallableReturnTypeUpdaterApplicator.Type) { createByKtType(denotableType) } + } +} \ No newline at end of file diff --git a/idea/idea-fir/tests/org/jetbrains/kotlin/idea/inspections/AbstractHLInspectionTest.kt b/idea/idea-fir/tests/org/jetbrains/kotlin/idea/inspections/AbstractHLInspectionTest.kt new file mode 100644 index 00000000000..c3dfb4fab71 --- /dev/null +++ b/idea/idea-fir/tests/org/jetbrains/kotlin/idea/inspections/AbstractHLInspectionTest.kt @@ -0,0 +1,14 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.inspections + +import org.jetbrains.kotlin.idea.codeInsight.AbstractInspectionTest + +abstract class AbstractHLInspectionTest : AbstractInspectionTest() { + override fun isFirPlugin() = true + override fun inspectionClassDirective() = "// FIR_INSPECTION_CLASS:" + override fun registerGradlPlugin() {} +} \ No newline at end of file diff --git a/idea/idea-fir/tests/org/jetbrains/kotlin/idea/inspections/HLInspectionTestGenerated.java b/idea/idea-fir/tests/org/jetbrains/kotlin/idea/inspections/HLInspectionTestGenerated.java new file mode 100644 index 00000000000..b23d6f9adc3 --- /dev/null +++ b/idea/idea-fir/tests/org/jetbrains/kotlin/idea/inspections/HLInspectionTestGenerated.java @@ -0,0 +1,36 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.inspections; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.util.KtTestUtil; +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/inspections/redundantUnitReturnType") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class HLInspectionTestGenerated extends AbstractHLInspectionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInRedundantUnitReturnType() throws Exception { + KtTestUtil.assertAllTestsPresentInSingleGeneratedClassWithExcluded(this.getClass(), new File("idea/testData/inspections/redundantUnitReturnType"), Pattern.compile("^(inspections\\.test)$"), null); + } + + @TestMetadata("inspectionData/inspections.test") + public void testInspectionData_Inspections_test() throws Exception { + runTest("idea/testData/inspections/redundantUnitReturnType/inspectionData/inspections.test"); + } +} diff --git a/idea/idea-fir/tests/org/jetbrains/kotlin/idea/intentions/AbstractHLIntentionTest.kt b/idea/idea-fir/tests/org/jetbrains/kotlin/idea/intentions/AbstractHLIntentionTest.kt new file mode 100644 index 00000000000..884387b470c --- /dev/null +++ b/idea/idea-fir/tests/org/jetbrains/kotlin/idea/intentions/AbstractHLIntentionTest.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.intentions + +import com.intellij.codeInsight.intention.IntentionAction +import com.intellij.psi.PsiFile +import org.jetbrains.kotlin.test.uitls.IgnoreTests +import java.io.File + +abstract class AbstractHLIntentionTest : AbstractIntentionTest() { + override fun intentionFileName() = ".firIntention" + override fun isFirPlugin() = true + + override fun doTestFor(mainFile: File, pathToFiles: Map, intentionAction: IntentionAction, fileText: String) { + IgnoreTests.runTestIfNotDisabledByFileDirective(mainFile.toPath(), IgnoreTests.DIRECTIVES.IGNORE_FIR) { + super.doTestFor(mainFile, pathToFiles, intentionAction, fileText) + } + } + + override fun checkForErrorsAfter(fileText: String) {} + override fun checkForErrorsBefore(fileText: String) {} +} \ No newline at end of file diff --git a/idea/idea-fir/tests/org/jetbrains/kotlin/idea/intentions/HLIntentionTestGenerated.java b/idea/idea-fir/tests/org/jetbrains/kotlin/idea/intentions/HLIntentionTestGenerated.java new file mode 100644 index 00000000000..9ffd85ce8f3 --- /dev/null +++ b/idea/idea-fir/tests/org/jetbrains/kotlin/idea/intentions/HLIntentionTestGenerated.java @@ -0,0 +1,206 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.intentions; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.util.KtTestUtil; +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/intentions/specifyTypeExplicitly") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class HLIntentionTestGenerated extends AbstractHLIntentionTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInSpecifyTypeExplicitly() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/intentions/specifyTypeExplicitly"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), null, true); + } + + @TestMetadata("anonymousObject.kt") + public void testAnonymousObject() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/anonymousObject.kt"); + } + + @TestMetadata("backticked.kt") + public void testBackticked() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/backticked.kt"); + } + + @TestMetadata("badCaretPosition.kt") + public void testBadCaretPosition() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/badCaretPosition.kt"); + } + + @TestMetadata("classNameClashing.kt") + public void testClassNameClashing() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/classNameClashing.kt"); + } + + @TestMetadata("constructor.kt") + public void testConstructor() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/constructor.kt"); + } + + @TestMetadata("destructuringInLambda.kt") + public void testDestructuringInLambda() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/destructuringInLambda.kt"); + } + + @TestMetadata("enumType.kt") + public void testEnumType() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/enumType.kt"); + } + + @TestMetadata("forAsExpression.kt") + public void testForAsExpression() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/forAsExpression.kt"); + } + + @TestMetadata("functionType.kt") + public void testFunctionType() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/functionType.kt"); + } + + @TestMetadata("genericClass.kt") + public void testGenericClass() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/genericClass.kt"); + } + + @TestMetadata("genericClassWithTypeParameters.kt") + public void testGenericClassWithTypeParameters() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/genericClassWithTypeParameters.kt"); + } + + @TestMetadata("genericClassWithTypeParameters2.kt") + public void testGenericClassWithTypeParameters2() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/genericClassWithTypeParameters2.kt"); + } + + @TestMetadata("genericFunction.kt") + public void testGenericFunction() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/genericFunction.kt"); + } + + @TestMetadata("innerTypeParameter.kt") + public void testInnerTypeParameter() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/innerTypeParameter.kt"); + } + + @TestMetadata("innerTypeParameter2.kt") + public void testInnerTypeParameter2() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/innerTypeParameter2.kt"); + } + + @TestMetadata("lambdaParam.kt") + public void testLambdaParam() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/lambdaParam.kt"); + } + + @TestMetadata("localClass.kt") + public void testLocalClass() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/localClass.kt"); + } + + @TestMetadata("localClassInSecondTypeParameter.kt") + public void testLocalClassInSecondTypeParameter() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter.kt"); + } + + @TestMetadata("localClassInSecondTypeParameter2.kt") + public void testLocalClassInSecondTypeParameter2() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter2.kt"); + } + + @TestMetadata("localClassInSecondTypeParameter3.kt") + public void testLocalClassInSecondTypeParameter3() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter3.kt"); + } + + @TestMetadata("localClassInTypeParameter.kt") + public void testLocalClassInTypeParameter() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/localClassInTypeParameter.kt"); + } + + @TestMetadata("loopParameter.kt") + public void testLoopParameter() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/loopParameter.kt"); + } + + @TestMetadata("outClass.kt") + public void testOutClass() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/outClass.kt"); + } + + @TestMetadata("outClass2.kt") + public void testOutClass2() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/outClass2.kt"); + } + + @TestMetadata("outClass3.kt") + public void testOutClass3() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/outClass3.kt"); + } + + @TestMetadata("overriddenAsNull.kt") + public void testOverriddenAsNull() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/overriddenAsNull.kt"); + } + + @TestMetadata("overrideNotNullFunction.kt") + public void testOverrideNotNullFunction() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/overrideNotNullFunction.kt"); + } + + @TestMetadata("overrideNotNullProperty.kt") + public void testOverrideNotNullProperty() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/overrideNotNullProperty.kt"); + } + + @TestMetadata("overrideNullableFunction.kt") + public void testOverrideNullableFunction() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/overrideNullableFunction.kt"); + } + + @TestMetadata("overrideNullableProperty.kt") + public void testOverrideNullableProperty() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/overrideNullableProperty.kt"); + } + + @TestMetadata("propertyTypeFromGetter.kt") + public void testPropertyTypeFromGetter() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/propertyTypeFromGetter.kt"); + } + + @TestMetadata("publicMember.kt") + public void testPublicMember() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/publicMember.kt"); + } + + @TestMetadata("stringRedefined.kt") + public void testStringRedefined() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/stringRedefined.kt"); + } + + @TestMetadata("typeAlreadyProvided.kt") + public void testTypeAlreadyProvided() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/typeAlreadyProvided.kt"); + } + + @TestMetadata("unitType.kt") + public void testUnitType() throws Exception { + runTest("idea/testData/intentions/specifyTypeExplicitly/unitType.kt"); + } +} diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/KtAnalysisSession.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/KtAnalysisSession.kt index ef912edf1e7..a35b6d012d7 100644 --- a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/KtAnalysisSession.kt +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/KtAnalysisSession.kt @@ -85,6 +85,8 @@ abstract class KtAnalysisSession(final override val token: ValidityToken) : Vali val builtinTypes: KtBuiltinTypes get() = typeProvider.builtinTypes + fun KtType.approximateToPublicDenotable(): KtType? = typeProvider.approximateToPublicDenotable(this) + fun KtClassOrObjectSymbol.buildSelfClassType(): KtType = typeProvider.buildSelfClassType(this) fun KtElement.getDiagnostics(): Collection = diagnosticProvider.getDiagnosticsForElement(this) diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtTypeProvider.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtTypeProvider.kt index 587da915d6f..e90bd35b6f6 100644 --- a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtTypeProvider.kt +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/components/KtTypeProvider.kt @@ -13,6 +13,8 @@ import org.jetbrains.kotlin.idea.frontend.api.types.KtType abstract class KtTypeProvider : KtAnalysisSessionComponent() { abstract val builtinTypes: KtBuiltinTypes + abstract fun approximateToPublicDenotable(type: KtType): KtType? + abstract fun buildSelfClassType(symbol: KtClassOrObjectSymbol): KtType } diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirAnalysisSessionComponent.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirAnalysisSessionComponent.kt index 855336e6705..ea772b79def 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirAnalysisSessionComponent.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirAnalysisSessionComponent.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.idea.frontend.api.fir.components +import org.jetbrains.kotlin.fir.FirSession import org.jetbrains.kotlin.fir.FirSourceElement import org.jetbrains.kotlin.fir.analysis.diagnostics.FirPsiDiagnostic import org.jetbrains.kotlin.fir.diagnostics.ConeDiagnostic @@ -17,6 +18,8 @@ import org.jetbrains.kotlin.idea.frontend.api.fir.KtFirAnalysisSession internal interface KtFirAnalysisSessionComponent { val analysisSession: KtFirAnalysisSession + val rootModuleSession: FirSession get() = analysisSession.firResolveState.rootModuleSession + val firSymbolBuilder get() = analysisSession.firSymbolBuilder val firResolveState get() = analysisSession.firResolveState fun ConeKotlinType.asKtType() = analysisSession.firSymbolBuilder.buildKtType(this) diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirTypeProvider.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirTypeProvider.kt index 3fddeed7a34..224965245be 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirTypeProvider.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/components/KtFirTypeProvider.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.idea.frontend.api.fir.components import org.jetbrains.kotlin.fir.declarations.FirResolvePhase +import org.jetbrains.kotlin.fir.resolve.inference.inferenceComponents import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl import org.jetbrains.kotlin.fir.types.impl.ConeTypeParameterTypeImpl import org.jetbrains.kotlin.idea.frontend.api.ValidityToken @@ -14,9 +15,9 @@ import org.jetbrains.kotlin.idea.frontend.api.components.KtTypeProvider import org.jetbrains.kotlin.idea.frontend.api.fir.KtFirAnalysisSession import org.jetbrains.kotlin.idea.frontend.api.fir.symbols.KtFirClassOrObjectSymbol import org.jetbrains.kotlin.idea.frontend.api.fir.types.KtFirType +import org.jetbrains.kotlin.idea.frontend.api.fir.types.PublicTypeApproximator import org.jetbrains.kotlin.idea.frontend.api.symbols.KtClassOrObjectSymbol import org.jetbrains.kotlin.idea.frontend.api.types.KtType -import org.jetbrains.kotlin.idea.frontend.api.withValidityAssertion internal class KtFirTypeProvider( override val analysisSession: KtFirAnalysisSession, @@ -25,6 +26,18 @@ internal class KtFirTypeProvider( override val builtinTypes: KtBuiltinTypes = KtFirBuiltInTypes(analysisSession.firResolveState.rootModuleSession.builtinTypes, analysisSession.firSymbolBuilder, token) + override fun approximateToPublicDenotable(type: KtType): KtType? { + require(type is KtFirType) + val coneType = type.coneType + val approximatedConeType = PublicTypeApproximator.approximateTypeToPublicDenotable( + coneType, + rootModuleSession.inferenceComponents.ctx, + rootModuleSession + ) + return approximatedConeType?.asKtType() + } + + override fun buildSelfClassType(symbol: KtClassOrObjectSymbol): KtType { require(symbol is KtFirClassOrObjectSymbol) val type = symbol.firRef.withFir(FirResolvePhase.TYPES) { firClass -> diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/types/PublicTypeApproximator.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/types/PublicTypeApproximator.kt new file mode 100644 index 00000000000..419793ce5cf --- /dev/null +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/types/PublicTypeApproximator.kt @@ -0,0 +1,133 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.frontend.api.fir.types + +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.fir.resolve.calls.fullyExpandedClass +import org.jetbrains.kotlin.fir.resolve.lookupSuperTypes +import org.jetbrains.kotlin.fir.resolve.toSymbol +import org.jetbrains.kotlin.fir.types.* +import org.jetbrains.kotlin.fir.types.ConeKotlinType +import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl +import org.jetbrains.kotlin.types.AbstractTypeChecker +import org.jetbrains.kotlin.types.Variance + +internal object PublicTypeApproximator { + fun approximateTypeToPublicDenotable( + type: ConeKotlinType, + context: ConeInferenceContext, + session: FirSession, + ): ConeKotlinType? = approximate(type, context, session) as ConeKotlinType? + + private fun approximate( + type: ConeKotlinType, + context: ConeInferenceContext, + session: FirSession, + ): ConeTypeProjection? = when (type) { + is ConeFlexibleType -> approximate(type.upperBound, context, session) + is ConeCapturedType -> type + is ConeDefinitelyNotNullType -> ConeDefinitelyNotNullType( + approximate( + type.original, + context, + session, + ) as ConeKotlinType + ) + is ConeIntegerLiteralType -> type + is ConeIntersectionType -> context.commonSuperTypeOrNull(type.intersectedTypes.toList())?.let { approximate(it, context, session) } + is ConeLookupTagBasedType -> when (type) { + is ConeTypeParameterType -> type + is ConeClassErrorType -> null + is ConeClassLikeTypeImpl -> approximateClassLikeType(type, context, session) + else -> error("Unexpected type ${type::class}") + } + is ConeStubType -> null + } + + private fun approximateClassLikeType( + typeImpl: ConeClassLikeTypeImpl, + context: ConeInferenceContext, + session: FirSession, + ): ConeTypeProjection? { + val regularClass = typeImpl.classOrAnonymousClass(session) ?: return null + val type = if (needApproximationAsSuperType(regularClass)) { + firstSuperType(regularClass, session) ?: return null + } else { + typeImpl + } + + val typeClassifier = type.classOrAnonymousClass(session) ?: return null + + val typeArguments = approximateTypeParameters(type, typeClassifier, context, session)?.toTypedArray() ?: return null + return ConeClassLikeTypeImpl( + type.lookupTag, + typeArguments, + type.isNullable, + type.attributes + ) + } + + private fun approximateTypeParameters( + type: ConeClassLikeTypeImpl, + typeClassifier: FirClass<*>, + context: ConeInferenceContext, + session: FirSession + ): List? { + val result = mutableListOf() + if (type.typeArguments.size != typeClassifier.typeParameters.size) return null + for ((typeArg, typeParam) in type.typeArguments.zip(typeClassifier.typeParameters)) { + val variance = (typeParam as? FirTypeParameter)?.variance ?: return null + result += approximateTypeProjection(typeArg, context, session, variance) ?: return null + } + return result + } + + private fun firstSuperType(regularClass: FirClass<*>, session: FirSession): ConeClassLikeTypeImpl? { + val superTypes = lookupSuperTypes(regularClass, lookupInterfaces = true, deep = false, session, substituteTypes = true) + return superTypes.first() as? ConeClassLikeTypeImpl + } + + fun ConeClassLikeTypeImpl.classOrAnonymousClass(session: FirSession): FirClass<*>? { + val fir = lookupTag.toSymbol(session)?.fir ?: return null + if (fir is FirAnonymousObject) return fir + else return fir.fullyExpandedClass(session) + } + + private fun needApproximationAsSuperType(fir: FirClass<*>) = when (fir) { + is FirRegularClass -> fir.isLocal + is FirAnonymousObject -> true + else -> false + } + + private fun approximateTypeProjection( + typeProjection: ConeTypeProjection, + context: ConeInferenceContext, + session: FirSession, + variance: Variance, + ): ConeTypeProjection? { + val type = when (typeProjection) { + ConeStarProjection -> return ConeStarProjection + is ConeKotlinTypeProjection -> typeProjection.type + else -> error("Unexpected type ${typeProjection::class}") + } + val newType = when (val new = approximate(type, context, session)) { + ConeStarProjection -> return ConeStarProjection + is ConeKotlinTypeProjection -> new.type + null -> return null + else -> error("Unexpected type ${new::class}") + } + return when (variance) { + Variance.INVARIANT -> when { + with(context) { newType.typeConstructor().isAnyConstructor() } -> ConeStarProjection + AbstractTypeChecker.equalTypes(context, type, newType) -> newType + else -> ConeStarProjection + } + Variance.IN_VARIANCE -> if (AbstractTypeChecker.isSubtypeOf(context, newType, type)) newType else ConeStarProjection + Variance.OUT_VARIANCE -> if (AbstractTypeChecker.isSubtypeOf(context, type, newType)) newType else ConeStarProjection + } + } +} \ No newline at end of file diff --git a/idea/resources-en/inspectionDescriptions/HLRedundantUnitReturnType.html b/idea/resources-en/inspectionDescriptions/HLRedundantUnitReturnType.html new file mode 100644 index 00000000000..49626cac789 --- /dev/null +++ b/idea/resources-en/inspectionDescriptions/HLRedundantUnitReturnType.html @@ -0,0 +1,5 @@ + + +This inspection reports a redundant Unit return type which can be omitted. + + \ No newline at end of file diff --git a/idea/resources-en/intentionDescriptions/HLAddFunctionReturnTypeIntention/description.html b/idea/resources-en/intentionDescriptions/HLAddFunctionReturnTypeIntention/description.html new file mode 100644 index 00000000000..671f03b6397 --- /dev/null +++ b/idea/resources-en/intentionDescriptions/HLAddFunctionReturnTypeIntention/description.html @@ -0,0 +1,13 @@ + + +

Write your description here. + Start the description with a verb in 3rd person singular, like reports, detects, highlights. + In the first sentence, briefly explain what exactly the inspection helps you detect. + Make sure the sentence is not very long and complicated. + The first sentence must be in a dedicated paragraph separated from the rest of the text. This will make the description easier to read. + Make sure the description doesn’t just repeat the inspection title. +

+ +

Text after this comment will only be shown in the settings of the inspection.

+ + \ No newline at end of file diff --git a/idea/resources-en/intentionDescriptions/HLSpecifyExplicitTypeForCallableDeclarationIntention/description.html b/idea/resources-en/intentionDescriptions/HLSpecifyExplicitTypeForCallableDeclarationIntention/description.html new file mode 100644 index 00000000000..5ae4fcbb90b --- /dev/null +++ b/idea/resources-en/intentionDescriptions/HLSpecifyExplicitTypeForCallableDeclarationIntention/description.html @@ -0,0 +1,7 @@ + + +

+ Specify declaration return type explicitly +

+ + \ No newline at end of file diff --git a/idea/resources-fir/META-INF/extensions.xml b/idea/resources-fir/META-INF/extensions.xml index 6c246fb2a9f..59037fc488e 100644 --- a/idea/resources-fir/META-INF/extensions.xml +++ b/idea/resources-fir/META-INF/extensions.xml @@ -2,7 +2,7 @@ diff --git a/idea/resources-fir/META-INF/firInspections.xml b/idea/resources-fir/META-INF/firInspections.xml new file mode 100644 index 00000000000..003fc65c660 --- /dev/null +++ b/idea/resources-fir/META-INF/firInspections.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/idea/resources-fir/META-INF/firIntentions.xml b/idea/resources-fir/META-INF/firIntentions.xml new file mode 100644 index 00000000000..bf2c30a3cc0 --- /dev/null +++ b/idea/resources-fir/META-INF/firIntentions.xml @@ -0,0 +1,8 @@ + + + + org.jetbrains.kotlin.idea.fir.intentions.declarations.HLSpecifyExplicitTypeForCallableDeclarationIntention + Kotlin + + + \ No newline at end of file diff --git a/idea/resources-fir/META-INF/plugin.xml b/idea/resources-fir/META-INF/plugin.xml index 3b35f50be99..69c6efe8e26 100644 --- a/idea/resources-fir/META-INF/plugin.xml +++ b/idea/resources-fir/META-INF/plugin.xml @@ -37,6 +37,8 @@ The Kotlin FIR plugin provides language support in IntelliJ IDEA and Android Stu + + diff --git a/idea/resources-fir/inspectionDescriptions/AddFunctionReturnTypeIntention.html b/idea/resources-fir/inspectionDescriptions/AddFunctionReturnTypeIntention.html deleted file mode 100644 index f29f4353573..00000000000 --- a/idea/resources-fir/inspectionDescriptions/AddFunctionReturnTypeIntention.html +++ /dev/null @@ -1,5 +0,0 @@ - - -This intention adds an explicit type specification for functions. - - \ No newline at end of file diff --git a/idea/testData/inspections/redundantUnitReturnType/inspectionData/inspections.test b/idea/testData/inspections/redundantUnitReturnType/inspectionData/inspections.test index 770eda29a61..e8c779128fb 100644 --- a/idea/testData/inspections/redundantUnitReturnType/inspectionData/inspections.test +++ b/idea/testData/inspections/redundantUnitReturnType/inspectionData/inspections.test @@ -1 +1,2 @@ -// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.RedundantUnitReturnTypeInspection \ No newline at end of file +// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.RedundantUnitReturnTypeInspection +// FIR_INSPECTION_CLASS: org.jetbrains.kotlin.idea.fir.inspections.declarations.HLRedundantUnitReturnTypeInspection \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/.firIntention b/idea/testData/intentions/specifyTypeExplicitly/.firIntention new file mode 100644 index 00000000000..c91c7bd63fe --- /dev/null +++ b/idea/testData/intentions/specifyTypeExplicitly/.firIntention @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.fir.intentions.declarations.HLSpecifyExplicitTypeForCallableDeclarationIntention \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/anonymousObject.kt b/idea/testData/intentions/specifyTypeExplicitly/anonymousObject.kt index a033868e63b..642344a8efe 100644 --- a/idea/testData/intentions/specifyTypeExplicitly/anonymousObject.kt +++ b/idea/testData/intentions/specifyTypeExplicitly/anonymousObject.kt @@ -1,3 +1,4 @@ +// IGNORE_FIR class A { private fun foo() = { object {} } } \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/anonymousObject.kt.after b/idea/testData/intentions/specifyTypeExplicitly/anonymousObject.kt.after index 374168dfb5a..74139f27e15 100644 --- a/idea/testData/intentions/specifyTypeExplicitly/anonymousObject.kt.after +++ b/idea/testData/intentions/specifyTypeExplicitly/anonymousObject.kt.after @@ -1,3 +1,4 @@ +// IGNORE_FIR class A { private fun foo(): () -> Any = { object {} } } \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/backticked.kt b/idea/testData/intentions/specifyTypeExplicitly/backticked.kt index 66be7b9ab5f..e5ca237bf27 100644 --- a/idea/testData/intentions/specifyTypeExplicitly/backticked.kt +++ b/idea/testData/intentions/specifyTypeExplicitly/backticked.kt @@ -1,2 +1,3 @@ +// IGNORE_FIR class `1` val x = `1`() \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/backticked.kt.after b/idea/testData/intentions/specifyTypeExplicitly/backticked.kt.after index 158fdd3c91c..f817cb01821 100644 --- a/idea/testData/intentions/specifyTypeExplicitly/backticked.kt.after +++ b/idea/testData/intentions/specifyTypeExplicitly/backticked.kt.after @@ -1,2 +1,3 @@ +// IGNORE_FIR class `1` val x: `1` = `1`() \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/badCaretPosition.kt b/idea/testData/intentions/specifyTypeExplicitly/badCaretPosition.kt index 00c028d20be..fa07eac68b6 100644 --- a/idea/testData/intentions/specifyTypeExplicitly/badCaretPosition.kt +++ b/idea/testData/intentions/specifyTypeExplicitly/badCaretPosition.kt @@ -1,3 +1,4 @@ +// IGNORE_FIR // IS_APPLICABLE: false val x = "" \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/classNameClashing.kt b/idea/testData/intentions/specifyTypeExplicitly/classNameClashing.kt index 3c1116959ed..2372086559a 100644 --- a/idea/testData/intentions/specifyTypeExplicitly/classNameClashing.kt +++ b/idea/testData/intentions/specifyTypeExplicitly/classNameClashing.kt @@ -1,3 +1,4 @@ +// IGNORE_FIR // WITH_RUNTIME object Holder { diff --git a/idea/testData/intentions/specifyTypeExplicitly/classNameClashing.kt.after b/idea/testData/intentions/specifyTypeExplicitly/classNameClashing.kt.after index f7a67a94bac..cbff2fb6bf4 100644 --- a/idea/testData/intentions/specifyTypeExplicitly/classNameClashing.kt.after +++ b/idea/testData/intentions/specifyTypeExplicitly/classNameClashing.kt.after @@ -1,3 +1,4 @@ +// IGNORE_FIR // WITH_RUNTIME object Holder { diff --git a/idea/testData/intentions/specifyTypeExplicitly/localClass.kt b/idea/testData/intentions/specifyTypeExplicitly/localClass.kt index a9d9ab11fef..fa6766cc5cc 100644 --- a/idea/testData/intentions/specifyTypeExplicitly/localClass.kt +++ b/idea/testData/intentions/specifyTypeExplicitly/localClass.kt @@ -1,3 +1,4 @@ +// IGNORE_FIR class A { private fun bar() = { class Local() diff --git a/idea/testData/intentions/specifyTypeExplicitly/localClass.kt.after b/idea/testData/intentions/specifyTypeExplicitly/localClass.kt.after index 7bde653b217..7ffd9643ad2 100644 --- a/idea/testData/intentions/specifyTypeExplicitly/localClass.kt.after +++ b/idea/testData/intentions/specifyTypeExplicitly/localClass.kt.after @@ -1,5 +1,6 @@ +// IGNORE_FIR class A { - private fun bar(): () -> Any = { + private fun bar(): () -> Any = { class Local() Local() } diff --git a/idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter3.kt b/idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter3.kt index b3464b8aa46..8389c588e67 100644 --- a/idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter3.kt +++ b/idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter3.kt @@ -1,3 +1,4 @@ +// IGNORE_FIR open class F class TestClass diff --git a/idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter3.kt.after b/idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter3.kt.after index 40da2097f01..1481c2242e2 100644 --- a/idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter3.kt.after +++ b/idea/testData/intentions/specifyTypeExplicitly/localClassInSecondTypeParameter3.kt.after @@ -1,3 +1,4 @@ +// IGNORE_FIR open class F class TestClass diff --git a/idea/testData/intentions/specifyTypeExplicitly/loopParameter.kt b/idea/testData/intentions/specifyTypeExplicitly/loopParameter.kt index 3186d98f203..670ab2396c9 100644 --- a/idea/testData/intentions/specifyTypeExplicitly/loopParameter.kt +++ b/idea/testData/intentions/specifyTypeExplicitly/loopParameter.kt @@ -1,3 +1,4 @@ +// IGNORE_FIR // WITH_RUNTIME import java.util.HashMap diff --git a/idea/testData/intentions/specifyTypeExplicitly/loopParameter.kt.after b/idea/testData/intentions/specifyTypeExplicitly/loopParameter.kt.after index afda0d44a70..9715c773509 100644 --- a/idea/testData/intentions/specifyTypeExplicitly/loopParameter.kt.after +++ b/idea/testData/intentions/specifyTypeExplicitly/loopParameter.kt.after @@ -1,3 +1,4 @@ +// IGNORE_FIR // WITH_RUNTIME import java.util.HashMap diff --git a/idea/testData/intentions/specifyTypeExplicitly/overriddenAsNull.kt b/idea/testData/intentions/specifyTypeExplicitly/overriddenAsNull.kt index 49facbbf905..158e6ad6566 100644 --- a/idea/testData/intentions/specifyTypeExplicitly/overriddenAsNull.kt +++ b/idea/testData/intentions/specifyTypeExplicitly/overriddenAsNull.kt @@ -7,3 +7,5 @@ interface I { class Test : I { override fun foo() = null } + +// IGNORE_FIR diff --git a/idea/testData/intentions/specifyTypeExplicitly/overriddenAsNull.kt.after b/idea/testData/intentions/specifyTypeExplicitly/overriddenAsNull.kt.after index f8e1cbcf895..bdfe227e05f 100644 --- a/idea/testData/intentions/specifyTypeExplicitly/overriddenAsNull.kt.after +++ b/idea/testData/intentions/specifyTypeExplicitly/overriddenAsNull.kt.after @@ -7,3 +7,5 @@ interface I { class Test : I { override fun foo(): String? = null } + +// IGNORE_FIR diff --git a/idea/testData/intentions/specifyTypeExplicitly/overrideNotNullFunction.kt b/idea/testData/intentions/specifyTypeExplicitly/overrideNotNullFunction.kt index aca4df68dd0..697e228feac 100644 --- a/idea/testData/intentions/specifyTypeExplicitly/overrideNotNullFunction.kt +++ b/idea/testData/intentions/specifyTypeExplicitly/overrideNotNullFunction.kt @@ -1,3 +1,4 @@ +// IGNORE_FIR // CHOOSE_NULLABLE_TYPE_IF_EXISTS // WITH_RUNTIME interface Base { diff --git a/idea/testData/intentions/specifyTypeExplicitly/overrideNotNullFunction.kt.after b/idea/testData/intentions/specifyTypeExplicitly/overrideNotNullFunction.kt.after index 0cafd272634..f475e2a12c4 100644 --- a/idea/testData/intentions/specifyTypeExplicitly/overrideNotNullFunction.kt.after +++ b/idea/testData/intentions/specifyTypeExplicitly/overrideNotNullFunction.kt.after @@ -1,3 +1,4 @@ +// IGNORE_FIR // CHOOSE_NULLABLE_TYPE_IF_EXISTS // WITH_RUNTIME interface Base { diff --git a/idea/testData/intentions/specifyTypeExplicitly/overrideNotNullProperty.kt b/idea/testData/intentions/specifyTypeExplicitly/overrideNotNullProperty.kt index 1eeb24880ce..300d629a26d 100644 --- a/idea/testData/intentions/specifyTypeExplicitly/overrideNotNullProperty.kt +++ b/idea/testData/intentions/specifyTypeExplicitly/overrideNotNullProperty.kt @@ -1,3 +1,4 @@ +// IGNORE_FIR // CHOOSE_NULLABLE_TYPE_IF_EXISTS // WITH_RUNTIME interface Base { diff --git a/idea/testData/intentions/specifyTypeExplicitly/overrideNotNullProperty.kt.after b/idea/testData/intentions/specifyTypeExplicitly/overrideNotNullProperty.kt.after index 37db9e8a0e2..a14967bdce6 100644 --- a/idea/testData/intentions/specifyTypeExplicitly/overrideNotNullProperty.kt.after +++ b/idea/testData/intentions/specifyTypeExplicitly/overrideNotNullProperty.kt.after @@ -1,3 +1,4 @@ +// IGNORE_FIR // CHOOSE_NULLABLE_TYPE_IF_EXISTS // WITH_RUNTIME interface Base { diff --git a/idea/testData/intentions/specifyTypeExplicitly/stringRedefined.kt b/idea/testData/intentions/specifyTypeExplicitly/stringRedefined.kt index 3fbcf12974c..8f053174c34 100644 --- a/idea/testData/intentions/specifyTypeExplicitly/stringRedefined.kt +++ b/idea/testData/intentions/specifyTypeExplicitly/stringRedefined.kt @@ -1,3 +1,4 @@ +// IGNORE_FIR class String {} val x = "" \ No newline at end of file diff --git a/idea/testData/intentions/specifyTypeExplicitly/stringRedefined.kt.after b/idea/testData/intentions/specifyTypeExplicitly/stringRedefined.kt.after index 3299d9b467e..69ca13d5690 100644 --- a/idea/testData/intentions/specifyTypeExplicitly/stringRedefined.kt.after +++ b/idea/testData/intentions/specifyTypeExplicitly/stringRedefined.kt.after @@ -1,5 +1,6 @@ import kotlin.String +// IGNORE_FIR class String {} val x: String = "" \ 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 d3768c15995..d146f9d1fd2 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractInspectionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractInspectionTest.kt @@ -37,13 +37,17 @@ abstract class AbstractInspectionTest : KotlinLightCodeInsightFixtureTestCase() try { super.setUp() EntryPointsManagerBase.getInstance(project).ADDITIONAL_ANNOTATIONS.add(ENTRY_POINT_ANNOTATION) - runWriteAction { FileTypeManager.getInstance().associateExtension(GroovyFileType.GROOVY_FILE_TYPE, "gradle") } + registerGradlPlugin() } catch (e: Throwable) { TestLoggerFactory.onTestFinished(false) throw e } } + protected open fun registerGradlPlugin() { + runWriteAction { FileTypeManager.getInstance().associateExtension(GroovyFileType.GROOVY_FILE_TYPE, "gradle") } + } + override fun tearDown() { EntryPointsManagerBase.getInstance(project).ADDITIONAL_ANNOTATIONS.remove(ENTRY_POINT_ANNOTATION) super.tearDown() @@ -55,11 +59,13 @@ abstract class AbstractInspectionTest : KotlinLightCodeInsightFixtureTestCase() protected open val forceUsePackageFolder: Boolean = false //workaround for IDEA-176033 - protected fun doTest(path: String) { + protected open fun inspectionClassDirective(): String = "// INSPECTION_CLASS: " + + protected open fun doTest(path: String) { val optionsFile = File(path) val options = FileUtil.loadFile(optionsFile, true) - val inspectionClass = Class.forName(InTextDirectivesUtils.findStringWithPrefixes(options, "// INSPECTION_CLASS: ")!!) + val inspectionClass = Class.forName(InTextDirectivesUtils.findStringWithPrefixes(options, inspectionClassDirective())!!) val fixtureClasses = InTextDirectivesUtils.findListWithPrefixes(options, "// FIXTURE_CLASS: ") diff --git a/idea/tests/org/jetbrains/kotlin/idea/intentions/AbstractIntentionTest.kt b/idea/tests/org/jetbrains/kotlin/idea/intentions/AbstractIntentionTest.kt index 6f400aa139a..553e89c28cf 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/intentions/AbstractIntentionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/intentions/AbstractIntentionTest.kt @@ -22,6 +22,7 @@ import com.intellij.refactoring.util.CommonRefactoringUtil import com.intellij.testFramework.PlatformTestUtil import junit.framework.ComparisonFailure import junit.framework.TestCase +import org.jetbrains.annotations.NotNull import org.jetbrains.kotlin.formatter.FormatSettingsUtil import org.jetbrains.kotlin.idea.test.* import org.jetbrains.kotlin.idea.util.application.executeCommand @@ -109,15 +110,11 @@ abstract class AbstractIntentionTest : KotlinLightCodeInsightFixtureTestCase() { val minJavaVersion = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// MIN_JAVA_VERSION: ") if (minJavaVersion != null && !SystemInfo.isJavaVersionAtLeast(minJavaVersion)) return@configureRegistryAndRun - if (file is KtFile && !InTextDirectivesUtils.isDirectiveDefined(fileText, "// SKIP_ERRORS_BEFORE")) { - DirectiveBasedActionUtils.checkForUnexpectedErrors(file as KtFile) - } + checkForErrorsBefore(fileText) - doTestFor(mainFile.name, pathToFiles, intentionAction, fileText) + doTestFor(mainFile, pathToFiles, intentionAction, fileText) - if (file is KtFile && !InTextDirectivesUtils.isDirectiveDefined(fileText, "// SKIP_ERRORS_AFTER")) { - DirectiveBasedActionUtils.checkForUnexpectedErrors(file as KtFile) - } + checkForErrorsAfter(fileText) } finally { ConfigLibraryUtil.unconfigureLibrariesByDirective(module, fileText) } @@ -126,6 +123,18 @@ abstract class AbstractIntentionTest : KotlinLightCodeInsightFixtureTestCase() { } } + protected open fun checkForErrorsAfter(fileText: String) { + if (file is KtFile && !InTextDirectivesUtils.isDirectiveDefined(fileText, "// SKIP_ERRORS_AFTER")) { + DirectiveBasedActionUtils.checkForUnexpectedErrors(file as KtFile) + } + } + + protected open fun checkForErrorsBefore(fileText: String) { + if (file is KtFile && !InTextDirectivesUtils.isDirectiveDefined(fileText, "// SKIP_ERRORS_BEFORE")) { + DirectiveBasedActionUtils.checkForUnexpectedErrors(file as KtFile) + } + } + private fun computeUnderProgressIndicatorAndWait(compute: () -> T): T { val result = CompletableFuture() val progressIndicator = ProgressIndicatorBase() @@ -143,7 +152,8 @@ abstract class AbstractIntentionTest : KotlinLightCodeInsightFixtureTestCase() { } @Throws(Exception::class) - private fun doTestFor(mainFilePath: String, pathToFiles: Map, intentionAction: IntentionAction, fileText: String) { + protected open fun doTestFor(mainFile: File, pathToFiles: Map, intentionAction: IntentionAction, fileText: String) { + val mainFilePath = mainFile.name val isApplicableString = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// ${isApplicableDirectiveName()}: ") val isApplicableExpected = isApplicableString == null || isApplicableString == "true"