Skip to content

Commit eef14bd

Browse files
authored
Merge pull request #379 from themkat/GH-359
General override member(s) functionality
2 parents 6484b9b + 596c6a3 commit eef14bd

File tree

6 files changed

+478
-126
lines changed

6 files changed

+478
-126
lines changed

server/src/main/kotlin/org/javacs/kt/KotlinProtocolExtensionService.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import org.eclipse.lsp4j.*
44
import org.javacs.kt.util.AsyncExecutor
55
import org.javacs.kt.util.parseURI
66
import org.javacs.kt.resolve.resolveMain
7+
import org.javacs.kt.position.offset
8+
import org.javacs.kt.overridemembers.listOverridableMembers
79
import java.util.concurrent.CompletableFuture
810
import java.nio.file.Paths
911

@@ -39,4 +41,12 @@ class KotlinProtocolExtensionService(
3941
"projectRoot" to workspacePath
4042
)
4143
}
44+
45+
override fun overrideMember(position: TextDocumentPositionParams): CompletableFuture<List<CodeAction>> = async.compute {
46+
val fileUri = parseURI(position.textDocument.uri)
47+
val compiledFile = sp.currentVersion(fileUri)
48+
val cursorOffset = offset(compiledFile.content, position.position)
49+
50+
listOverridableMembers(compiledFile, cursorOffset)
51+
}
4252
}

server/src/main/kotlin/org/javacs/kt/KotlinProtocolExtensions.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,7 @@ interface KotlinProtocolExtensions {
1515

1616
@JsonRequest
1717
fun mainClass(textDocument: TextDocumentIdentifier): CompletableFuture<Map<String, Any?>>
18+
19+
@JsonRequest
20+
fun overrideMember(position: TextDocumentPositionParams): CompletableFuture<List<CodeAction>>
1821
}

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

Lines changed: 8 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ import org.javacs.kt.index.SymbolIndex
77
import org.javacs.kt.position.offset
88
import org.javacs.kt.position.position
99
import org.javacs.kt.util.toPath
10+
import org.javacs.kt.overridemembers.createFunctionStub
11+
import org.javacs.kt.overridemembers.createVariableStub
12+
import org.javacs.kt.overridemembers.getClassDescriptor
13+
import org.javacs.kt.overridemembers.getDeclarationPadding
14+
import org.javacs.kt.overridemembers.getNewMembersStartPosition
15+
import org.javacs.kt.overridemembers.getSuperClassTypeProjections
16+
import org.javacs.kt.overridemembers.hasNoBody
17+
import org.javacs.kt.overridemembers.overridesDeclaration
1018
import org.jetbrains.kotlin.descriptors.ClassDescriptor
1119
import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
1220
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
@@ -32,8 +40,6 @@ import org.jetbrains.kotlin.types.KotlinType
3240
import org.jetbrains.kotlin.types.TypeProjection
3341
import org.jetbrains.kotlin.types.typeUtil.asTypeProjection
3442

35-
private const val DEFAULT_TAB_SIZE = 4
36-
3743
class ImplementAbstractMembersQuickFix : QuickFix {
3844
override fun compute(file: CompiledFile, index: SymbolIndex, range: Range, diagnostics: List<Diagnostic>): List<Either<Command, CodeAction>> {
3945
val diagnostic = findDiagnosticMatch(diagnostics, range)
@@ -108,127 +114,3 @@ private fun getAbstractMembersStubs(file: CompiledFile, kotlinClass: KtClass) =
108114
null
109115
}
110116
}.flatten()
111-
112-
// interfaces are ClassDescriptors by default. When calling AbstractClass super methods, we get a ClassConstructorDescriptor
113-
private fun getClassDescriptor(descriptor: DeclarationDescriptor?): ClassDescriptor? = if (descriptor is ClassDescriptor) {
114-
descriptor
115-
} else if (descriptor is ClassConstructorDescriptor) {
116-
descriptor.containingDeclaration
117-
} else {
118-
null
119-
}
120-
121-
private fun getSuperClassTypeProjections(file: CompiledFile, superType: KtSuperTypeListEntry): List<TypeProjection> = superType.typeReference?.typeElement?.children?.filter {
122-
it is KtTypeArgumentList
123-
}?.flatMap {
124-
(it as KtTypeArgumentList).arguments
125-
}?.mapNotNull {
126-
(file.referenceExpressionAtPoint(it?.startOffset ?: 0)?.second as? ClassDescriptor)?.defaultType?.asTypeProjection()
127-
} ?: emptyList()
128-
129-
// Checks if the class overrides the given declaration
130-
private fun overridesDeclaration(kotlinClass: KtClass, descriptor: FunctionDescriptor): Boolean =
131-
kotlinClass.declarations.any {
132-
if (it.name == descriptor.name.asString() && it.hasModifier(KtTokens.OVERRIDE_KEYWORD)) {
133-
if (it is KtNamedFunction) {
134-
parametersMatch(it, descriptor)
135-
} else {
136-
true
137-
}
138-
} else {
139-
false
140-
}
141-
}
142-
143-
private fun overridesDeclaration(kotlinClass: KtClass, descriptor: PropertyDescriptor): Boolean =
144-
kotlinClass.declarations.any {
145-
it.name == descriptor.name.asString() && it.hasModifier(KtTokens.OVERRIDE_KEYWORD)
146-
}
147-
148-
// Checks if two functions have matching parameters
149-
private fun parametersMatch(function: KtNamedFunction, functionDescriptor: FunctionDescriptor): Boolean {
150-
if (function.valueParameters.size == functionDescriptor.valueParameters.size) {
151-
for (index in 0 until function.valueParameters.size) {
152-
if (function.valueParameters[index].name != functionDescriptor.valueParameters[index].name.asString()) {
153-
return false
154-
} else if (function.valueParameters[index].typeReference?.typeName() != functionDescriptor.valueParameters[index].type.unwrappedType().toString()) {
155-
// Note: Since we treat Java overrides as non nullable by default, the above test will fail when the user has made the type nullable.
156-
// TODO: look into this
157-
return false
158-
}
159-
}
160-
161-
if (function.typeParameters.size == functionDescriptor.typeParameters.size) {
162-
for (index in 0 until function.typeParameters.size) {
163-
if (function.typeParameters[index].variance != functionDescriptor.typeParameters[index].variance) {
164-
return false
165-
}
166-
}
167-
}
168-
169-
return true
170-
}
171-
172-
return false
173-
}
174-
175-
private fun KtTypeReference.typeName(): String? = this.name ?: this.typeElement?.children?.filter {
176-
it is KtSimpleNameExpression
177-
}?.map {
178-
(it as KtSimpleNameExpression).getReferencedName()
179-
}?.firstOrNull()
180-
181-
private fun createFunctionStub(function: FunctionDescriptor): String {
182-
val name = function.name
183-
val arguments = function.valueParameters.map { argument ->
184-
val argumentName = argument.name
185-
val argumentType = argument.type.unwrappedType()
186-
187-
"$argumentName: $argumentType"
188-
}.joinToString(", ")
189-
val returnType = function.returnType?.unwrappedType()?.toString()?.takeIf { "Unit" != it }
190-
191-
return "override fun $name($arguments)${returnType?.let { ": $it" } ?: ""} { }"
192-
}
193-
194-
private fun createVariableStub(variable: PropertyDescriptor): String {
195-
val variableType = variable.returnType?.unwrappedType()?.toString()?.takeIf { "Unit" != it }
196-
return "override val ${variable.name}${variableType?.let { ": $it" } ?: ""} = TODO(\"SET VALUE\")"
197-
}
198-
199-
// about types: regular Kotlin types are marked T or T?, but types from Java are (T..T?) because nullability cannot be decided.
200-
// 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
201-
private fun KotlinType.unwrappedType(): KotlinType = this.unwrap().makeNullableAsSpecified(this.isMarkedNullable)
202-
203-
private fun getDeclarationPadding(file: CompiledFile, kotlinClass: KtClass): String {
204-
// If the class is not empty, the amount of padding is the same as the one in the last declaration of the class
205-
val paddingSize = if (kotlinClass.declarations.isNotEmpty()) {
206-
val lastFunctionStartOffset = kotlinClass.declarations.last().startOffset
207-
position(file.content, lastFunctionStartOffset).character
208-
} else {
209-
// Otherwise, we just use a default tab size in addition to any existing padding
210-
// on the class itself (note that the class could be inside another class, for example)
211-
position(file.content, kotlinClass.startOffset).character + DEFAULT_TAB_SIZE
212-
}
213-
214-
return " ".repeat(paddingSize)
215-
}
216-
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
219-
if (kotlinClass.declarations.isNotEmpty()) {
220-
val lastFunctionEndOffset = kotlinClass.declarations.last().endOffset
221-
position(file.content, lastFunctionEndOffset)
222-
} else { // Otherwise, the member is put at the beginning of the class
223-
val body = kotlinClass.body
224-
if (body != null) {
225-
position(file.content, body.startOffset + 1)
226-
} else {
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
231-
}
232-
}
233-
234-
private fun KtClass.hasNoBody() = null == this.body

0 commit comments

Comments
 (0)