From 1cd438c3f7f45f5147176ac7d0b8ddca2a23e6eb Mon Sep 17 00:00:00 2001 From: Toshiaki Kameyama Date: Wed, 11 Apr 2018 18:18:58 +0300 Subject: [PATCH] Add inspection "main should return Unit" #KT-16085 Fixed --- .../kotlin/idea/MainFunctionDetector.kt | 14 +++- .../MainFunctionReturnUnit.html | 5 ++ idea/src/META-INF/plugin.xml | 9 +++ idea/src/META-INF/plugin.xml.172 | 9 +++ idea/src/META-INF/plugin.xml.181 | 9 +++ idea/src/META-INF/plugin.xml.182 | 9 +++ idea/src/META-INF/plugin.xml.as31 | 9 +++ idea/src/META-INF/plugin.xml.as32 | 9 +++ .../MainFunctionReturnUnitInspection.kt | 66 +++++++++++++++++++ .../mainFunctionReturnUnit/.inspection | 1 + .../mainFunctionReturnUnit/notMain.kt | 2 + .../mainFunctionReturnUnit/notUnit1.kt | 3 + .../mainFunctionReturnUnit/notUnit1.kt.after | 3 + .../mainFunctionReturnUnit/notUnit2.kt | 3 + .../mainFunctionReturnUnit/notUnit2.kt.after | 3 + .../mainFunctionReturnUnit/notUnit3.kt | 4 ++ .../mainFunctionReturnUnit/notUnit3.kt.after | 4 ++ .../mainFunctionReturnUnit/unit.kt | 2 + .../LocalInspectionTestGenerated.java | 39 +++++++++++ 19 files changed, 200 insertions(+), 3 deletions(-) create mode 100644 idea/resources/inspectionDescriptions/MainFunctionReturnUnit.html create mode 100644 idea/src/org/jetbrains/kotlin/idea/inspections/MainFunctionReturnUnitInspection.kt create mode 100644 idea/testData/inspectionsLocal/mainFunctionReturnUnit/.inspection create mode 100644 idea/testData/inspectionsLocal/mainFunctionReturnUnit/notMain.kt create mode 100644 idea/testData/inspectionsLocal/mainFunctionReturnUnit/notUnit1.kt create mode 100644 idea/testData/inspectionsLocal/mainFunctionReturnUnit/notUnit1.kt.after create mode 100644 idea/testData/inspectionsLocal/mainFunctionReturnUnit/notUnit2.kt create mode 100644 idea/testData/inspectionsLocal/mainFunctionReturnUnit/notUnit2.kt.after create mode 100644 idea/testData/inspectionsLocal/mainFunctionReturnUnit/notUnit3.kt create mode 100644 idea/testData/inspectionsLocal/mainFunctionReturnUnit/notUnit3.kt.after create mode 100644 idea/testData/inspectionsLocal/mainFunctionReturnUnit/unit.kt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/idea/MainFunctionDetector.kt b/compiler/frontend/src/org/jetbrains/kotlin/idea/MainFunctionDetector.kt index d6556cbbcd4..b9abec86e8c 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/idea/MainFunctionDetector.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/idea/MainFunctionDetector.kt @@ -101,7 +101,11 @@ class MainFunctionDetector { companion object { - fun isMain(descriptor: DeclarationDescriptor, checkJvmStaticAnnotation: Boolean = true): Boolean { + fun isMain( + descriptor: DeclarationDescriptor, + checkJvmStaticAnnotation: Boolean = true, + checkReturnType: Boolean = true + ): Boolean { if (descriptor !is FunctionDescriptor) return false if (getJVMFunctionName(descriptor) != "main") { @@ -127,8 +131,7 @@ class MainFunctionDetector { return false } - val returnType = descriptor.returnType - if (returnType == null || !KotlinBuiltIns.isUnit(returnType)) return false + if (checkReturnType && !isMainReturnType(descriptor)) return false if (DescriptorUtils.isTopLevelDeclaration(descriptor)) return true @@ -138,6 +141,11 @@ class MainFunctionDetector { && (descriptor.hasJvmStaticAnnotation() || !checkJvmStaticAnnotation) } + fun isMainReturnType(descriptor: FunctionDescriptor): Boolean { + val returnType = descriptor.returnType + return returnType != null && KotlinBuiltIns.isUnit(returnType) + } + private fun getJVMFunctionName(functionDescriptor: FunctionDescriptor): String { return DescriptorUtils.getJvmName(functionDescriptor) ?: functionDescriptor.name.asString() } diff --git a/idea/resources/inspectionDescriptions/MainFunctionReturnUnit.html b/idea/resources/inspectionDescriptions/MainFunctionReturnUnit.html new file mode 100644 index 00000000000..7043219f03d --- /dev/null +++ b/idea/resources/inspectionDescriptions/MainFunctionReturnUnit.html @@ -0,0 +1,5 @@ + + +This inspection reports a main function with incorrect return type (should be Unit). + + diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 196f8452f4f..c3e81245147 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -2783,6 +2783,15 @@ language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.172 b/idea/src/META-INF/plugin.xml.172 index 477db1fa1cf..689faae300d 100644 --- a/idea/src/META-INF/plugin.xml.172 +++ b/idea/src/META-INF/plugin.xml.172 @@ -2783,6 +2783,15 @@ language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.181 b/idea/src/META-INF/plugin.xml.181 index 18197316d2a..74bc207d48d 100644 --- a/idea/src/META-INF/plugin.xml.181 +++ b/idea/src/META-INF/plugin.xml.181 @@ -2783,6 +2783,15 @@ language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.182 b/idea/src/META-INF/plugin.xml.182 index cc83f28386c..61210eddac4 100644 --- a/idea/src/META-INF/plugin.xml.182 +++ b/idea/src/META-INF/plugin.xml.182 @@ -2784,6 +2784,15 @@ language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.as31 b/idea/src/META-INF/plugin.xml.as31 index 278f670124e..e3065d4f451 100644 --- a/idea/src/META-INF/plugin.xml.as31 +++ b/idea/src/META-INF/plugin.xml.as31 @@ -2784,6 +2784,15 @@ language="kotlin" /> + + diff --git a/idea/src/META-INF/plugin.xml.as32 b/idea/src/META-INF/plugin.xml.as32 index 6c8ca8ab306..ffa290fd8c0 100644 --- a/idea/src/META-INF/plugin.xml.as32 +++ b/idea/src/META-INF/plugin.xml.as32 @@ -2785,6 +2785,15 @@ language="kotlin" /> + + diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/MainFunctionReturnUnitInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/MainFunctionReturnUnitInspection.kt new file mode 100644 index 00000000000..c3162ab24b9 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/MainFunctionReturnUnitInspection.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.codeInspection.LocalQuickFix +import com.intellij.codeInspection.ProblemDescriptor +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiElementVisitor +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.idea.MainFunctionDetector +import org.jetbrains.kotlin.idea.core.setType +import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor +import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.namedFunctionVisitor +import org.jetbrains.kotlin.psi.psiUtil.endOffset +import org.jetbrains.kotlin.psi.psiUtil.startOffset + +class MainFunctionReturnUnitInspection : AbstractKotlinInspection() { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { + return namedFunctionVisitor(fun(function: KtNamedFunction) { + if (function.name != "main") return + + val descriptor = function.descriptor as? FunctionDescriptor ?: return + if (!MainFunctionDetector.isMain(descriptor, checkReturnType = false)) return + if (MainFunctionDetector.isMainReturnType(descriptor)) return + + val funKeyword = function.funKeyword ?: return + val valueParameterList = function.valueParameterList ?: return + val typeReference = function.typeReference + val start = function.startOffset + val highlightRange = TextRange( + funKeyword.startOffset - start, + if (typeReference != null) typeReference.endOffset - start else valueParameterList.endOffset - start + ) + + holder.registerProblem( + holder.manager.createProblemDescriptor( + function, + highlightRange, + "main should return Unit", + ProblemHighlightType.GENERIC_ERROR_OR_WARNING, + isOnTheFly, + ChangeMainFunctionReturnTypeToUnitFix(function.typeReference != null) + ) + ) + }) + } +} + +private class ChangeMainFunctionReturnTypeToUnitFix(private val hasExplicitReturnType: Boolean) : LocalQuickFix { + override fun getName() = if (hasExplicitReturnType) "Change return type to Unit" else "Add explicit Unit return type" + + override fun getFamilyName() = name + + override fun applyFix(project: Project, descriptor: ProblemDescriptor) { + val function = descriptor.psiElement as? KtNamedFunction ?: return + function.setType(KotlinBuiltIns.FQ_NAMES.unit.asString()) + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/mainFunctionReturnUnit/.inspection b/idea/testData/inspectionsLocal/mainFunctionReturnUnit/.inspection new file mode 100644 index 00000000000..0037a5f015e --- /dev/null +++ b/idea/testData/inspectionsLocal/mainFunctionReturnUnit/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.MainFunctionReturnUnitInspection diff --git a/idea/testData/inspectionsLocal/mainFunctionReturnUnit/notMain.kt b/idea/testData/inspectionsLocal/mainFunctionReturnUnit/notMain.kt new file mode 100644 index 00000000000..338945e05c4 --- /dev/null +++ b/idea/testData/inspectionsLocal/mainFunctionReturnUnit/notMain.kt @@ -0,0 +1,2 @@ +// PROBLEM: none +fun test(args: Array): Int = 1 \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/mainFunctionReturnUnit/notUnit1.kt b/idea/testData/inspectionsLocal/mainFunctionReturnUnit/notUnit1.kt new file mode 100644 index 00000000000..a19f7a6cffc --- /dev/null +++ b/idea/testData/inspectionsLocal/mainFunctionReturnUnit/notUnit1.kt @@ -0,0 +1,3 @@ +// FIX: Add explicit Unit return type +// DISABLE-ERRORS +fun main(args: Array) = 1 \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/mainFunctionReturnUnit/notUnit1.kt.after b/idea/testData/inspectionsLocal/mainFunctionReturnUnit/notUnit1.kt.after new file mode 100644 index 00000000000..bff1ac1d1b5 --- /dev/null +++ b/idea/testData/inspectionsLocal/mainFunctionReturnUnit/notUnit1.kt.after @@ -0,0 +1,3 @@ +// FIX: Add explicit Unit return type +// DISABLE-ERRORS +fun main(args: Array): Unit = 1 \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/mainFunctionReturnUnit/notUnit2.kt b/idea/testData/inspectionsLocal/mainFunctionReturnUnit/notUnit2.kt new file mode 100644 index 00000000000..2bdf40a4e09 --- /dev/null +++ b/idea/testData/inspectionsLocal/mainFunctionReturnUnit/notUnit2.kt @@ -0,0 +1,3 @@ +// FIX: Change return type to Unit +// DISABLE-ERRORS +fun main(args: Array): Int = 1 \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/mainFunctionReturnUnit/notUnit2.kt.after b/idea/testData/inspectionsLocal/mainFunctionReturnUnit/notUnit2.kt.after new file mode 100644 index 00000000000..a7e4e3d933b --- /dev/null +++ b/idea/testData/inspectionsLocal/mainFunctionReturnUnit/notUnit2.kt.after @@ -0,0 +1,3 @@ +// FIX: Change return type to Unit +// DISABLE-ERRORS +fun main(args: Array): Unit = 1 \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/mainFunctionReturnUnit/notUnit3.kt b/idea/testData/inspectionsLocal/mainFunctionReturnUnit/notUnit3.kt new file mode 100644 index 00000000000..313a98e71ae --- /dev/null +++ b/idea/testData/inspectionsLocal/mainFunctionReturnUnit/notUnit3.kt @@ -0,0 +1,4 @@ +// DISABLE-ERRORS +fun main(args: Array): Int { + return 1 +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/mainFunctionReturnUnit/notUnit3.kt.after b/idea/testData/inspectionsLocal/mainFunctionReturnUnit/notUnit3.kt.after new file mode 100644 index 00000000000..f326f1244ab --- /dev/null +++ b/idea/testData/inspectionsLocal/mainFunctionReturnUnit/notUnit3.kt.after @@ -0,0 +1,4 @@ +// DISABLE-ERRORS +fun main(args: Array): Unit { + return 1 +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/mainFunctionReturnUnit/unit.kt b/idea/testData/inspectionsLocal/mainFunctionReturnUnit/unit.kt new file mode 100644 index 00000000000..95ed374bd53 --- /dev/null +++ b/idea/testData/inspectionsLocal/mainFunctionReturnUnit/unit.kt @@ -0,0 +1,2 @@ +// PROBLEM: none +fun main(args: Array) {} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index 6a84d27fc76..3fc4018179c 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -2320,6 +2320,45 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest { } } + @TestMetadata("idea/testData/inspectionsLocal/mainFunctionReturnUnit") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class MainFunctionReturnUnit extends AbstractLocalInspectionTest { + public void testAllFilesPresentInMainFunctionReturnUnit() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/mainFunctionReturnUnit"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true); + } + + @TestMetadata("notMain.kt") + public void testNotMain() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/mainFunctionReturnUnit/notMain.kt"); + doTest(fileName); + } + + @TestMetadata("notUnit1.kt") + public void testNotUnit1() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/mainFunctionReturnUnit/notUnit1.kt"); + doTest(fileName); + } + + @TestMetadata("notUnit2.kt") + public void testNotUnit2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/mainFunctionReturnUnit/notUnit2.kt"); + doTest(fileName); + } + + @TestMetadata("notUnit3.kt") + public void testNotUnit3() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/mainFunctionReturnUnit/notUnit3.kt"); + doTest(fileName); + } + + @TestMetadata("unit.kt") + public void testUnit() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/mainFunctionReturnUnit/unit.kt"); + doTest(fileName); + } + } + @TestMetadata("idea/testData/inspectionsLocal/mayBeConstant") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)