Skip to content

Commit 092e44e

Browse files
committed
Implement abstract members, now includes abstract variables
1 parent a4c53b6 commit 092e44e

File tree

5 files changed

+123
-16
lines changed

5 files changed

+123
-16
lines changed

server/src/main/kotlin/org/javacs/kt/codeaction/CodeAction.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ package org.javacs.kt.codeaction
33
import org.eclipse.lsp4j.*
44
import org.eclipse.lsp4j.jsonrpc.messages.Either
55
import org.javacs.kt.CompiledFile
6-
import org.javacs.kt.codeaction.quickfix.ImplementAbstractFunctionsQuickFix
6+
import org.javacs.kt.codeaction.quickfix.ImplementAbstractMembersQuickFix
77
import org.javacs.kt.codeaction.quickfix.AddMissingImportsQuickFix
88
import org.javacs.kt.command.JAVA_TO_KOTLIN_COMMAND
99
import org.javacs.kt.util.toPath
1010
import org.javacs.kt.index.SymbolIndex
1111

1212
val QUICK_FIXES = listOf(
13-
ImplementAbstractFunctionsQuickFix(),
13+
ImplementAbstractMembersQuickFix(),
1414
AddMissingImportsQuickFix()
1515
)
1616

server/src/main/kotlin/org/javacs/kt/codeaction/quickfix/ImplementAbstractFunctionsQuickFix.kt renamed to server/src/main/kotlin/org/javacs/kt/codeaction/quickfix/ImplementAbstractMembersQuickFix.kt

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor
1111
import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
1212
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
1313
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
14+
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
1415
import org.jetbrains.kotlin.descriptors.isInterface
1516
import org.jetbrains.kotlin.descriptors.Modality
1617
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
@@ -33,7 +34,7 @@ import org.jetbrains.kotlin.types.typeUtil.asTypeProjection
3334

3435
private const val DEFAULT_TAB_SIZE = 4
3536

36-
class ImplementAbstractFunctionsQuickFix : QuickFix {
37+
class ImplementAbstractMembersQuickFix : QuickFix {
3738
override fun compute(file: CompiledFile, index: SymbolIndex, range: Range, diagnostics: List<Diagnostic>): List<Either<Command, CodeAction>> {
3839
val diagnostic = findDiagnosticMatch(diagnostics, range)
3940

@@ -64,7 +65,7 @@ class ImplementAbstractFunctionsQuickFix : QuickFix {
6465
val codeAction = CodeAction()
6566
codeAction.edit = WorkspaceEdit(mapOf(uri to textEdits))
6667
codeAction.kind = CodeActionKind.QuickFix
67-
codeAction.title = "Implement abstract functions"
68+
codeAction.title = "Implement abstract members"
6869
codeAction.diagnostics = listOf(diagnostic)
6970
return listOf(Either.forRight(codeAction))
7071
}
@@ -92,9 +93,15 @@ private fun getAbstractFunctionStubs(file: CompiledFile, kotlinClass: KtClass) =
9293
if (null != classDescriptor && (classDescriptor.kind.isInterface || classDescriptor.modality == Modality.ABSTRACT)) {
9394
val superClassTypeArguments = getSuperClassTypeProjections(file, it)
9495
classDescriptor.getMemberScope(superClassTypeArguments).getContributedDescriptors().filter { classMember ->
95-
classMember is FunctionDescriptor && classMember.modality == Modality.ABSTRACT && !overridesDeclaration(kotlinClass, classMember)
96-
}.map { function ->
97-
createFunctionStub(function as FunctionDescriptor)
96+
(classMember is FunctionDescriptor && classMember.modality == Modality.ABSTRACT && !overridesDeclaration(kotlinClass, classMember)) || (classMember is PropertyDescriptor && classMember.modality == Modality.ABSTRACT && !overridesDeclaration(kotlinClass, classMember))
97+
}.mapNotNull { member ->
98+
if(member is FunctionDescriptor) {
99+
createFunctionStub(member)
100+
} else if(member is PropertyDescriptor) {
101+
createVariableStub(member)
102+
} else {
103+
null
104+
}
98105
}
99106
} else {
100107
null
@@ -132,6 +139,11 @@ private fun overridesDeclaration(kotlinClass: KtClass, descriptor: FunctionDescr
132139
}
133140
}
134141

142+
private fun overridesDeclaration(kotlinClass: KtClass, descriptor: PropertyDescriptor): Boolean =
143+
kotlinClass.declarations.any {
144+
it.name == descriptor.name.asString() && it.hasModifier(KtTokens.OVERRIDE_KEYWORD)
145+
}
146+
135147
// Checks if two functions have matching parameters
136148
private fun parametersMatch(function: KtNamedFunction, functionDescriptor: FunctionDescriptor): Boolean {
137149
if (function.valueParameters.size == functionDescriptor.valueParameters.size) {
@@ -178,6 +190,11 @@ private fun createFunctionStub(function: FunctionDescriptor): String {
178190
return "override fun $name($arguments)${returnType?.let { ": $it" } ?: ""} { }"
179191
}
180192

193+
private fun createVariableStub(variable: PropertyDescriptor): String {
194+
val variableType = variable.returnType?.unwrappedType()?.toString()?.takeIf { "Unit" != it }
195+
return "override val ${variable.name}${variableType?.let { ": $it" } ?: ""} = TODO(\"SET VALUE\")"
196+
}
197+
181198
// about types: regular Kotlin types are marked T or T?, but types from Java are (T..T?) because nullability cannot be decided.
182199
// Therefore we have to unpack in case we have the Java type. Fortunately, the Java types are not marked nullable, so we default to non nullable types. Let the user decide if they want nullable types instead. With this implementation Kotlin types also keeps their nullability
183200
private fun KotlinType.unwrappedType(): KotlinType = this.unwrap().makeNullableAsSpecified(this.isMarkedNullable)

server/src/test/kotlin/org/javacs/kt/QuickFixesTest.kt

Lines changed: 83 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import org.hamcrest.Matchers.hasSize
88
import org.junit.Assert.assertThat
99
import org.junit.Test
1010

11-
class ImplementAbstractFunctionsQuickFixTest : SingleFileTestFixture("quickfixes", "SomeSubclass.kt") {
11+
class ImplementAbstractMembersQuickFixTest : SingleFileTestFixture("quickfixes", "SomeSubclass.kt") {
1212
@Test
1313
fun `gets workspace edit for all abstract methods when none are implemented`() {
1414
val diagnostic = Diagnostic(range(3, 1, 3, 19), "")
@@ -90,7 +90,7 @@ class ImplementAbstractFunctionsQuickFixTest : SingleFileTestFixture("quickfixes
9090
}
9191
}
9292

93-
class ImplementAbstractFunctionsQuickFixSameFileTest : SingleFileTestFixture("quickfixes", "samefile.kt") {
93+
class ImplementAbstractMembersQuickFixSameFileTest : SingleFileTestFixture("quickfixes", "samefile.kt") {
9494
@Test
9595
fun `should find no code actions`() {
9696
val only = listOf(CodeActionKind.QuickFix)
@@ -111,7 +111,7 @@ class ImplementAbstractFunctionsQuickFixSameFileTest : SingleFileTestFixture("qu
111111
assertThat(codeActionResult, hasSize(1))
112112
val codeAction = codeActionResult[0].right
113113
assertThat(codeAction.kind, equalTo(CodeActionKind.QuickFix))
114-
assertThat(codeAction.title, equalTo("Implement abstract functions"))
114+
assertThat(codeAction.title, equalTo("Implement abstract members"))
115115
assertThat(codeAction.diagnostics, equalTo(listOf(diagnostics[0])))
116116

117117
val textEdit = codeAction.edit.changes
@@ -134,7 +134,7 @@ class ImplementAbstractFunctionsQuickFixSameFileTest : SingleFileTestFixture("qu
134134
assertThat(codeActionResult, hasSize(1))
135135
val codeAction = codeActionResult[0].right
136136
assertThat(codeAction.kind, equalTo(CodeActionKind.QuickFix))
137-
assertThat(codeAction.title, equalTo("Implement abstract functions"))
137+
assertThat(codeAction.title, equalTo("Implement abstract members"))
138138

139139
val textEdit = codeAction.edit.changes
140140
val key = workspaceRoot.resolve(file).toUri().toString()
@@ -160,7 +160,7 @@ class ImplementAbstractFunctionsQuickFixSameFileTest : SingleFileTestFixture("qu
160160
assertThat(codeActionResult, hasSize(1))
161161
val codeAction = codeActionResult[0].right
162162
assertThat(codeAction.kind, equalTo(CodeActionKind.QuickFix))
163-
assertThat(codeAction.title, equalTo("Implement abstract functions"))
163+
assertThat(codeAction.title, equalTo("Implement abstract members"))
164164

165165
val textEdit = codeAction.edit.changes
166166
val key = workspaceRoot.resolve(file).toUri().toString()
@@ -182,7 +182,7 @@ class ImplementAbstractFunctionsQuickFixSameFileTest : SingleFileTestFixture("qu
182182
assertThat(codeActionResult, hasSize(1))
183183
val codeAction = codeActionResult[0].right
184184
assertThat(codeAction.kind, equalTo(CodeActionKind.QuickFix))
185-
assertThat(codeAction.title, equalTo("Implement abstract functions"))
185+
assertThat(codeAction.title, equalTo("Implement abstract members"))
186186

187187
val textEdit = codeAction.edit.changes
188188
val key = workspaceRoot.resolve(file).toUri().toString()
@@ -193,9 +193,57 @@ class ImplementAbstractFunctionsQuickFixSameFileTest : SingleFileTestFixture("qu
193193
assertThat(functionToImplementEdit?.range, equalTo(range(25, 48, 25, 48)))
194194
assertThat(functionToImplementEdit?.newText, equalTo(System.lineSeparator() + System.lineSeparator() + " override fun myMethod(myStr: String?): String? { }"))
195195
}
196+
197+
@Test
198+
fun `should find abstract variable and function`() {
199+
val only = listOf(CodeActionKind.QuickFix)
200+
val codeActionParams = codeActionParams(file, 35, 1, 35, 18, diagnostics, only)
201+
202+
val codeActionResult = languageServer.textDocumentService.codeAction(codeActionParams).get()
203+
204+
assertThat(codeActionResult, hasSize(1))
205+
val codeAction = codeActionResult[0].right
206+
assertThat(codeAction.kind, equalTo(CodeActionKind.QuickFix))
207+
assertThat(codeAction.title, equalTo("Implement abstract members"))
208+
209+
val textEdit = codeAction.edit.changes
210+
val key = workspaceRoot.resolve(file).toUri().toString()
211+
assertThat(textEdit.containsKey(key), equalTo(true))
212+
assertThat(textEdit[key], hasSize(2))
213+
214+
val firstMemberToImplementEdit = textEdit[key]?.get(0)
215+
assertThat(firstMemberToImplementEdit?.range, equalTo(range(35, 35, 35, 35)))
216+
assertThat(firstMemberToImplementEdit?.newText, equalTo(System.lineSeparator() + System.lineSeparator() + " override val name: String = TODO(\"SET VALUE\")"))
217+
218+
val secondMemberToImplementEdit = textEdit[key]?.get(1)
219+
assertThat(secondMemberToImplementEdit?.range, equalTo(range(35, 35, 35, 35)))
220+
assertThat(secondMemberToImplementEdit?.newText, equalTo(System.lineSeparator() + System.lineSeparator() + " override fun myFun() { }"))
221+
}
222+
223+
@Test
224+
fun `should find abstract function when variable is already implemented`() {
225+
val only = listOf(CodeActionKind.QuickFix)
226+
val codeActionParams = codeActionParams(file, 37, 1, 37, 17, diagnostics, only)
227+
228+
val codeActionResult = languageServer.textDocumentService.codeAction(codeActionParams).get()
229+
230+
assertThat(codeActionResult, hasSize(1))
231+
val codeAction = codeActionResult[0].right
232+
assertThat(codeAction.kind, equalTo(CodeActionKind.QuickFix))
233+
assertThat(codeAction.title, equalTo("Implement abstract members"))
234+
235+
val textEdit = codeAction.edit.changes
236+
val key = workspaceRoot.resolve(file).toUri().toString()
237+
assertThat(textEdit.containsKey(key), equalTo(true))
238+
assertThat(textEdit[key], hasSize(1))
239+
240+
val memberToImplementEdit = textEdit[key]?.get(0)
241+
assertThat(memberToImplementEdit?.range, equalTo(range(38, 31, 38, 31)))
242+
assertThat(memberToImplementEdit?.newText, equalTo(System.lineSeparator() + System.lineSeparator() + " override fun myFun() { }"))
243+
}
196244
}
197245

198-
class ImplementAbstractFunctionsQuickFixExternalLibraryTest : SingleFileTestFixture("quickfixes", "standardlib.kt") {
246+
class ImplementAbstractMembersQuickFixExternalLibraryTest : SingleFileTestFixture("quickfixes", "standardlib.kt") {
199247
@Test
200248
fun `should find one abstract method from Runnable to implement`() {
201249
val only = listOf(CodeActionKind.QuickFix)
@@ -206,7 +254,7 @@ class ImplementAbstractFunctionsQuickFixExternalLibraryTest : SingleFileTestFixt
206254
assertThat(codeActionResult, hasSize(1))
207255
val codeAction = codeActionResult[0].right
208256
assertThat(codeAction.kind, equalTo(CodeActionKind.QuickFix))
209-
assertThat(codeAction.title, equalTo("Implement abstract functions"))
257+
assertThat(codeAction.title, equalTo("Implement abstract members"))
210258

211259
val textEdit = codeAction.edit.changes
212260
val key = workspaceRoot.resolve(file).toUri().toString()
@@ -228,7 +276,7 @@ class ImplementAbstractFunctionsQuickFixExternalLibraryTest : SingleFileTestFixt
228276
assertThat(codeActionResult, hasSize(1))
229277
val codeAction = codeActionResult[0].right
230278
assertThat(codeAction.kind, equalTo(CodeActionKind.QuickFix))
231-
assertThat(codeAction.title, equalTo("Implement abstract functions"))
279+
assertThat(codeAction.title, equalTo("Implement abstract members"))
232280

233281
val textEdit = codeAction.edit.changes
234282
val key = workspaceRoot.resolve(file).toUri().toString()
@@ -239,4 +287,30 @@ class ImplementAbstractFunctionsQuickFixExternalLibraryTest : SingleFileTestFixt
239287
assertThat(functionToImplementEdit?.range, equalTo(range(7, 42, 7, 42)))
240288
assertThat(functionToImplementEdit?.newText, equalTo(System.lineSeparator() + System.lineSeparator() + " override fun compare(p0: String, p1: String): Int { }"))
241289
}
290+
291+
@Test
292+
fun `should find abstract members for AbstractList`() {
293+
val only = listOf(CodeActionKind.QuickFix)
294+
val codeActionParams = codeActionParams(file, 9, 1, 9, 13, diagnostics, only)
295+
296+
val codeActionResult = languageServer.textDocumentService.codeAction(codeActionParams).get()
297+
298+
assertThat(codeActionResult, hasSize(1))
299+
val codeAction = codeActionResult[0].right
300+
assertThat(codeAction.kind, equalTo(CodeActionKind.QuickFix))
301+
assertThat(codeAction.title, equalTo("Implement abstract members"))
302+
303+
val textEdit = codeAction.edit.changes
304+
val key = workspaceRoot.resolve(file).toUri().toString()
305+
assertThat(textEdit.containsKey(key), equalTo(true))
306+
assertThat(textEdit[key], hasSize(2))
307+
308+
val firstMemberToImplementEdit = textEdit[key]?.get(0)
309+
assertThat(firstMemberToImplementEdit?.range, equalTo(range(9, 40, 9, 40)))
310+
assertThat(firstMemberToImplementEdit?.newText, equalTo(System.lineSeparator() + System.lineSeparator() + " override val size: Int = TODO(\"SET VALUE\")"))
311+
312+
val secondMemberToImplementEdit = textEdit[key]?.get(1)
313+
assertThat(secondMemberToImplementEdit?.range, equalTo(range(9, 40, 9, 40)))
314+
assertThat(secondMemberToImplementEdit?.newText, equalTo(System.lineSeparator() + System.lineSeparator() + " override fun get(index: Int): String { }"))
315+
}
242316
}

server/src/test/resources/quickfixes/samefile.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,17 @@ interface NullMethodAndReturn<T> {
2323
}
2424

2525
class NullClass : NullMethodAndReturn<String> {}
26+
27+
abstract class MyAbstract {
28+
val otherValToTestAbstractOverride = 1
29+
30+
abstract val name: String
31+
32+
abstract fun myFun()
33+
}
34+
35+
class MyImplClass : MyAbstract() {}
36+
37+
class My2ndClass : MyAbstract() {
38+
override val name = "Nils"
39+
}

server/src/test/resources/quickfixes/standardlib.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ import java.util.Comparator
55
class MyThread : Runnable {}
66

77
class MyComperable : Comparator<String> {}
8+
9+
class MyList : AbstractList<String>() {}

0 commit comments

Comments
 (0)