Skip to content

Commit de3fbe3

Browse files
committed
KT-12152: quick fix "make final" for member / containing class
1 parent a22e7d3 commit de3fbe3

17 files changed

+258
-10
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2010-2016 JetBrains s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jetbrains.kotlin.idea.inspections
18+
19+
import com.intellij.codeInspection.LocalQuickFix
20+
import com.intellij.codeInspection.ProblemDescriptor
21+
import com.intellij.openapi.project.Project
22+
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
23+
import org.jetbrains.kotlin.psi.KtModifierListOwner
24+
import org.jetbrains.kotlin.psi.addRemoveModifier.addModifier
25+
26+
open class AddModifierFix(val modifierListOwner: KtModifierListOwner,
27+
val modifier: KtModifierKeywordToken,
28+
val text: String) : LocalQuickFix {
29+
30+
override fun getName() = text
31+
32+
override fun getFamilyName() = text
33+
34+
final override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
35+
addModifier(modifierListOwner, modifier)
36+
}
37+
}

idea/src/org/jetbrains/kotlin/idea/inspections/LeakingThisInspection.kt

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,24 @@
1717
package org.jetbrains.kotlin.idea.inspections
1818

1919
import com.intellij.codeInspection.LocalInspectionToolSession
20+
import com.intellij.codeInspection.LocalQuickFix
21+
import com.intellij.codeInspection.ProblemDescriptor
2022
import com.intellij.codeInspection.ProblemHighlightType.*
2123
import com.intellij.codeInspection.ProblemsHolder
24+
import com.intellij.openapi.project.Project
2225
import com.intellij.psi.PsiElementVisitor
26+
import com.intellij.psi.search.searches.DefinitionsScopedSearch
2327
import org.jetbrains.kotlin.cfg.LeakingThisDescriptor.*
28+
import org.jetbrains.kotlin.descriptors.MemberDescriptor
29+
import org.jetbrains.kotlin.descriptors.Modality
2430
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFully
31+
import org.jetbrains.kotlin.lexer.KtModifierKeywordToken
32+
import org.jetbrains.kotlin.lexer.KtTokens
33+
import org.jetbrains.kotlin.name.Name
2534
import org.jetbrains.kotlin.psi.*
2635
import org.jetbrains.kotlin.resolve.BindingContext.LEAKING_THIS
36+
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
37+
import org.jetbrains.kotlin.psi.addRemoveModifier.addModifier
2738

2839
class LeakingThisInspection : AbstractKotlinInspection() {
2940
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
@@ -32,26 +43,58 @@ class LeakingThisInspection : AbstractKotlinInspection() {
3243
val context = expression.analyzeFully()
3344
val leakingThisDescriptor = context.get(LEAKING_THIS, expression) ?: return
3445
val description = when (leakingThisDescriptor) {
35-
is PropertyIsNull -> null // Not supported yet
46+
is PropertyIsNull -> return // Not supported yet
3647
is NonFinalClass ->
3748
if (expression is KtThisExpression)
3849
"Leaking 'this' in constructor of non-final class ${leakingThisDescriptor.klass.name}"
3950
else
40-
null // Not supported yet
51+
return // Not supported yet
4152
is NonFinalProperty ->
4253
"Accessing non-final property ${leakingThisDescriptor.property.name} in constructor"
4354
is NonFinalFunction ->
4455
"Calling non-final function ${leakingThisDescriptor.function.name} in constructor"
4556
}
46-
if (description != null) {
47-
holder.registerProblem(
48-
expression, description,
49-
when (leakingThisDescriptor) {
50-
is NonFinalProperty, is NonFinalFunction -> GENERIC_ERROR_OR_WARNING
51-
else -> WEAK_WARNING
52-
}
53-
)
57+
val memberDescriptorToFix = when (leakingThisDescriptor) {
58+
is NonFinalProperty -> leakingThisDescriptor.property
59+
is NonFinalFunction -> leakingThisDescriptor.function
60+
else -> null
5461
}
62+
val memberFix = memberDescriptorToFix?.let {
63+
if (it.modality == Modality.OPEN) {
64+
val modifierListOwner = DescriptorToSourceUtils.descriptorToDeclaration(it) as? KtDeclaration
65+
MakeFinalFix.create(modifierListOwner, it.name)
66+
}
67+
else null
68+
}
69+
70+
val klass = leakingThisDescriptor.classOrObject as? KtClass
71+
val classFix =
72+
if (klass != null && klass.hasModifier(KtTokens.OPEN_KEYWORD)) {
73+
MakeFinalFix.create(klass, klass.nameAsSafeName)
74+
}
75+
else null
76+
77+
holder.registerProblem(
78+
expression, description,
79+
when (leakingThisDescriptor) {
80+
is NonFinalProperty, is NonFinalFunction -> GENERIC_ERROR_OR_WARNING
81+
else -> WEAK_WARNING
82+
},
83+
*(arrayOf(memberFix, classFix).filterNotNull().toTypedArray())
84+
)
85+
}
86+
}
87+
}
88+
89+
class MakeFinalFix private constructor(modifierListOwner: KtModifierListOwner, name: Name) :
90+
AddModifierFix(modifierListOwner, KtTokens.FINAL_KEYWORD, "Make '$name' final") {
91+
92+
companion object {
93+
fun create(declaration: KtDeclaration?, name: Name): MakeFinalFix? {
94+
declaration ?: return null
95+
val useScope = declaration.useScope
96+
if (DefinitionsScopedSearch.search(declaration, useScope).findFirst() != null) return null
97+
return MakeFinalFix(declaration, name)
5598
}
5699
}
57100
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org.jetbrains.kotlin.idea.inspections.LeakingThisInspection
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// "Make 'x' final" "true"
2+
3+
open class My(open val x: Int) {
4+
val y = <caret>x
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// "Make 'x' final" "true"
2+
3+
open class My(val x: Int) {
4+
val y = x
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// "Make 'My' final" "true"
2+
3+
open class My(open val x: Int) {
4+
val y = <caret>x
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// "Make 'My' final" "true"
2+
3+
class My(open val x: Int) {
4+
val y = x
5+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// "Make 'x' final" "false"
2+
// ACTION: Convert property initializer to getter
3+
4+
open class My(open val x: Int) {
5+
val y = <caret>x
6+
}
7+
8+
class Your(x : Int) : My(x) {
9+
override val x: Int get() = 42
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// "Make 'init' final" "true"
2+
3+
open class My {
4+
5+
init {
6+
<caret>init()
7+
}
8+
9+
open fun init() {}
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// "Make 'init' final" "true"
2+
3+
open class My {
4+
5+
init {
6+
init()
7+
}
8+
9+
fun init() {}
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// "Make 'My' final" "true"
2+
3+
open class My {
4+
5+
init {
6+
<caret>init()
7+
}
8+
9+
open fun init() {}
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// "Make 'My' final" "true"
2+
3+
class My {
4+
5+
init {
6+
init()
7+
}
8+
9+
open fun init() {}
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// "Make 'My' final" "false"
2+
// ACTION: Add 'my =' to argument
3+
4+
abstract class My {
5+
init {
6+
register(<caret>this)
7+
}
8+
}
9+
10+
fun register(my: My) {}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// "Make 'My' final" "true"
2+
3+
open class My {
4+
init {
5+
register(<caret>this)
6+
}
7+
}
8+
9+
fun register(my: My) {}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// "Make 'My' final" "true"
2+
3+
class My {
4+
init {
5+
register(this)
6+
}
7+
}
8+
9+
fun register(my: My) {}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// "Make 'My' final" "false"
2+
// ACTION: Add 'my =' to argument
3+
4+
open class My {
5+
init {
6+
register(<caret>this)
7+
}
8+
}
9+
10+
class Derived : My()
11+
12+
fun register(my: My) {}

idea/tests/org/jetbrains/kotlin/idea/quickfix/QuickFixTestGenerated.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5101,6 +5101,63 @@ public void testSimple() throws Exception {
51015101
}
51025102
}
51035103

5104+
@TestMetadata("idea/testData/quickfix/leakingThis")
5105+
@TestDataPath("$PROJECT_ROOT")
5106+
@RunWith(JUnit3RunnerWithInners.class)
5107+
public static class LeakingThis extends AbstractQuickFixTest {
5108+
@TestMetadata("accessOpenProperty.kt")
5109+
public void testAccessOpenProperty() throws Exception {
5110+
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/leakingThis/accessOpenProperty.kt");
5111+
doTest(fileName);
5112+
}
5113+
5114+
@TestMetadata("accessOpenPropertyClass.kt")
5115+
public void testAccessOpenPropertyClass() throws Exception {
5116+
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/leakingThis/accessOpenPropertyClass.kt");
5117+
doTest(fileName);
5118+
}
5119+
5120+
@TestMetadata("accessOverriddenProperty.kt")
5121+
public void testAccessOverriddenProperty() throws Exception {
5122+
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/leakingThis/accessOverriddenProperty.kt");
5123+
doTest(fileName);
5124+
}
5125+
5126+
public void testAllFilesPresentInLeakingThis() throws Exception {
5127+
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/quickfix/leakingThis"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), true);
5128+
}
5129+
5130+
@TestMetadata("callOpenMethod.kt")
5131+
public void testCallOpenMethod() throws Exception {
5132+
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/leakingThis/callOpenMethod.kt");
5133+
doTest(fileName);
5134+
}
5135+
5136+
@TestMetadata("callOpenMethodClass.kt")
5137+
public void testCallOpenMethodClass() throws Exception {
5138+
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/leakingThis/callOpenMethodClass.kt");
5139+
doTest(fileName);
5140+
}
5141+
5142+
@TestMetadata("inAbstract.kt")
5143+
public void testInAbstract() throws Exception {
5144+
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/leakingThis/inAbstract.kt");
5145+
doTest(fileName);
5146+
}
5147+
5148+
@TestMetadata("inNonFinal.kt")
5149+
public void testInNonFinal() throws Exception {
5150+
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/leakingThis/inNonFinal.kt");
5151+
doTest(fileName);
5152+
}
5153+
5154+
@TestMetadata("inOverridden.kt")
5155+
public void testInOverridden() throws Exception {
5156+
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/quickfix/leakingThis/inOverridden.kt");
5157+
doTest(fileName);
5158+
}
5159+
}
5160+
51045161
@TestMetadata("idea/testData/quickfix/libraries")
51055162
@TestDataPath("$PROJECT_ROOT")
51065163
@RunWith(JUnit3RunnerWithInners.class)

0 commit comments

Comments
 (0)