Merge pull request #564 from JetBrains/rr/yole/kdoc-completion
completion in kdoc
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user