Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,23 +44,26 @@ class ImplementAbstractMembersQuickFix : QuickFix {

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

val uri = file.parse.toPath().toUri().toString()
// Get the padding to be introduced before the function declarations
// Get the padding to be introduced before the member declarations
val padding = getDeclarationPadding(file, kotlinClass)

// Get the location where the new code will be placed
val newFunctionStartPosition = getNewFunctionStartPosition(file, kotlinClass)
val newMembersStartPosition = getNewMembersStartPosition(file, kotlinClass)
val bodyAppendBeginning = listOf(TextEdit(Range(newMembersStartPosition, newMembersStartPosition), "{")).takeIf { kotlinClass.hasNoBody() } ?: emptyList()
val bodyAppendEnd = listOf(TextEdit(Range(newMembersStartPosition, newMembersStartPosition), System.lineSeparator() + "}")).takeIf { kotlinClass.hasNoBody() } ?: emptyList()

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

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

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

private fun getNewFunctionStartPosition(file: CompiledFile, kotlinClass: KtClass): Position? =
// If the class is not empty, the new function will be put right after the last declaration
private fun getNewMembersStartPosition(file: CompiledFile, kotlinClass: KtClass): Position? =
// If the class is not empty, the new member will be put right after the last declaration
if (kotlinClass.declarations.isNotEmpty()) {
val lastFunctionEndOffset = kotlinClass.declarations.last().endOffset
position(file.content, lastFunctionEndOffset)
} else { // Otherwise, the function is put at the beginning of the class
} else { // Otherwise, the member is put at the beginning of the class
val body = kotlinClass.body
if (body != null) {
position(file.content, body.startOffset + 1)
} else {
null
// function has no body. We have to create one. New position is right after entire kotlin class text (with space)
val newPosCorrectLine = position(file.content, kotlinClass.startOffset + 1)
newPosCorrectLine.character = (kotlinClass.text.length + 2)
newPosCorrectLine
}
}

private fun KtClass.hasNoBody() = null == this.body
30 changes: 30 additions & 0 deletions server/src/test/kotlin/org/javacs/kt/QuickFixesTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,36 @@ class ImplementAbstractMembersQuickFixSameFileTest : SingleFileTestFixture("quic
assertThat(memberToImplementEdit?.range, equalTo(range(38, 31, 38, 31)))
assertThat(memberToImplementEdit?.newText, equalTo(System.lineSeparator() + System.lineSeparator() + " override fun myFun() { }"))
}

@Test
fun `should find abstract members when class has no body (square brackets)`() {
val only = listOf(CodeActionKind.QuickFix)
val codeActionParams = codeActionParams(file, 47, 1, 47, 12, diagnostics, only)

val codeActionResult = languageServer.textDocumentService.codeAction(codeActionParams).get()

assertThat(codeActionResult, hasSize(1))
val codeAction = codeActionResult[0].right
assertThat(codeAction.kind, equalTo(CodeActionKind.QuickFix))
assertThat(codeAction.title, equalTo("Implement abstract members"))

val textEdit = codeAction.edit.changes
val key = workspaceRoot.resolve(file).toUri().toString()
assertThat(textEdit.containsKey(key), equalTo(true))
assertThat(textEdit[key], hasSize(3))

val firstMemberToImplementEdit = textEdit[key]?.get(0)
assertThat(firstMemberToImplementEdit?.range, equalTo(range(47, 23, 47, 23)))
assertThat(firstMemberToImplementEdit?.newText, equalTo("{"))

val secondMemberToImplementEdit = textEdit[key]?.get(1)
assertThat(secondMemberToImplementEdit?.range, equalTo(range(47, 23, 47, 23)))
assertThat(secondMemberToImplementEdit?.newText, equalTo(System.lineSeparator() + System.lineSeparator() + " override fun behaviour() { }"))

val thirdMemberToImplementEdit = textEdit[key]?.get(2)
assertThat(thirdMemberToImplementEdit?.range, equalTo(range(47, 23, 47, 23)))
assertThat(thirdMemberToImplementEdit?.newText, equalTo(System.lineSeparator() + "}"))
}
}

class ImplementAbstractMembersQuickFixExternalLibraryTest : SingleFileTestFixture("quickfixes", "standardlib.kt") {
Expand Down
8 changes: 8 additions & 0 deletions server/src/test/resources/quickfixes/samefile.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,11 @@ class MyImplClass : MyAbstract() {}
class My2ndClass : MyAbstract() {
override val name = "Nils"
}


// defect GH-366, part of the solution
interface IThing {
fun behaviour
}

class Thing : IThing