Introduce "Boolean literal arguments" inspection #KT-2029 Fixed
This commit is contained in:
committed by
Mikhail Glukhikh
parent
aa9e48b9b6
commit
0d7116aa5d
@@ -3164,6 +3164,15 @@
|
|||||||
language="kotlin"
|
language="kotlin"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.BooleanLiteralArgumentInspection"
|
||||||
|
displayName="Boolean literal argument without parameter name"
|
||||||
|
groupPath="Kotlin"
|
||||||
|
groupName="Style issues"
|
||||||
|
enabledByDefault="true"
|
||||||
|
level="INFO"
|
||||||
|
language="kotlin"
|
||||||
|
/>
|
||||||
|
|
||||||
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
|
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
|
||||||
|
|
||||||
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
This inspection reports boolean literal arguments should be named.
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* 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.IntentionWrapper
|
||||||
|
import com.intellij.codeInspection.ProblemHighlightType.GENERIC_ERROR_OR_WARNING
|
||||||
|
import com.intellij.codeInspection.ProblemsHolder
|
||||||
|
import com.intellij.openapi.util.TextRange
|
||||||
|
import org.jetbrains.kotlin.KtNodeTypes
|
||||||
|
import org.jetbrains.kotlin.diagnostics.Severity
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
|
||||||
|
import org.jetbrains.kotlin.idea.intentions.AddNameToArgumentIntention
|
||||||
|
import org.jetbrains.kotlin.idea.intentions.AddNamesToCallArgumentsIntention
|
||||||
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||||
|
|
||||||
|
class BooleanLiteralArgumentInspection : AbstractKotlinInspection() {
|
||||||
|
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean) =
|
||||||
|
valueArgumentVisitor(fun(argument: KtValueArgument) {
|
||||||
|
if (argument.getArgumentName() != null) return
|
||||||
|
val argumentExpression = argument.getArgumentExpression() as? KtConstantExpression ?: return
|
||||||
|
if (argumentExpression.node.elementType != KtNodeTypes.BOOLEAN_CONSTANT) return
|
||||||
|
if (argumentExpression.analyze().diagnostics.forElement(argumentExpression).any { it.severity == Severity.ERROR }) return
|
||||||
|
val call = argument.getStrictParentOfType<KtCallExpression>() ?: return
|
||||||
|
if (call.resolveToCall()?.resultingDescriptor?.hasStableParameterNames() != true) return
|
||||||
|
|
||||||
|
when {
|
||||||
|
call.valueArguments.takeLastWhile { it != argument }.none { !it.isNamed() } ->
|
||||||
|
holder.registerProblem(
|
||||||
|
argument,
|
||||||
|
"Boolean literal argument without parameter name",
|
||||||
|
GENERIC_ERROR_OR_WARNING,
|
||||||
|
IntentionWrapper(AddNameToArgumentIntention(), argument.containingKtFile)
|
||||||
|
)
|
||||||
|
AddNamesToCallArgumentsIntention.canAddNamesToCallArguments(call) ->
|
||||||
|
holder.registerProblem(
|
||||||
|
holder.manager.createProblemDescriptor(
|
||||||
|
call,
|
||||||
|
argument.textRange.shiftRight(-call.startOffset),
|
||||||
|
description,
|
||||||
|
highlightType,
|
||||||
|
isOnTheFly,
|
||||||
|
IntentionWrapper(AddNamesIntention(), file)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
private class AddNamesIntention : AddNamesToCallArgumentsIntention() {
|
||||||
|
override fun applicabilityRange(element: KtCallElement): TextRange? = element.textRange
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,26 +23,12 @@ import org.jetbrains.kotlin.idea.project.languageVersionSettings
|
|||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch
|
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch
|
||||||
|
|
||||||
class AddNamesToCallArgumentsIntention : SelfTargetingRangeIntention<KtCallElement>(
|
open class AddNamesToCallArgumentsIntention : SelfTargetingRangeIntention<KtCallElement>(
|
||||||
KtCallElement::class.java,
|
KtCallElement::class.java,
|
||||||
"Add names to call arguments"
|
"Add names to call arguments"
|
||||||
) {
|
) {
|
||||||
override fun applicabilityRange(element: KtCallElement): TextRange? {
|
override fun applicabilityRange(element: KtCallElement): TextRange? =
|
||||||
val arguments = element.valueArguments
|
element.calleeExpression?.textRange?.takeIf { canAddNamesToCallArguments(element) }
|
||||||
if (arguments.all { it.isNamed() || it is LambdaArgument }) return null
|
|
||||||
|
|
||||||
val resolvedCall = element.resolveToCall() ?: return null
|
|
||||||
if (!resolvedCall.resultingDescriptor.hasStableParameterNames()) return null
|
|
||||||
|
|
||||||
if (arguments.all {
|
|
||||||
AddNameToArgumentIntention.argumentMatchedAndCouldBeNamedInCall(it, resolvedCall, element.languageVersionSettings)
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
return element.calleeExpression?.textRange
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun applyTo(element: KtCallElement, editor: Editor?) {
|
override fun applyTo(element: KtCallElement, editor: Editor?) {
|
||||||
val arguments = element.valueArguments
|
val arguments = element.valueArguments
|
||||||
@@ -59,4 +45,19 @@ class AddNamesToCallArgumentsIntention : SelfTargetingRangeIntention<KtCallEleme
|
|||||||
argument.replace(newArgument)
|
argument.replace(newArgument)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun canAddNamesToCallArguments(element: KtCallElement): Boolean {
|
||||||
|
val arguments = element.valueArguments
|
||||||
|
if (arguments.all { it.isNamed() || it is LambdaArgument }) return false
|
||||||
|
|
||||||
|
val resolvedCall = element.resolveToCall() ?: return false
|
||||||
|
if (!resolvedCall.resultingDescriptor.hasStableParameterNames()) return false
|
||||||
|
|
||||||
|
return arguments.all {
|
||||||
|
AddNameToArgumentIntention.argumentMatchedAndCouldBeNamedInCall(it, resolvedCall, element.languageVersionSettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
org.jetbrains.kotlin.idea.inspections.BooleanLiteralArgumentInspection
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
fun foo(a: Boolean, b: Boolean, c: Boolean) {}
|
||||||
|
|
||||||
|
fun test(b: Boolean) {
|
||||||
|
foo(true, true, b<caret>)
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// HIGHLIGHT: GENERIC_ERROR_OR_WARNING
|
||||||
|
// FIX: Add 'c =' to argument
|
||||||
|
fun foo(a: Boolean, b: Boolean, c: Boolean) {}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
foo(true, true, true<caret>)
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// HIGHLIGHT: GENERIC_ERROR_OR_WARNING
|
||||||
|
// FIX: Add 'c =' to argument
|
||||||
|
fun foo(a: Boolean, b: Boolean, c: Boolean) {}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
foo(true, true, c = true)
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// HIGHLIGHT: GENERIC_ERROR_OR_WARNING
|
||||||
|
// FIX: Add names to call arguments
|
||||||
|
fun foo(a: Boolean, b: Boolean, c: Boolean) {}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
foo(true, true<caret>, true)
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// HIGHLIGHT: GENERIC_ERROR_OR_WARNING
|
||||||
|
// FIX: Add names to call arguments
|
||||||
|
fun foo(a: Boolean, b: Boolean, c: Boolean) {}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
foo(a = true, b = true, c = true)
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// HIGHLIGHT: GENERIC_ERROR_OR_WARNING
|
||||||
|
// FIX: Add 'b =' to argument
|
||||||
|
fun foo(a: Boolean, b: Boolean, c: Boolean) {}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
foo(true, <caret>true, c = true)
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// HIGHLIGHT: GENERIC_ERROR_OR_WARNING
|
||||||
|
// FIX: Add 'b =' to argument
|
||||||
|
fun foo(a: Boolean, b: Boolean, c: Boolean) {}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
foo(true, b = true, c = true)
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// HIGHLIGHT: GENERIC_ERROR_OR_WARNING
|
||||||
|
// FIX: Add names to call arguments
|
||||||
|
fun foo(a: Boolean, b: Boolean, c: Boolean) {}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
foo(<caret>true, true, c = true)
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
// HIGHLIGHT: GENERIC_ERROR_OR_WARNING
|
||||||
|
// FIX: Add names to call arguments
|
||||||
|
fun foo(a: Boolean, b: Boolean, c: Boolean) {}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
foo(a = true, b = true, c = true)
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
// DISABLE-ERRORS
|
||||||
|
fun foo(vararg a: Int, b: Boolean) {}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
foo(1, 2, 3, true<caret>)
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
fun foo(a: Boolean, b: Boolean, c: Boolean) {}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
foo(true, true, c = true<caret>)
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
fun foo(a: Boolean, vararg b: Int) {}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
foo(true<caret>, 1, 2, 3)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
fun foo(a: Boolean, b: Boolean, c: Int) {}
|
||||||
|
|
||||||
|
fun test(b: Boolean) {
|
||||||
|
foo(true, true, 1<caret>)
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
public class JavaClass {
|
||||||
|
void foo(boolean b) {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
// PROBLEM: none
|
||||||
|
fun test() {
|
||||||
|
JavaClass().foo(false<caret>)
|
||||||
|
}
|
||||||
+63
@@ -47,6 +47,69 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("idea/testData/inspectionsLocal/booleanLiteralArgument")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public static class BooleanLiteralArgument extends AbstractLocalInspectionTest {
|
||||||
|
private void runTest(String testDataFilePath) throws Exception {
|
||||||
|
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAllFilesPresentInBooleanLiteralArgument() throws Exception {
|
||||||
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/booleanLiteralArgument"), Pattern.compile("^([\\w\\-_]+)\\.(kt|kts)$"), TargetBackend.ANY, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("boolean.kt")
|
||||||
|
public void testBoolean() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/booleanLiteralArgument/boolean.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("booleanLiteral.kt")
|
||||||
|
public void testBooleanLiteral() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/booleanLiteralArgument/booleanLiteral.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("booleanLiteral2.kt")
|
||||||
|
public void testBooleanLiteral2() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/booleanLiteralArgument/booleanLiteral2.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("booleanLiteral3.kt")
|
||||||
|
public void testBooleanLiteral3() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/booleanLiteralArgument/booleanLiteral3.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("booleanLiteral4.kt")
|
||||||
|
public void testBooleanLiteral4() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/booleanLiteralArgument/booleanLiteral4.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("hasError.kt")
|
||||||
|
public void testHasError() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/booleanLiteralArgument/hasError.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("hasName.kt")
|
||||||
|
public void testHasName() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/booleanLiteralArgument/hasName.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("hasVararg.kt")
|
||||||
|
public void testHasVararg() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/booleanLiteralArgument/hasVararg.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("intLiteral.kt")
|
||||||
|
public void testIntLiteral() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/booleanLiteralArgument/intLiteral.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("javaMethod.kt")
|
||||||
|
public void testJavaMethod() throws Exception {
|
||||||
|
runTest("idea/testData/inspectionsLocal/booleanLiteralArgument/javaMethod.kt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("idea/testData/inspectionsLocal/branched")
|
@TestMetadata("idea/testData/inspectionsLocal/branched")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user