Can be parameter inspection #KT-10819 Fixed

This commit is contained in:
Mikhail Glukhikh
2016-04-06 14:37:53 +03:00
parent 8ace253559
commit 1d83a1a97c
18 changed files with 388 additions and 5 deletions
@@ -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>
+7
View File
@@ -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
View File
@@ -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());
}
}
+1
View File
@@ -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)