Can be parameter inspection #KT-10819 Fixed
This commit is contained in:
@@ -394,17 +394,26 @@ fun KtLambdaArgument.getLambdaArgumentName(bindingContext: BindingContext): Name
|
||||
fun KtExpression.asAssignment(): KtBinaryExpression? =
|
||||
if (KtPsiUtil.isAssignment(this)) this as KtBinaryExpression else null
|
||||
|
||||
fun KtDeclaration.visibilityModifier(): PsiElement? {
|
||||
private fun KtDeclaration.modifierFromTokenSet(set: TokenSet): PsiElement? {
|
||||
val modifierList = modifierList ?: return null
|
||||
return KtTokens.VISIBILITY_MODIFIERS.types
|
||||
.asSequence()
|
||||
.map { modifierList.getModifier(it as KtModifierKeywordToken) }
|
||||
.firstOrNull { it != null }
|
||||
return set.types
|
||||
.asSequence()
|
||||
.map { modifierList.getModifier(it as KtModifierKeywordToken) }
|
||||
.firstOrNull { it != null }
|
||||
|
||||
}
|
||||
|
||||
fun KtDeclaration.visibilityModifier() = modifierFromTokenSet(KtTokens.VISIBILITY_MODIFIERS)
|
||||
|
||||
fun KtDeclaration.visibilityModifierType(): KtModifierKeywordToken?
|
||||
= visibilityModifier()?.node?.elementType as KtModifierKeywordToken?
|
||||
|
||||
private val MODALITY_MODIFIERS = TokenSet.create(
|
||||
KtTokens.ABSTRACT_KEYWORD, KtTokens.FINAL_KEYWORD, KtTokens.SEALED_KEYWORD, KtTokens.OPEN_KEYWORD
|
||||
)
|
||||
|
||||
fun KtDeclaration.modalityModifier() = modifierFromTokenSet(MODALITY_MODIFIERS)
|
||||
|
||||
fun KtStringTemplateExpression.isPlain() = entries.all { it is KtLiteralStringTemplateEntry }
|
||||
|
||||
val KtDeclaration.containingClassOrObject: KtClassOrObject?
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<html>
|
||||
<body>
|
||||
This inspection reports primary constructor parameters that can have 'val' or 'var' removed.
|
||||
Unnecessary usage of 'val' and 'var' in primary constructor consumes unnecessary memory.
|
||||
</body>
|
||||
</html>
|
||||
@@ -1436,6 +1436,13 @@
|
||||
level="WEAK WARNING"
|
||||
/>
|
||||
|
||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.CanBeParameterInspection"
|
||||
displayName="Constructor parameter is never used as a property"
|
||||
groupName="Kotlin"
|
||||
enabledByDefault="true"
|
||||
level="WARNING"
|
||||
/>
|
||||
|
||||
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
|
||||
|
||||
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.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.psi.PsiElement
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import com.intellij.psi.PsiReference
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.search.PsiSearchHelper
|
||||
import com.intellij.psi.search.PsiSearchHelper.SearchCostResult.FEW_OCCURRENCES
|
||||
import com.intellij.psi.search.PsiSearchHelper.SearchCostResult.ZERO_OCCURRENCES
|
||||
import com.intellij.psi.search.searches.ReferencesSearch
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import org.jetbrains.kotlin.idea.quickfix.RemoveValVarFromParameterFix
|
||||
import org.jetbrains.kotlin.idea.references.KtSimpleNameReference
|
||||
import org.jetbrains.kotlin.idea.search.usagesSearch.getAccessorNames
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
|
||||
class CanBeParameterInspection : AbstractKotlinInspection() {
|
||||
private fun PsiReference.usedAsPropertyIn(klass: KtClass): Boolean {
|
||||
if (this !is KtSimpleNameReference) return true
|
||||
val nameExpression = element
|
||||
// this.x
|
||||
val parent = element.parent
|
||||
if (parent is KtQualifiedExpression) {
|
||||
if (parent.receiverExpression is KtThisExpression) return true
|
||||
}
|
||||
// x += something
|
||||
if (parent is KtBinaryExpression &&
|
||||
parent.left == element &&
|
||||
KtPsiUtil.isAssignment(parent)) return true
|
||||
// init / constructor / non-local property?
|
||||
var parameterUser: PsiElement = nameExpression
|
||||
do {
|
||||
parameterUser = PsiTreeUtil.getParentOfType(parameterUser, KtProperty::class.java, KtPropertyAccessor::class.java,
|
||||
KtClassInitializer::class.java, KtSecondaryConstructor::class.java) ?: return true
|
||||
} while (parameterUser is KtProperty && parameterUser.isLocal)
|
||||
return when (parameterUser) {
|
||||
is KtProperty -> parameterUser.containingClassOrObject !== klass
|
||||
is KtPropertyAccessor -> true
|
||||
is KtClassInitializer -> parameterUser.containingDeclaration !== klass
|
||||
is KtSecondaryConstructor -> parameterUser.getContainingClassOrObject() !== klass
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
||||
return object : KtVisitorVoid() {
|
||||
|
||||
override fun visitParameter(parameter: KtParameter) {
|
||||
// Applicable to val / var parameters of a class / object primary constructors
|
||||
val valOrVar = parameter.valOrVarKeyword ?: return
|
||||
val name = parameter.name ?: return
|
||||
if (parameter.hasModifier(KtTokens.OVERRIDE_KEYWORD)) return
|
||||
if (parameter.annotationEntries.isNotEmpty()) return
|
||||
val constructor = parameter.parent.parent as? KtPrimaryConstructor ?: return
|
||||
val klass = constructor.getContainingClassOrObject() as? KtClass ?: return
|
||||
if (klass.isData()) return
|
||||
|
||||
val useScope = parameter.useScope
|
||||
if (useScope is GlobalSearchScope) {
|
||||
val psiSearchHelper = PsiSearchHelper.SERVICE.getInstance(parameter.project)
|
||||
for (accessorName in parameter.getAccessorNames()) {
|
||||
when (psiSearchHelper.isCheapEnoughToSearch(accessorName, useScope, null, null)) {
|
||||
ZERO_OCCURRENCES -> {
|
||||
} // go on
|
||||
else -> return // accessor in use: should remain a property
|
||||
}
|
||||
}
|
||||
// TOO_MANY_OCCURRENCES: too expensive
|
||||
// ZERO_OCCURRENCES: unused at all, reported elsewhere
|
||||
if (psiSearchHelper.isCheapEnoughToSearch(name, useScope, null, null) != FEW_OCCURRENCES) return
|
||||
}
|
||||
// Find all references and check them
|
||||
val references = ReferencesSearch.search(parameter, useScope)
|
||||
if (references.none()) return
|
||||
if (references.any { it.usedAsPropertyIn(klass) }) return
|
||||
holder.registerProblem(
|
||||
valOrVar,
|
||||
"Constructor parameter is never used as a property",
|
||||
ProblemHighlightType.LIKE_UNUSED_SYMBOL,
|
||||
RemoveValVarFix(parameter)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RemoveValVarFix(val parameter: KtParameter) : LocalQuickFix {
|
||||
|
||||
private val fix = RemoveValVarFromParameterFix(parameter)
|
||||
|
||||
override fun getName() = fix.text
|
||||
|
||||
override fun getFamilyName() = fix.familyName
|
||||
|
||||
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||
parameter.valOrVarKeyword?.delete()
|
||||
// Delete visibility / modality, if any
|
||||
parameter.modifierList?.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<problems>
|
||||
<problem>
|
||||
<file>test.kt</file>
|
||||
<line>5</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Constructor parameter is never used as a property</problem_class>
|
||||
<description>Constructor parameter is never used as a property</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>test.kt</file>
|
||||
<line>9</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Constructor parameter is never used as a property</problem_class>
|
||||
<description>Constructor parameter is never used as a property</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>test.kt</file>
|
||||
<line>16</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Constructor parameter is never used as a property</problem_class>
|
||||
<description>Constructor parameter is never used as a property</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>test.kt</file>
|
||||
<line>52</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Constructor parameter is never used as a property</problem_class>
|
||||
<description>Constructor parameter is never used as a property</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>test.kt</file>
|
||||
<line>62</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Constructor parameter is never used as a property</problem_class>
|
||||
<description>Constructor parameter is never used as a property</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>test.kt</file>
|
||||
<line>70</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="NOT_USED_ELEMENT_ATTRIBUTES">Constructor parameter is never used as a property</problem_class>
|
||||
<description>Constructor parameter is never used as a property</description>
|
||||
</problem>
|
||||
</problems>
|
||||
@@ -0,0 +1 @@
|
||||
// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.CanBeParameterInspection
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
class NonUsed(val x: Int) // NO
|
||||
// NO
|
||||
data class UsedInData(val x: Int)
|
||||
// YES
|
||||
class UsedInProperty(val x: Int) {
|
||||
val y = x
|
||||
}
|
||||
// YES
|
||||
class UsedInInitializer(val x: Int) {
|
||||
val y: Int
|
||||
init {
|
||||
y = x
|
||||
}
|
||||
}
|
||||
// YES
|
||||
class UsedInConstructor(val x: Int) {
|
||||
fun foo(arg: Int) = arg
|
||||
|
||||
constructor(): this(42) {
|
||||
foo(x)
|
||||
}
|
||||
}
|
||||
// NO
|
||||
class UsedInFunction(val x: Int) {
|
||||
fun get() = x
|
||||
}
|
||||
// NO
|
||||
class UsedInGetter(val x: Int) {
|
||||
val y: Int
|
||||
get() = x
|
||||
}
|
||||
// NO
|
||||
class UsedInSetter(val x: Int) {
|
||||
var y: Int
|
||||
get() = field
|
||||
set(arg) { field = x + arg }
|
||||
}
|
||||
// NO
|
||||
class UsedInInnerClass(val x: Int) {
|
||||
inner class Inner {
|
||||
fun foo() = x
|
||||
}
|
||||
}
|
||||
// NO
|
||||
class UsedOutside(val x: Int)
|
||||
|
||||
fun use(): Int {
|
||||
val used = UsedOutside(30)
|
||||
return used.x
|
||||
}
|
||||
// YES
|
||||
class PrivateUsedInProperty(private val x: Int) {
|
||||
val y = x
|
||||
}
|
||||
// NO
|
||||
open class Base(protected open val x: Int)
|
||||
// NO
|
||||
override class UsedOverridden(override val x: Int) {
|
||||
val y = x
|
||||
}
|
||||
// YES
|
||||
class UsedInPropertyVar(var x: Int) {
|
||||
var y = x
|
||||
}
|
||||
// NO
|
||||
class UsedInPropertyAnnotated(@JvmField val x: Int) {
|
||||
val y = x
|
||||
}
|
||||
// YES
|
||||
class UsedWithoutThisInInitProperty(val x: Int) {
|
||||
init {
|
||||
val y = x
|
||||
}
|
||||
}
|
||||
// NO
|
||||
class UsedWithThisInInitProperty(val x: Int) {
|
||||
init {
|
||||
val y = this.x
|
||||
}
|
||||
}
|
||||
// NO
|
||||
class UsedWithLabeledThisInInitProperty(val x: Int) {
|
||||
init {
|
||||
run {
|
||||
val y = this@UsedWithLabeledThisInInitProperty.x
|
||||
}
|
||||
}
|
||||
}
|
||||
// NO
|
||||
class UsedInFunctionProperty(val x: Int) {
|
||||
fun get() {
|
||||
val y = x
|
||||
return y
|
||||
}
|
||||
}
|
||||
// NO
|
||||
class ModifiedInInit(var x: Int) {
|
||||
init {
|
||||
x += 2
|
||||
}
|
||||
}
|
||||
// NO
|
||||
open class UsedInOverride(val x: Int)
|
||||
|
||||
class UserInOverride(override val x: Int) : UsedInOverride(x)
|
||||
@@ -0,0 +1,2 @@
|
||||
// NO
|
||||
class UsedFromJava(val z: Int, val w: Int)
|
||||
@@ -0,0 +1,9 @@
|
||||
package user;
|
||||
|
||||
class User {
|
||||
public static void main(String[] args) {
|
||||
UsedFromJava used = new UsedFromJava(1, 2);
|
||||
System.out.println(used.getZ());
|
||||
System.out.println(used.getW());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.idea.inspections.CanBeParameterInspection
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Remove 'val' from parameter" "true"
|
||||
open class Base(open <caret>val x: Int) {
|
||||
val y = x
|
||||
}
|
||||
|
||||
class Derived(y: Int) : Base(y)
|
||||
@@ -0,0 +1,6 @@
|
||||
// "Remove 'val' from parameter" "true"
|
||||
open class Base(x: Int) {
|
||||
val y = x
|
||||
}
|
||||
|
||||
class Derived(y: Int) : Base(y)
|
||||
@@ -0,0 +1,4 @@
|
||||
// "Remove 'var' from parameter" "true"
|
||||
class UsedInProperty(<caret>var x: Int) {
|
||||
var y = x
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// "Remove 'var' from parameter" "true"
|
||||
class UsedInProperty(x: Int) {
|
||||
var y = x
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Remove 'val' from parameter" "true"
|
||||
class UsedInProperty(private <caret>val x: Int) {
|
||||
var y: String
|
||||
|
||||
init {
|
||||
y = x.toString()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// "Remove 'val' from parameter" "true"
|
||||
class UsedInProperty(x: Int) {
|
||||
var y: String
|
||||
|
||||
init {
|
||||
y = x.toString()
|
||||
}
|
||||
}
|
||||
@@ -124,6 +124,12 @@ public class InspectionTestGenerated extends AbstractInspectionTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("canBeParameter/inspectionData/inspections.test")
|
||||
public void testCanBeParameter_inspectionData_Inspections_test() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/canBeParameter/inspectionData/inspections.test");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("canBeVal/inspectionData/inspections.test")
|
||||
public void testCanBeVal_inspectionData_Inspections_test() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/canBeVal/inspectionData/inspections.test");
|
||||
|
||||
@@ -680,6 +680,33 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/canBeParameter")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class CanBeParameter extends AbstractQuickFixTest {
|
||||
public void testAllFilesPresentInCanBeParameter() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/canBeParameter"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("usedInDerivedClass.kt")
|
||||
public void testUsedInDerivedClass() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/canBeParameter/usedInDerivedClass.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("usedInProperty.kt")
|
||||
public void testUsedInProperty() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/canBeParameter/usedInProperty.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("usedPrivateInInitializer.kt")
|
||||
public void testUsedPrivateInInitializer() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/canBeParameter/usedPrivateInInitializer.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/quickfix/changeSignature")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user