Introduce "Boolean literal arguments" inspection #KT-2029 Fixed

This commit is contained in:
Toshiaki Kameyama
2018-10-24 11:58:59 +03:00
committed by Mikhail Glukhikh
parent aa9e48b9b6
commit 0d7116aa5d
21 changed files with 248 additions and 17 deletions
@@ -3164,6 +3164,15 @@
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"/>
<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.resolve.calls.model.ArgumentMatch
class AddNamesToCallArgumentsIntention : SelfTargetingRangeIntention<KtCallElement>(
open class AddNamesToCallArgumentsIntention : SelfTargetingRangeIntention<KtCallElement>(
KtCallElement::class.java,
"Add names to call arguments"
) {
override fun applicabilityRange(element: KtCallElement): TextRange? {
val arguments = element.valueArguments
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 applicabilityRange(element: KtCallElement): TextRange? =
element.calleeExpression?.textRange?.takeIf { canAddNamesToCallArguments(element) }
override fun applyTo(element: KtCallElement, editor: Editor?) {
val arguments = element.valueArguments
@@ -59,4 +45,19 @@ class AddNamesToCallArgumentsIntention : SelfTargetingRangeIntention<KtCallEleme
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)
}
@@ -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)
}
@@ -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)
}
@@ -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>)
}
@@ -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")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)