diff --git a/server/src/main/kotlin/org/javacs/kt/codeaction/quickfix/ImplementAbstractFunctionsQuickFix.kt b/server/src/main/kotlin/org/javacs/kt/codeaction/quickfix/ImplementAbstractFunctionsQuickFix.kt index cb063951f..12910c87d 100644 --- a/server/src/main/kotlin/org/javacs/kt/codeaction/quickfix/ImplementAbstractFunctionsQuickFix.kt +++ b/server/src/main/kotlin/org/javacs/kt/codeaction/quickfix/ImplementAbstractFunctionsQuickFix.kt @@ -7,16 +7,29 @@ import org.javacs.kt.index.SymbolIndex import org.javacs.kt.position.offset import org.javacs.kt.position.position import org.javacs.kt.util.toPath +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.isInterface +import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.KtClass import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtNamedFunction +import org.jetbrains.kotlin.psi.KtSimpleNameExpression +import org.jetbrains.kotlin.psi.KtSuperTypeListEntry +import org.jetbrains.kotlin.psi.KtTypeArgumentList +import org.jetbrains.kotlin.psi.KtTypeReference import org.jetbrains.kotlin.psi.psiUtil.containingClass import org.jetbrains.kotlin.psi.psiUtil.endOffset import org.jetbrains.kotlin.psi.psiUtil.isAbstract import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.resolve.diagnostics.Diagnostics +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.TypeProjection +import org.jetbrains.kotlin.types.typeUtil.asTypeProjection private const val DEFAULT_TAB_SIZE = 4 @@ -27,7 +40,7 @@ class ImplementAbstractFunctionsQuickFix : QuickFix { val startCursor = offset(file.content, range.start) val endCursor = offset(file.content, range.end) val kotlinDiagnostics = file.compile.diagnostics - + // 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 @@ -70,31 +83,47 @@ private fun getAbstractFunctionStubs(file: CompiledFile, kotlinClass: KtClass) = // For each of the super types used by this class kotlinClass.superTypeListEntries.mapNotNull { // Find the definition of this super type - val descriptor = file.referenceAtPoint(it.startOffset)?.second - val superClass = descriptor?.findPsi() + val referenceAtPoint = file.referenceExpressionAtPoint(it.startOffset) + val descriptor = referenceAtPoint?.second + + val classDescriptor = getClassDescriptor(descriptor) + // If the super class is abstract or an interface - if (superClass is KtClass && (superClass.isAbstract() || superClass.isInterface())) { - // Get the abstract functions of this super type that are currently not implemented by this class - val abstractFunctions = superClass.declarations.filter { - declaration -> isAbstractFunction(declaration) && !overridesDeclaration(kotlinClass, declaration) + if (null != classDescriptor && (classDescriptor.kind.isInterface || classDescriptor.modality == Modality.ABSTRACT)) { + val superClassTypeArguments = getSuperClassTypeProjections(file, it) + classDescriptor.getMemberScope(superClassTypeArguments).getContributedDescriptors().filter { classMember -> + classMember is FunctionDescriptor && classMember.modality == Modality.ABSTRACT && !overridesDeclaration(kotlinClass, classMember) + }.map { function -> + createFunctionStub(function as FunctionDescriptor) } - // Get stubs for each function - abstractFunctions.map { function -> getFunctionStub(function as KtNamedFunction) } } else { null } }.flatten() -private fun isAbstractFunction(declaration: KtDeclaration): Boolean = - declaration is KtNamedFunction && !declaration.hasBody() - && (declaration.containingClass()?.isInterface() ?: false || declaration.hasModifier(KtTokens.ABSTRACT_KEYWORD)) +// interfaces are ClassDescriptors by default. When calling AbstractClass super methods, we get a ClassConstructorDescriptor +private fun getClassDescriptor(descriptor: DeclarationDescriptor?): ClassDescriptor? = if (descriptor is ClassDescriptor) { + descriptor +} else if (descriptor is ClassConstructorDescriptor) { + descriptor.containingDeclaration +} else { + null +} + +private fun getSuperClassTypeProjections(file: CompiledFile, superType: KtSuperTypeListEntry): List = superType.typeReference?.typeElement?.children?.filter { + it is KtTypeArgumentList +}?.flatMap { + (it as KtTypeArgumentList).arguments +}?.mapNotNull { + (file.referenceExpressionAtPoint(it?.startOffset ?: 0)?.second as? ClassDescriptor)?.defaultType?.asTypeProjection() +} ?: emptyList() // Checks if the class overrides the given declaration -private fun overridesDeclaration(kotlinClass: KtClass, declaration: KtDeclaration): Boolean = +private fun overridesDeclaration(kotlinClass: KtClass, descriptor: FunctionDescriptor): Boolean = kotlinClass.declarations.any { - if (it.name == declaration.name && it.hasModifier(KtTokens.OVERRIDE_KEYWORD)) { - if (it is KtNamedFunction && declaration is KtNamedFunction) { - parametersMatch(it, declaration) + if (it.name == descriptor.name.asString() && it.hasModifier(KtTokens.OVERRIDE_KEYWORD)) { + if (it is KtNamedFunction) { + parametersMatch(it, descriptor) } else { true } @@ -104,19 +133,21 @@ private fun overridesDeclaration(kotlinClass: KtClass, declaration: KtDeclaratio } // Checks if two functions have matching parameters -private fun parametersMatch(function: KtNamedFunction, functionDeclaration: KtNamedFunction): Boolean { - if (function.valueParameters.size == functionDeclaration.valueParameters.size) { +private fun parametersMatch(function: KtNamedFunction, functionDescriptor: FunctionDescriptor): Boolean { + if (function.valueParameters.size == functionDescriptor.valueParameters.size) { for (index in 0 until function.valueParameters.size) { - if (function.valueParameters[index].name != functionDeclaration.valueParameters[index].name) { + if (function.valueParameters[index].name != functionDescriptor.valueParameters[index].name.asString()) { return false - } else if (function.valueParameters[index].typeReference?.name != functionDeclaration.valueParameters[index].typeReference?.name) { + } else if (function.valueParameters[index].typeReference?.typeName() != functionDescriptor.valueParameters[index].type.unwrappedType().toString()) { + // Note: Since we treat Java overrides as non nullable by default, the above test will fail when the user has made the type nullable. + // TODO: look into this return false } } - if (function.typeParameters.size == functionDeclaration.typeParameters.size) { + if (function.typeParameters.size == functionDescriptor.typeParameters.size) { for (index in 0 until function.typeParameters.size) { - if (function.typeParameters[index].variance != functionDeclaration.typeParameters[index].variance) { + if (function.typeParameters[index].variance != functionDescriptor.typeParameters[index].variance) { return false } } @@ -128,8 +159,28 @@ private fun parametersMatch(function: KtNamedFunction, functionDeclaration: KtNa return false } -private fun getFunctionStub(function: KtNamedFunction): String = - "override fun" + function.text.substringAfter("fun") + " { }" +private fun KtTypeReference.typeName(): String? = this.name ?: this.typeElement?.children?.filter { + it is KtSimpleNameExpression +}?.map { + (it as KtSimpleNameExpression).getReferencedName() +}?.firstOrNull() + +private fun createFunctionStub(function: FunctionDescriptor): String { + val name = function.name + val arguments = function.valueParameters.map { argument -> + val argumentName = argument.name + val argumentType = argument.type.unwrappedType() + + "$argumentName: $argumentType" + }.joinToString(", ") + val returnType = function.returnType?.unwrappedType()?.toString()?.takeIf { "Unit" != it } + + return "override fun $name($arguments)${returnType?.let { ": $it" } ?: ""} { }" +} + +// about types: regular Kotlin types are marked T or T?, but types from Java are (T..T?) because nullability cannot be decided. +// 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 +private fun KotlinType.unwrappedType(): KotlinType = this.unwrap().makeNullableAsSpecified(this.isMarkedNullable) private fun getDeclarationPadding(file: CompiledFile, kotlinClass: KtClass): String { // If the class is not empty, the amount of padding is the same as the one in the last declaration of the class diff --git a/server/src/test/kotlin/org/javacs/kt/QuickFixesTest.kt b/server/src/test/kotlin/org/javacs/kt/QuickFixesTest.kt index 229a01859..4a514b89d 100644 --- a/server/src/test/kotlin/org/javacs/kt/QuickFixesTest.kt +++ b/server/src/test/kotlin/org/javacs/kt/QuickFixesTest.kt @@ -3,8 +3,9 @@ package org.javacs.kt import org.eclipse.lsp4j.CodeActionKind import org.eclipse.lsp4j.Diagnostic import org.eclipse.lsp4j.jsonrpc.messages.Either -import org.hamcrest.Matchers -import org.junit.Assert +import org.hamcrest.Matchers.equalTo +import org.hamcrest.Matchers.hasSize +import org.junit.Assert.assertThat import org.junit.Test class ImplementAbstractFunctionsQuickFixTest : SingleFileTestFixture("quickfixes", "SomeSubclass.kt") { @@ -16,27 +17,27 @@ class ImplementAbstractFunctionsQuickFixTest : SingleFileTestFixture("quickfixes val codeActions = languageServer.textDocumentService.codeAction(codeActionParams).get() - Assert.assertThat(codeActions.size, Matchers.equalTo(1)) - Assert.assertThat(codeActions[0].right.kind, Matchers.equalTo(CodeActionKind.QuickFix)) - Assert.assertThat(codeActions[0].right.diagnostics.size, Matchers.equalTo(1)) - Assert.assertThat(codeActions[0].right.diagnostics[0], Matchers.equalTo(diagnostic)) - Assert.assertThat(codeActions[0].right.edit.changes.size, Matchers.equalTo(1)) - Assert.assertThat(codeActions[0].right.edit.changes[codeActionParams.textDocument.uri]?.size, Matchers.equalTo(2)) - Assert.assertThat( + assertThat(codeActions.size, equalTo(1)) + assertThat(codeActions[0].right.kind, equalTo(CodeActionKind.QuickFix)) + assertThat(codeActions[0].right.diagnostics.size, equalTo(1)) + assertThat(codeActions[0].right.diagnostics[0], equalTo(diagnostic)) + assertThat(codeActions[0].right.edit.changes.size, equalTo(1)) + assertThat(codeActions[0].right.edit.changes[codeActionParams.textDocument.uri]?.size, equalTo(2)) + assertThat( codeActions[0].right.edit.changes[codeActionParams.textDocument.uri]?.get(0)?.range, - Matchers.equalTo(range(3, 55, 3, 55)) + equalTo(range(3, 55, 3, 55)) ) - Assert.assertThat( + assertThat( codeActions[0].right.edit.changes[codeActionParams.textDocument.uri]?.get(0)?.newText, - Matchers.equalTo(System.lineSeparator() + System.lineSeparator() + " override fun someSuperMethod(someParameter: String): Int { }") + equalTo(System.lineSeparator() + System.lineSeparator() + " override fun someSuperMethod(someParameter: String): Int { }") ) - Assert.assertThat( + assertThat( codeActions[0].right.edit.changes[codeActionParams.textDocument.uri]?.get(1)?.range, - Matchers.equalTo(range(3, 55, 3, 55)) + equalTo(range(3, 55, 3, 55)) ) - Assert.assertThat( + assertThat( codeActions[0].right.edit.changes[codeActionParams.textDocument.uri]?.get(1)?.newText, - Matchers.equalTo(System.lineSeparator() + System.lineSeparator() + " override fun someInterfaceMethod() { }") + equalTo(System.lineSeparator() + System.lineSeparator() + " override fun someInterfaceMethod() { }") ) } @@ -48,19 +49,19 @@ class ImplementAbstractFunctionsQuickFixTest : SingleFileTestFixture("quickfixes val codeActions = languageServer.textDocumentService.codeAction(codeActionParams).get() - Assert.assertThat(codeActions.size, Matchers.equalTo(1)) - Assert.assertThat(codeActions[0].right.kind, Matchers.equalTo(CodeActionKind.QuickFix)) - Assert.assertThat(codeActions[0].right.diagnostics.size, Matchers.equalTo(1)) - Assert.assertThat(codeActions[0].right.diagnostics[0], Matchers.equalTo(diagnostic)) - Assert.assertThat(codeActions[0].right.edit.changes.size, Matchers.equalTo(1)) - Assert.assertThat(codeActions[0].right.edit.changes[codeActionParams.textDocument.uri]?.size, Matchers.equalTo(1)) - Assert.assertThat( + assertThat(codeActions.size, equalTo(1)) + assertThat(codeActions[0].right.kind, equalTo(CodeActionKind.QuickFix)) + assertThat(codeActions[0].right.diagnostics.size, equalTo(1)) + assertThat(codeActions[0].right.diagnostics[0], equalTo(diagnostic)) + assertThat(codeActions[0].right.edit.changes.size, equalTo(1)) + assertThat(codeActions[0].right.edit.changes[codeActionParams.textDocument.uri]?.size, equalTo(1)) + assertThat( codeActions[0].right.edit.changes[codeActionParams.textDocument.uri]?.get(0)?.range, - Matchers.equalTo(range(7, 74, 7, 74)) + equalTo(range(7, 74, 7, 74)) ) - Assert.assertThat( + assertThat( codeActions[0].right.edit.changes[codeActionParams.textDocument.uri]?.get(0)?.newText, - Matchers.equalTo(System.lineSeparator() + System.lineSeparator() + " override fun someInterfaceMethod() { }") + equalTo(System.lineSeparator() + System.lineSeparator() + " override fun someInterfaceMethod() { }") ) } @@ -72,19 +73,170 @@ class ImplementAbstractFunctionsQuickFixTest : SingleFileTestFixture("quickfixes val codeActions = languageServer.textDocumentService.codeAction(codeActionParams).get() - Assert.assertThat(codeActions.size, Matchers.equalTo(1)) - Assert.assertThat(codeActions[0].right.kind, Matchers.equalTo(CodeActionKind.QuickFix)) - Assert.assertThat(codeActions[0].right.diagnostics.size, Matchers.equalTo(1)) - Assert.assertThat(codeActions[0].right.diagnostics[0], Matchers.equalTo(diagnostic)) - Assert.assertThat(codeActions[0].right.edit.changes.size, Matchers.equalTo(1)) - Assert.assertThat(codeActions[0].right.edit.changes[codeActionParams.textDocument.uri]?.size, Matchers.equalTo(1)) - Assert.assertThat( + assertThat(codeActions.size, equalTo(1)) + assertThat(codeActions[0].right.kind, equalTo(CodeActionKind.QuickFix)) + assertThat(codeActions[0].right.diagnostics.size, equalTo(1)) + assertThat(codeActions[0].right.diagnostics[0], equalTo(diagnostic)) + assertThat(codeActions[0].right.edit.changes.size, equalTo(1)) + assertThat(codeActions[0].right.edit.changes[codeActionParams.textDocument.uri]?.size, equalTo(1)) + assertThat( codeActions[0].right.edit.changes[codeActionParams.textDocument.uri]?.get(0)?.range, - Matchers.equalTo(range(11, 43, 11, 43)) + equalTo(range(11, 43, 11, 43)) ) - Assert.assertThat( + assertThat( codeActions[0].right.edit.changes[codeActionParams.textDocument.uri]?.get(0)?.newText, - Matchers.equalTo(System.lineSeparator() + System.lineSeparator() + " override fun someSuperMethod(someParameter: String): Int { }") + equalTo(System.lineSeparator() + System.lineSeparator() + " override fun someSuperMethod(someParameter: String): Int { }") ) } } + +class ImplementAbstractFunctionsQuickFixSameFileTest : SingleFileTestFixture("quickfixes", "samefile.kt") { + @Test + fun `should find no code actions`() { + val only = listOf(CodeActionKind.QuickFix) + val codeActionParams = codeActionParams(file, 3, 1, 3, 22, diagnostics, only) + + val codeActionResult = languageServer.textDocumentService.codeAction(codeActionParams).get() + + assertThat(codeActionResult, hasSize(0)) + } + + @Test + fun `should find one abstract method to implement`() { + val only = listOf(CodeActionKind.QuickFix) + val codeActionParams = codeActionParams(file, 7, 1, 7, 14, 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 functions")) + assertThat(codeAction.diagnostics, equalTo(listOf(diagnostics[0]))) + + val textEdit = codeAction.edit.changes + val key = workspaceRoot.resolve(file).toUri().toString() + assertThat(textEdit.containsKey(key), equalTo(true)) + assertThat(textEdit[key], hasSize(1)) + + val functionToImplementEdit = textEdit[key]?.get(0) + assertThat(functionToImplementEdit?.range, equalTo(range(7, 30, 7, 30))) + assertThat(functionToImplementEdit?.newText, equalTo(System.lineSeparator() + System.lineSeparator() + " override fun test(input: String, otherInput: Int) { }")) + } + + @Test + fun `should find several abstract methods to implement`() { + val only = listOf(CodeActionKind.QuickFix) + val codeActionParams = codeActionParams(file, 15, 1, 15, 21, 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 functions")) + + val textEdit = codeAction.edit.changes + val key = workspaceRoot.resolve(file).toUri().toString() + assertThat(textEdit.containsKey(key), equalTo(true)) + assertThat(textEdit[key], hasSize(2)) + + val firstFunctionToImplementEdit = textEdit[key]?.get(0) + assertThat(firstFunctionToImplementEdit?.range, equalTo(range(15, 49, 15, 49))) + assertThat(firstFunctionToImplementEdit?.newText, equalTo(System.lineSeparator() + System.lineSeparator() + " override fun print() { }")) + + val secondFunctionToImplementEdit = textEdit[key]?.get(1) + assertThat(secondFunctionToImplementEdit?.range, equalTo(range(15, 49, 15, 49))) + assertThat(secondFunctionToImplementEdit?.newText, equalTo(System.lineSeparator() + System.lineSeparator() + " override fun test(input: String, otherInput: Int) { }")) + } + + @Test + fun `should find only one abstract method when the other one is already implemented`() { + val only = listOf(CodeActionKind.QuickFix) + val codeActionParams = codeActionParams(file, 17, 1, 17, 26, 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 functions")) + + val textEdit = codeAction.edit.changes + val key = workspaceRoot.resolve(file).toUri().toString() + assertThat(textEdit.containsKey(key), equalTo(true)) + assertThat(textEdit[key], hasSize(1)) + + val functionToImplementEdit = textEdit[key]?.get(0) + assertThat(functionToImplementEdit?.range, equalTo(range(18, 57, 18, 57))) + assertThat(functionToImplementEdit?.newText, equalTo(System.lineSeparator() + System.lineSeparator() + " override fun print() { }")) + } + + @Test + fun `should respect nullability of parameter and return value in abstract method`() { + val only = listOf(CodeActionKind.QuickFix) + val codeActionParams = codeActionParams(file, 25, 1, 25, 16, 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 functions")) + + val textEdit = codeAction.edit.changes + val key = workspaceRoot.resolve(file).toUri().toString() + assertThat(textEdit.containsKey(key), equalTo(true)) + assertThat(textEdit[key], hasSize(1)) + + val functionToImplementEdit = textEdit[key]?.get(0) + assertThat(functionToImplementEdit?.range, equalTo(range(25, 48, 25, 48))) + assertThat(functionToImplementEdit?.newText, equalTo(System.lineSeparator() + System.lineSeparator() + " override fun myMethod(myStr: String?): String? { }")) + } +} + +class ImplementAbstractFunctionsQuickFixExternalLibraryTest : SingleFileTestFixture("quickfixes", "standardlib.kt") { + @Test + fun `should find one abstract method from Runnable to implement`() { + val only = listOf(CodeActionKind.QuickFix) + val codeActionParams = codeActionParams(file, 5, 1, 5, 15, 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 functions")) + + val textEdit = codeAction.edit.changes + val key = workspaceRoot.resolve(file).toUri().toString() + assertThat(textEdit.containsKey(key), equalTo(true)) + assertThat(textEdit[key], hasSize(1)) + + val functionToImplementEdit = textEdit[key]?.get(0) + assertThat(functionToImplementEdit?.range, equalTo(range(5, 28, 5, 28))) + assertThat(functionToImplementEdit?.newText, equalTo(System.lineSeparator() + System.lineSeparator() + " override fun run() { }")) + } + + @Test + fun `should find one abstract method from Comparable to implement`() { + val only = listOf(CodeActionKind.QuickFix) + val codeActionParams = codeActionParams(file, 7, 1, 7, 19, 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 functions")) + + val textEdit = codeAction.edit.changes + val key = workspaceRoot.resolve(file).toUri().toString() + assertThat(textEdit.containsKey(key), equalTo(true)) + assertThat(textEdit[key], hasSize(1)) + + val functionToImplementEdit = textEdit[key]?.get(0) + assertThat(functionToImplementEdit?.range, equalTo(range(7, 42, 7, 42))) + assertThat(functionToImplementEdit?.newText, equalTo(System.lineSeparator() + System.lineSeparator() + " override fun compare(p0: String, p1: String): Int { }")) + } +} diff --git a/server/src/test/resources/quickfixes/samefile.kt b/server/src/test/resources/quickfixes/samefile.kt new file mode 100644 index 000000000..6e322092d --- /dev/null +++ b/server/src/test/resources/quickfixes/samefile.kt @@ -0,0 +1,25 @@ +package test.kotlin.lsp + +interface MyInterface { + fun test(input: String, otherInput: Int) +} + +class MyClass : MyInterface { +} + + +abstract class CanPrint { + abstract fun print() +} + +class PrintableClass : CanPrint(), MyInterface {} + +class OtherPrintableClass : CanPrint(), MyInterface { + override fun test(input: String, otherInput: Int) {} +} + +interface NullMethodAndReturn { + fun myMethod(myStr: T?): T? +} + +class NullClass : NullMethodAndReturn {} diff --git a/server/src/test/resources/quickfixes/standardlib.kt b/server/src/test/resources/quickfixes/standardlib.kt new file mode 100644 index 000000000..665eab2d2 --- /dev/null +++ b/server/src/test/resources/quickfixes/standardlib.kt @@ -0,0 +1,7 @@ +package test.kotlin.lsp + +import java.util.Comparator + +class MyThread : Runnable {} + +class MyComperable : Comparator {}