Report cases when class member can be private

This commit is contained in:
Dmitry Neverov
2017-05-14 09:41:58 +02:00
committed by Dmitry Jemerov
parent c83b764c73
commit 753b714544
19 changed files with 468 additions and 0 deletions
@@ -0,0 +1,5 @@
<html>
<body>
This inspection reports class members which can be made private
</body>
</html>
+8
View File
@@ -2191,6 +2191,14 @@
language="kotlin"
/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.MemberVisibilityCanPrivateInspection"
displayName="Class member can have 'private' visibility"
groupName="Kotlin"
enabledByDefault="true"
level="WEAK WARNING"
language="kotlin"
/>
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
@@ -0,0 +1,93 @@
/*
* Copyright 2010-2017 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.*
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiNameIdentifierOwner
import com.intellij.psi.PsiReference
import com.intellij.psi.search.LocalSearchScope
import com.intellij.psi.search.searches.ReferencesSearch
import com.intellij.util.Processor
import org.jetbrains.kotlin.idea.quickfix.AddModifierFix
import org.jetbrains.kotlin.idea.refactoring.isConstructorDeclaredProperty
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.*
class MemberVisibilityCanPrivateInspection : AbstractKotlinInspection() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : KtVisitorVoid() {
override fun visitProperty(property: KtProperty) {
super.visitProperty(property)
if (!property.isLocal && canBePrivate(property)) {
registerProblem(holder, property)
}
}
override fun visitNamedFunction(function: KtNamedFunction) {
super.visitNamedFunction(function)
if (canBePrivate(function)) {
registerProblem(holder, function)
}
}
override fun visitParameter(parameter: KtParameter) {
super.visitParameter(parameter)
if (parameter.isConstructorDeclaredProperty() && canBePrivate(parameter)) {
registerProblem(holder, parameter)
}
}
}
}
private fun canBePrivate(declaration: KtDeclaration): Boolean {
if (declaration.hasModifier(KtTokens.PRIVATE_KEYWORD) || declaration.hasModifier(KtTokens.OVERRIDE_KEYWORD)) return false
val classOrObject = declaration.containingClassOrObject ?: return false
val inheritable = classOrObject is KtClass && classOrObject.isInheritable()
if (!inheritable && declaration.hasModifier(KtTokens.PROTECTED_KEYWORD)) return false //reported by ProtectedInFinalInspection
if (declaration.isOverridable()) return false
var otherUsageFound = false
var inClassUsageFound = false
ReferencesSearch.search(declaration, declaration.useScope).forEach(Processor<PsiReference> {
val usage = it.element
if (classOrObject != usage.getParentOfType<KtClassOrObject>(false)) {
otherUsageFound = true
false
} else {
inClassUsageFound = true
true
}
})
return inClassUsageFound && !otherUsageFound
}
private fun registerProblem(holder: ProblemsHolder, declaration: KtDeclaration) {
val modifierListOwner = declaration.getParentOfType<KtModifierListOwner>(false) ?: return
val member = when (declaration) {
is KtNamedFunction -> "Function"
else -> "Property"
}
val nameElement = (declaration as? PsiNameIdentifierOwner)?.nameIdentifier ?: return
holder.registerProblem(nameElement,
"$member '${declaration.name}' can be private",
ProblemHighlightType.WEAK_WARNING,
IntentionWrapper(AddModifierFix(modifierListOwner, KtTokens.PRIVATE_KEYWORD), declaration.containingFile))
}
}
@@ -0,0 +1,110 @@
<problems>
<problem>
<file>test.kt</file>
<line>13</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="test.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Can have 'private' visibility</problem_class>
<description>Property 'a' can be private</description>
</problem>
<problem>
<file>test.kt</file>
<line>19</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="test.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Can have 'private' visibility</problem_class>
<description>Function 'f1' can be private</description>
</problem>
<problem>
<file>test.kt</file>
<line>20</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="test.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Can have 'private' visibility</problem_class>
<description>Function 'f2' can be private</description>
</problem>
<problem>
<file>test.kt</file>
<line>65</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="test.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Can have 'private' visibility</problem_class>
<description>Property 'y' can be private</description>
</problem>
<problem>
<file>test.kt</file>
<line>68</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="test.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Can have 'private' visibility</problem_class>
<description>Function 'f2' can be private</description>
</problem>
<problem>
<file>test.kt</file>
<line>78</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="test.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Can have 'private' visibility</problem_class>
<description>Property 'a' can be private</description>
</problem>
<problem>
<file>test.kt</file>
<line>79</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="test.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Can have 'private' visibility</problem_class>
<description>Property 'b' can be private</description>
</problem>
<problem>
<file>test.kt</file>
<line>80</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="test.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Can have 'private' visibility</problem_class>
<description>Property 'c' can be private</description>
</problem>
<problem>
<file>test.kt</file>
<line>109</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="test.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Can have 'private' visibility</problem_class>
<description>Property 'a' can be private</description>
</problem>
<problem>
<file>test.kt</file>
<line>110</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="test.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Can have 'private' visibility</problem_class>
<description>Property 'b' can be private</description>
</problem>
<problem>
<file>test.kt</file>
<line>114</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="test.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Can have 'private' visibility</problem_class>
<description>Function 'f1' can be private</description>
</problem>
<problem>
<file>test.kt</file>
<line>115</line>
<module>light_idea_test_case</module>
<entry_point TYPE="file" FQNAME="test.kt" />
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Can have 'private' visibility</problem_class>
<description>Function 'f2' can be private</description>
</problem>
</problems>
@@ -0,0 +1 @@
// INSPECTION_CLASS: org.jetbrains.kotlin.idea.inspections.MemberVisibilityCanPrivateInspection
@@ -0,0 +1,129 @@
val a = "a"
fun f1() {}
fun f2() {
println(a)
f1()
A().b
}
class A {
val unused = ""
val a = ""
internal val b = ""
protected val c = ""
private val d = ""
fun unused() {}
fun f1() {}
internal fun f2() {}
protected fun f3() {}
private fun f4() {}
fun bar() {
println(a)
println(b)
println(c)
println(d)
f1()
f2()
f3()
f4()
}
}
interface I {
val x: String
fun foo()
}
class B : I {
override val x: String
get() = ""
override fun foo() {
}
fun bar() {
println(x)
foo()
}
}
interface I2 {
val x: String
fun foo()
fun bar() {
println(x)
foo()
}
}
open class C {
open val x = ""
protected val y = ""
open fun f1() {}
protected fun f2() {}
fun bar() {
println(x)
println(y)
f1()
f2()
}
}
class D(val a: String = "",
var b: String = "",
internal val c: String = "",
protected val d: String = "",
private val e: String = "") {
fun foo() {
println(a)
println(b)
println(c)
println(d)
println(e)
}
}
open class E(override val x: String = "",
open val a: String = "") : I {
override fun foo() {}
fun foo() {
println(x)
println(a)
}
fun bar() {
var v1 = ""
val v2 = ""
println(v1)
println(v2)
}
}
val x = object {
val a: String = "",
internal val b: String = "",
protected val c: String = "",
private val d: String = ""
fun f1() {}
internal fun f2() {}
protected fun f3() {}
private fun f4() {}
fun foo() {
println(a)
println(b)
println(c)
println(d)
f1()
f2()
f3()
f4()
}
}
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.inspections.MemberVisibilityCanPrivateInspection
@@ -0,0 +1,6 @@
// "Add 'private' modifier" "true"
class A(internal val <caret>a: String = "") {
fun foo() {
a
}
}
@@ -0,0 +1,6 @@
// "Add 'private' modifier" "true"
class A(private val a: String = "") {
fun foo() {
a
}
}
@@ -0,0 +1,8 @@
// "Add 'private' modifier" "true"
class A {
internal val <caret>a = ""
fun foo() {
a
}
}
@@ -0,0 +1,8 @@
// "Add 'private' modifier" "true"
class A {
private val a = ""
fun foo() {
a
}
}
@@ -0,0 +1,8 @@
// "Add 'private' modifier" "true"
class A {
val <caret>a = ""
fun foo() {
a
}
}
@@ -0,0 +1,8 @@
// "Add 'private' modifier" "true"
class A {
private val a = ""
fun foo() {
a
}
}
@@ -0,0 +1,8 @@
// "Add 'private' modifier" "true"
open class A {
protected val <caret>a = ""
fun foo() {
a
}
}
@@ -0,0 +1,8 @@
// "Add 'private' modifier" "true"
open class A {
private val a = ""
fun foo() {
a
}
}
@@ -0,0 +1,8 @@
// "Add 'private' modifier" "true"
class A {
public val <caret>a = ""
fun foo() {
a
}
}
@@ -0,0 +1,8 @@
// "Add 'private' modifier" "true"
class A {
private val a = ""
fun foo() {
a
}
}
@@ -221,6 +221,12 @@ public class InspectionTestGenerated extends AbstractInspectionTest {
doTest(fileName);
}
@TestMetadata("memberVisibilityCanBePrivate/inspectionData/inspections.test")
public void testMemberVisibilityCanBePrivate_inspectionData_Inspections_test() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/memberVisibilityCanBePrivate/inspectionData/inspections.test");
doTest(fileName);
}
@TestMetadata("overridingDeprecatedMember/inspectionData/inspections.test")
public void testOverridingDeprecatedMember_inspectionData_Inspections_test() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspections/overridingDeprecatedMember/inspectionData/inspections.test");
@@ -6327,6 +6327,45 @@ public class QuickFixTestGenerated extends AbstractQuickFixTest {
}
}
@TestMetadata("idea/testData/quickfix/memberVisibilityCanBePrivate")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class MemberVisibilityCanBePrivate extends AbstractQuickFixTest {
public void testAllFilesPresentInMemberVisibilityCanBePrivate() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/memberVisibilityCanBePrivate"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("constructorParam.kt")
public void testConstructorParam() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/memberVisibilityCanBePrivate/constructorParam.kt");
doTest(fileName);
}
@TestMetadata("internal.kt")
public void testInternal() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/memberVisibilityCanBePrivate/internal.kt");
doTest(fileName);
}
@TestMetadata("noModifier.kt")
public void testNoModifier() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/memberVisibilityCanBePrivate/noModifier.kt");
doTest(fileName);
}
@TestMetadata("protected.kt")
public void testProtected() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/memberVisibilityCanBePrivate/protected.kt");
doTest(fileName);
}
@TestMetadata("public.kt")
public void testPublic() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/memberVisibilityCanBePrivate/public.kt");
doTest(fileName);
}
}
@TestMetadata("idea/testData/quickfix/migration")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)