completion in kdoc

This commit is contained in:
Dmitry Jemerov
2015-02-05 14:59:50 +01:00
parent f450ff8ece
commit 49cecc35fd
15 changed files with 261 additions and 37 deletions
@@ -30,10 +30,12 @@ import org.jetbrains.kotlin.psi.psiUtil.getChildOfType
public class KDocName(node: ASTNode): JetElementImpl(node) {
public fun getContainingDoc(): KDoc {
val kdoc = getStrictParentOfType<KDoc>()
if (kdoc == null) {
throw IllegalStateException("KDocName must be inside a KDoc")
}
return kdoc
return kdoc ?: throw IllegalStateException("KDocName must be inside a KDoc")
}
public fun getContainingSection(): KDocSection {
val kdoc = getStrictParentOfType<KDocSection>()
return kdoc ?: throw IllegalStateException("KDocName must be inside a KDocSection")
}
public fun getQualifier(): KDocName? = getChildOfType()
@@ -678,6 +678,10 @@ fun main(args: Array<String>) {
testClass(javaClass<AbstractKDocHighlightingTest>()) {
model("kdoc/highlighting")
}
testClass<AbstractJvmBasicCompletionTest>("org.jetbrains.kotlin.idea.kdoc.KDocCompletionTestGenerated") {
model("kdoc/completion")
}
}
testGroup("idea/tests", "compiler/testData") {
@@ -734,6 +738,13 @@ fun main(args: Array<String>) {
}
private class TestGroup(val testsRoot: String, val testDataRoot: String) {
inline fun <reified T: TestCase> testClass(
suiteTestClass: String = getDefaultSuiteTestClass(javaClass<T>()),
[noinline] init: TestClass.() -> Unit
) {
testClass(javaClass<T>(), suiteTestClass, init)
}
fun testClass(
baseTestClass: Class<out TestCase>,
suiteTestClass: String = getDefaultSuiteTestClass(baseTestClass),
@@ -46,6 +46,8 @@ import org.jetbrains.kotlin.resolve.source.PsiSourceElement
import org.jetbrains.kotlin.psi.JetFile
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag
import com.intellij.psi.PsiReference
import org.jetbrains.kotlin.idea.caches.resolve.analyze
public class KDocReference(element: KDocName): JetMultiReference<KDocName>(element) {
override fun getTargetDescriptors(context: BindingContext): Collection<DeclarationDescriptor> {
@@ -92,27 +94,28 @@ public fun resolveKDocLink(session: KotlinCodeAnalyzer,
return result
}
private fun resolveParamLink(fromDescriptor: DeclarationDescriptor, qualifiedName: List<String>): List<DeclarationDescriptor> {
public fun getParamDescriptors(fromDescriptor: DeclarationDescriptor): List<DeclarationDescriptor> {
// TODO resolve parameters of functions passed as parameters
val name = qualifiedName.singleOrNull() ?: return listOf()
when (fromDescriptor) {
is CallableDescriptor ->
return fromDescriptor.getValueParameters().filter { it.getName().asString() == name }
return fromDescriptor.getValueParameters()
is ClassifierDescriptor -> {
val typeParams = fromDescriptor.getTypeConstructor().getParameters().filter { it.getName().asString() == name }
if (typeParams.isNotEmpty()) {
return typeParams
}
val typeParams = fromDescriptor.getTypeConstructor().getParameters()
if (fromDescriptor is ClassDescriptor) {
return resolveParamLink(fromDescriptor.getUnsubstitutedPrimaryConstructor(), qualifiedName)
return typeParams + fromDescriptor.getUnsubstitutedPrimaryConstructor().getValueParameters()
}
return listOf()
return typeParams
}
}
return listOf()
}
private fun resolveParamLink(fromDescriptor: DeclarationDescriptor, qualifiedName: List<String>): List<DeclarationDescriptor> {
val name = qualifiedName.singleOrNull() ?: return listOf()
return getParamDescriptors(fromDescriptor).filter { it.getName().asString() == name }
}
private fun getPackageInnerScope(descriptor: PackageFragmentDescriptor): JetScope {
val module = descriptor.getContainingDeclaration()
val packageView = module.getPackage(descriptor.fqName)
@@ -136,7 +139,7 @@ private fun getClassInnerScope(outerScope: JetScope, descriptor: ClassDescriptor
return classScope
}
private fun getResolutionScope(session: KotlinCodeAnalyzer, descriptor: DeclarationDescriptor): JetScope {
public fun getResolutionScope(session: KotlinCodeAnalyzer, descriptor: DeclarationDescriptor): JetScope {
return when (descriptor) {
is PackageFragmentDescriptor ->
getPackageInnerScope(descriptor)
+2
View File
@@ -276,6 +276,8 @@
id="JetCompletionContributor"
order="first"
implementationClass="org.jetbrains.kotlin.idea.completion.KotlinCompletionContributor"/>
<completion.contributor language="jet"
implementationClass="org.jetbrains.kotlin.idea.kdoc.KDocCompletionContributor"/>
<completion.confidence language="jet" implementationClass="com.intellij.codeInsight.completion.UnfocusedNameIdentifier"/>
<completion.confidence language="jet"
@@ -192,6 +192,11 @@ public class KotlinCompletionContributor : CompletionContributor() {
val position = parameters.getPosition()
if (position.getContainingFile() !is JetFile) return
if (position.getNonStrictParentOfType<PsiComment>() != null) {
// don't stop here, allow other contributors to run
return
}
if (shouldSuppressCompletion(parameters, result.getPrefixMatcher())) {
result.stopHere()
return
@@ -235,10 +240,6 @@ public class KotlinCompletionContributor : CompletionContributor() {
val position = parameters.getPosition()
val invocationCount = parameters.getInvocationCount()
// no completion in comments
// TODO: this must be changed if we will have references in doc-comments
if (position.getNonStrictParentOfType<PsiComment>() != null) return true
// no completion inside number literals
if (AFTER_NUMBER_LITERAL.accepts(position)) return true
@@ -0,0 +1,111 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.kdoc
import com.intellij.codeInsight.completion.CompletionContributor
import com.intellij.codeInsight.completion.CompletionType
import com.intellij.patterns.PlatformPatterns.psiElement
import org.jetbrains.kotlin.kdoc.psi.impl.KDocName
import com.intellij.codeInsight.completion.CompletionProvider
import com.intellij.codeInsight.completion.CompletionParameters
import com.intellij.util.ProcessingContext
import com.intellij.codeInsight.completion.CompletionResultSet
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.kdoc.parser.KDocKnownTag
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.kdoc.psi.impl.KDocLink
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.KotlinCacheService
import org.jetbrains.kotlin.resolve.lazy.KotlinCodeAnalyzer
import org.jetbrains.kotlin.idea.completion.LookupElementFactory
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import com.intellij.codeInsight.lookup.LookupElementDecorator
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.completion.InsertionContext
import org.jetbrains.kotlin.kdoc.lexer.KDocTokens
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.patterns.StandardPatterns
class KDocCompletionContributor(): CompletionContributor() {
{
extend(CompletionType.BASIC, psiElement().inside(javaClass<KDocName>()),
KDocNameCompletionProvider)
extend(CompletionType.BASIC,
psiElement().afterLeaf(
StandardPatterns.or(psiElement(KDocTokens.LEADING_ASTERISK), psiElement(KDocTokens.START))),
KDocTagCompletionProvider)
}
}
object KDocNameCompletionProvider: CompletionProvider<CompletionParameters>() {
override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) {
val position = parameters.getPosition().getParentOfType<KDocName>(false) ?: return
val declaration = position.getContainingDoc().getOwner() ?: return
val bindingContext = declaration.analyze()
val kdocLink = position.getStrictParentOfType<KDocLink>()!!
val declarationDescriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, declaration]
if (kdocLink.getTagIfSubject()?.knownTag == KDocKnownTag.PARAM) {
addParamCompletions(position, declarationDescriptor, result)
} else {
val session = KotlinCacheService.getInstance(parameters.getPosition().getProject()).getLazyResolveSession(position)
addLinkCompletions(session, position, declarationDescriptor, result)
}
}
private fun addParamCompletions(position: KDocName,
declarationDescriptor: DeclarationDescriptor,
result: CompletionResultSet) {
val section = position.getContainingSection()
val documentedParameters = section.findTagsByName("param").map { it.getSubjectName() }.toSet()
val descriptors = getParamDescriptors(declarationDescriptor)
.filter { it.getName().asString() !in documentedParameters }
val resolutionFacade = position.getResolutionFacade()
val factory = LookupElementFactory(listOf())
descriptors.forEach {
result.addElement(factory.createLookupElement(resolutionFacade, it, false))
}
}
private fun addLinkCompletions(session: KotlinCodeAnalyzer,
position: KDocName,
declarationDescriptor: DeclarationDescriptor,
result: CompletionResultSet) {
val scope = getResolutionScope(session, declarationDescriptor)
val resolutionFacade = position.getResolutionFacade()
val factory = LookupElementFactory(listOf())
scope.getAllDescriptors().forEach {
val element = factory.createLookupElement(resolutionFacade, it, false)
result.addElement(object: LookupElementDecorator<LookupElement>(element) {
override fun handleInsert(context: InsertionContext?) {
// don't insert any qualified name here
}
})
}
}
}
object KDocTagCompletionProvider: CompletionProvider<CompletionParameters>() {
override fun addCompletions(parameters: CompletionParameters?, context: ProcessingContext, result: CompletionResultSet) {
KDocKnownTag.values().forEach {
result.addElement(LookupElementBuilder.create("@" + it.name().toLowerCase()))
}
}
}
@@ -1,7 +0,0 @@
/**
* <caret>
*/
class C
// INVOCATION_COUNT: 1
// NUMBER: 0
+13
View File
@@ -0,0 +1,13 @@
/**
* [<caret>]
*/
fun f(xyzzy: String) {
}
fun bar() {
}
// EXIST: xyzzy
// EXIST: bar
@@ -0,0 +1,8 @@
/**
* @param <caret>
*/
fun foo(xyzzy: Int, foobar: String) {
}
// EXIST: xyzzy
// EXIST: foobar
@@ -0,0 +1,9 @@
/**
* @param xyzzy This is a xyzzy
* @param <caret>
*/
fun foo(xyzzy: Int, foobar: String) {
}
// ABSENT: xyzzy
// EXIST: foobar
+8
View File
@@ -0,0 +1,8 @@
/**
* <caret>
*/
fun f(x: Int): Int {
}
// EXIST: @param
// EXIST: @return
@@ -0,0 +1,6 @@
/** <caret> */
fun f(x: Int): Int {
}
// EXIST: @param
// EXIST: @return
@@ -694,12 +694,6 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes
doTest(fileName);
}
@TestMetadata("NoCompletionInDocComment.kt")
public void testNoCompletionInDocComment() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/NoCompletionInDocComment.kt");
doTest(fileName);
}
@TestMetadata("NoEmptyPackage.kt")
public void testNoEmptyPackage() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/NoEmptyPackage.kt");
@@ -694,12 +694,6 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
doTest(fileName);
}
@TestMetadata("NoCompletionInDocComment.kt")
public void testNoCompletionInDocComment() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/NoCompletionInDocComment.kt");
doTest(fileName);
}
@TestMetadata("NoEmptyPackage.kt")
public void testNoEmptyPackage() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/completion/basic/common/NoEmptyPackage.kt");
@@ -0,0 +1,69 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.kdoc;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.InnerTestClasses;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.JetTestUtils;
import org.jetbrains.kotlin.completion.AbstractJvmBasicCompletionTest;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("idea/testData/kdoc/completion")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class KDocCompletionTestGenerated extends AbstractJvmBasicCompletionTest {
public void testAllFilesPresentInCompletion() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/kdoc/completion"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("Link.kt")
public void testLink() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/kdoc/completion/Link.kt");
doTest(fileName);
}
@TestMetadata("ParamTag.kt")
public void testParamTag() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/kdoc/completion/ParamTag.kt");
doTest(fileName);
}
@TestMetadata("SkipExistingParamTag.kt")
public void testSkipExistingParamTag() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/kdoc/completion/SkipExistingParamTag.kt");
doTest(fileName);
}
@TestMetadata("TagName.kt")
public void testTagName() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/kdoc/completion/TagName.kt");
doTest(fileName);
}
@TestMetadata("TagNameStart.kt")
public void testTagNameStart() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/kdoc/completion/TagNameStart.kt");
doTest(fileName);
}
}