Skip to content

Commit f6b0638

Browse files
committed
Merge pull request #368 from themkat/abstract-members-empty-body
2 parents 5df1a00 + 8ee1628 commit f6b0638

File tree

3 files changed

+59
-13
lines changed

3 files changed

+59
-13
lines changed

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

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -44,23 +44,26 @@ class ImplementAbstractMembersQuickFix : QuickFix {
4444

4545
// If the client side and the server side diagnostics contain a valid diagnostic for this range.
4646
if (diagnostic != null && anyDiagnosticMatch(kotlinDiagnostics, startCursor, endCursor)) {
47-
// Get the class with the missing functions
47+
// Get the class with the missing members
4848
val kotlinClass = file.parseAtPoint(startCursor)
4949
if (kotlinClass is KtClass) {
5050
// Get the functions that need to be implemented
51-
val functionsToImplement = getAbstractFunctionStubs(file, kotlinClass)
51+
val membersToImplement = getAbstractMembersStubs(file, kotlinClass)
5252

5353
val uri = file.parse.toPath().toUri().toString()
54-
// Get the padding to be introduced before the function declarations
54+
// Get the padding to be introduced before the member declarations
5555
val padding = getDeclarationPadding(file, kotlinClass)
56+
5657
// Get the location where the new code will be placed
57-
val newFunctionStartPosition = getNewFunctionStartPosition(file, kotlinClass)
58+
val newMembersStartPosition = getNewMembersStartPosition(file, kotlinClass)
59+
val bodyAppendBeginning = listOf(TextEdit(Range(newMembersStartPosition, newMembersStartPosition), "{")).takeIf { kotlinClass.hasNoBody() } ?: emptyList()
60+
val bodyAppendEnd = listOf(TextEdit(Range(newMembersStartPosition, newMembersStartPosition), System.lineSeparator() + "}")).takeIf { kotlinClass.hasNoBody() } ?: emptyList()
5861

59-
val textEdits = functionsToImplement.map {
60-
// We leave two new lines before the function is inserted
62+
val textEdits = bodyAppendBeginning + membersToImplement.map {
63+
// We leave two new lines before the member is inserted
6164
val newText = System.lineSeparator() + System.lineSeparator() + padding + it
62-
TextEdit(Range(newFunctionStartPosition, newFunctionStartPosition), newText)
63-
}
65+
TextEdit(Range(newMembersStartPosition, newMembersStartPosition), newText)
66+
} + bodyAppendEnd
6467

6568
val codeAction = CodeAction()
6669
codeAction.edit = WorkspaceEdit(mapOf(uri to textEdits))
@@ -80,7 +83,7 @@ fun findDiagnosticMatch(diagnostics: List<Diagnostic>, range: Range) =
8083
private fun anyDiagnosticMatch(diagnostics: Diagnostics, startCursor: Int, endCursor: Int) =
8184
diagnostics.any { diagnosticMatch(it, startCursor, endCursor, hashSetOf("ABSTRACT_MEMBER_NOT_IMPLEMENTED", "ABSTRACT_CLASS_MEMBER_NOT_IMPLEMENTED")) }
8285

83-
private fun getAbstractFunctionStubs(file: CompiledFile, kotlinClass: KtClass) =
86+
private fun getAbstractMembersStubs(file: CompiledFile, kotlinClass: KtClass) =
8487
// For each of the super types used by this class
8588
kotlinClass.superTypeListEntries.mapNotNull {
8689
// Find the definition of this super type
@@ -211,16 +214,21 @@ private fun getDeclarationPadding(file: CompiledFile, kotlinClass: KtClass): Str
211214
return " ".repeat(paddingSize)
212215
}
213216

214-
private fun getNewFunctionStartPosition(file: CompiledFile, kotlinClass: KtClass): Position? =
215-
// If the class is not empty, the new function will be put right after the last declaration
217+
private fun getNewMembersStartPosition(file: CompiledFile, kotlinClass: KtClass): Position? =
218+
// If the class is not empty, the new member will be put right after the last declaration
216219
if (kotlinClass.declarations.isNotEmpty()) {
217220
val lastFunctionEndOffset = kotlinClass.declarations.last().endOffset
218221
position(file.content, lastFunctionEndOffset)
219-
} else { // Otherwise, the function is put at the beginning of the class
222+
} else { // Otherwise, the member is put at the beginning of the class
220223
val body = kotlinClass.body
221224
if (body != null) {
222225
position(file.content, body.startOffset + 1)
223226
} else {
224-
null
227+
// function has no body. We have to create one. New position is right after entire kotlin class text (with space)
228+
val newPosCorrectLine = position(file.content, kotlinClass.startOffset + 1)
229+
newPosCorrectLine.character = (kotlinClass.text.length + 2)
230+
newPosCorrectLine
225231
}
226232
}
233+
234+
private fun KtClass.hasNoBody() = null == this.body

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,36 @@ class ImplementAbstractMembersQuickFixSameFileTest : SingleFileTestFixture("quic
241241
assertThat(memberToImplementEdit?.range, equalTo(range(38, 31, 38, 31)))
242242
assertThat(memberToImplementEdit?.newText, equalTo(System.lineSeparator() + System.lineSeparator() + " override fun myFun() { }"))
243243
}
244+
245+
@Test
246+
fun `should find abstract members when class has no body (square brackets)`() {
247+
val only = listOf(CodeActionKind.QuickFix)
248+
val codeActionParams = codeActionParams(file, 47, 1, 47, 12, diagnostics, only)
249+
250+
val codeActionResult = languageServer.textDocumentService.codeAction(codeActionParams).get()
251+
252+
assertThat(codeActionResult, hasSize(1))
253+
val codeAction = codeActionResult[0].right
254+
assertThat(codeAction.kind, equalTo(CodeActionKind.QuickFix))
255+
assertThat(codeAction.title, equalTo("Implement abstract members"))
256+
257+
val textEdit = codeAction.edit.changes
258+
val key = workspaceRoot.resolve(file).toUri().toString()
259+
assertThat(textEdit.containsKey(key), equalTo(true))
260+
assertThat(textEdit[key], hasSize(3))
261+
262+
val firstMemberToImplementEdit = textEdit[key]?.get(0)
263+
assertThat(firstMemberToImplementEdit?.range, equalTo(range(47, 23, 47, 23)))
264+
assertThat(firstMemberToImplementEdit?.newText, equalTo("{"))
265+
266+
val secondMemberToImplementEdit = textEdit[key]?.get(1)
267+
assertThat(secondMemberToImplementEdit?.range, equalTo(range(47, 23, 47, 23)))
268+
assertThat(secondMemberToImplementEdit?.newText, equalTo(System.lineSeparator() + System.lineSeparator() + " override fun behaviour() { }"))
269+
270+
val thirdMemberToImplementEdit = textEdit[key]?.get(2)
271+
assertThat(thirdMemberToImplementEdit?.range, equalTo(range(47, 23, 47, 23)))
272+
assertThat(thirdMemberToImplementEdit?.newText, equalTo(System.lineSeparator() + "}"))
273+
}
244274
}
245275

246276
class ImplementAbstractMembersQuickFixExternalLibraryTest : SingleFileTestFixture("quickfixes", "standardlib.kt") {

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,11 @@ class MyImplClass : MyAbstract() {}
3737
class My2ndClass : MyAbstract() {
3838
override val name = "Nils"
3939
}
40+
41+
42+
// defect GH-366, part of the solution
43+
interface IThing {
44+
fun behaviour
45+
}
46+
47+
class Thing : IThing

0 commit comments

Comments
 (0)