KT-14710 Sample references aren't resolved in IDE
#KT-14710 fixed
This commit is contained in:
committed by
Simon Ogorodnik
parent
cd73d17bd4
commit
bf3d4471cd
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2017 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.openapi.components.ServiceManager
|
||||||
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
|
||||||
|
interface SampleResolutionService {
|
||||||
|
fun resolveSample(context: BindingContext, fromDescriptor: DeclarationDescriptor, resolutionFacade: ResolutionFacade, qualifiedName: List<String>): Collection<DeclarationDescriptor>
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It's internal implementation, please use [resolveKDocSampleLink], or [resolveKDocLink]
|
||||||
|
*/
|
||||||
|
internal fun resolveSample(context: BindingContext, fromDescriptor: DeclarationDescriptor, resolutionFacade: ResolutionFacade, qualifiedName: List<String>): Collection<DeclarationDescriptor> {
|
||||||
|
val instance = ServiceManager.getService(resolutionFacade.project, SampleResolutionService::class.java)
|
||||||
|
return instance?.resolveSample(context, fromDescriptor, resolutionFacade, qualifiedName) ?: emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,17 +35,64 @@ import org.jetbrains.kotlin.resolve.scopes.*
|
|||||||
import org.jetbrains.kotlin.resolve.scopes.utils.collectDescriptorsFiltered
|
import org.jetbrains.kotlin.resolve.scopes.utils.collectDescriptorsFiltered
|
||||||
import org.jetbrains.kotlin.resolve.scopes.utils.memberScopeAsImportingScope
|
import org.jetbrains.kotlin.resolve.scopes.utils.memberScopeAsImportingScope
|
||||||
import org.jetbrains.kotlin.resolve.source.PsiSourceElement
|
import org.jetbrains.kotlin.resolve.source.PsiSourceElement
|
||||||
|
import org.jetbrains.kotlin.utils.ifEmpty
|
||||||
|
|
||||||
fun resolveKDocLink(context: BindingContext,
|
fun resolveKDocLink(context: BindingContext,
|
||||||
resolutionFacade: ResolutionFacade,
|
resolutionFacade: ResolutionFacade,
|
||||||
fromDescriptor: DeclarationDescriptor,
|
fromDescriptor: DeclarationDescriptor,
|
||||||
fromSubjectOfTag: KDocTag?,
|
fromSubjectOfTag: KDocTag?,
|
||||||
qualifiedName: List<String>): Collection<DeclarationDescriptor> {
|
qualifiedName: List<String>): Collection<DeclarationDescriptor> =
|
||||||
|
when (fromSubjectOfTag?.knownTag) {
|
||||||
|
KDocKnownTag.PARAM -> resolveParamLink(fromDescriptor, qualifiedName)
|
||||||
|
KDocKnownTag.SAMPLE -> resolveKDocSampleLink(context, resolutionFacade, fromDescriptor, qualifiedName)
|
||||||
|
else -> resolveDefaultKDocLink(context, resolutionFacade, fromDescriptor, qualifiedName)
|
||||||
|
}
|
||||||
|
|
||||||
if (fromSubjectOfTag?.knownTag == KDocKnownTag.PARAM) {
|
|
||||||
return resolveParamLink(fromDescriptor, qualifiedName)
|
fun getParamDescriptors(fromDescriptor: DeclarationDescriptor): List<DeclarationDescriptor> {
|
||||||
|
// TODO resolve parameters of functions passed as parameters
|
||||||
|
when (fromDescriptor) {
|
||||||
|
is CallableDescriptor ->
|
||||||
|
return fromDescriptor.valueParameters + fromDescriptor.typeParameters
|
||||||
|
is ClassifierDescriptor -> {
|
||||||
|
val typeParams = fromDescriptor.typeConstructor.parameters
|
||||||
|
if (fromDescriptor is ClassDescriptor) {
|
||||||
|
val constructorDescriptor = fromDescriptor.unsubstitutedPrimaryConstructor
|
||||||
|
if (constructorDescriptor != null) {
|
||||||
|
return typeParams + constructorDescriptor.valueParameters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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.name.asString() == name }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resolveKDocSampleLink(context: BindingContext,
|
||||||
|
resolutionFacade: ResolutionFacade,
|
||||||
|
fromDescriptor: DeclarationDescriptor,
|
||||||
|
qualifiedName: List<String>): Collection<DeclarationDescriptor> {
|
||||||
|
|
||||||
|
val resolvedViaService = SampleResolutionService.resolveSample(context, fromDescriptor, resolutionFacade, qualifiedName)
|
||||||
|
if (resolvedViaService.isNotEmpty()) return resolvedViaService
|
||||||
|
|
||||||
|
return resolveDefaultKDocLink(context, resolutionFacade, fromDescriptor, qualifiedName)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private fun resolveDefaultKDocLink(context: BindingContext,
|
||||||
|
resolutionFacade: ResolutionFacade,
|
||||||
|
fromDescriptor: DeclarationDescriptor,
|
||||||
|
qualifiedName: List<String>): Collection<DeclarationDescriptor> {
|
||||||
|
|
||||||
val scope = getKDocLinkResolutionScope(resolutionFacade, fromDescriptor)
|
val scope = getKDocLinkResolutionScope(resolutionFacade, fromDescriptor)
|
||||||
|
|
||||||
if (qualifiedName.size == 1) {
|
if (qualifiedName.size == 1) {
|
||||||
@@ -82,31 +129,6 @@ fun resolveKDocLink(context: BindingContext,
|
|||||||
return listOf(descriptor)
|
return listOf(descriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getParamDescriptors(fromDescriptor: DeclarationDescriptor): List<DeclarationDescriptor> {
|
|
||||||
// TODO resolve parameters of functions passed as parameters
|
|
||||||
when (fromDescriptor) {
|
|
||||||
is CallableDescriptor ->
|
|
||||||
return fromDescriptor.valueParameters + fromDescriptor.typeParameters
|
|
||||||
is ClassifierDescriptor -> {
|
|
||||||
val typeParams = fromDescriptor.typeConstructor.parameters
|
|
||||||
if (fromDescriptor is ClassDescriptor) {
|
|
||||||
val constructorDescriptor = fromDescriptor.unsubstitutedPrimaryConstructor
|
|
||||||
if (constructorDescriptor != null) {
|
|
||||||
return typeParams + constructorDescriptor.valueParameters
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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.name.asString() == name }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getPackageInnerScope(descriptor: PackageFragmentDescriptor): MemberScope {
|
private fun getPackageInnerScope(descriptor: PackageFragmentDescriptor): MemberScope {
|
||||||
return descriptor.containingDeclaration.getPackage(descriptor.fqName).memberScope
|
return descriptor.containingDeclaration.getPackage(descriptor.fqName).memberScope
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -314,6 +314,9 @@
|
|||||||
<projectService serviceInterface="org.jetbrains.kotlin.idea.core.NotPropertiesService"
|
<projectService serviceInterface="org.jetbrains.kotlin.idea.core.NotPropertiesService"
|
||||||
serviceImplementation="org.jetbrains.kotlin.idea.intentions.NotPropertiesServiceImpl"/>
|
serviceImplementation="org.jetbrains.kotlin.idea.intentions.NotPropertiesServiceImpl"/>
|
||||||
|
|
||||||
|
<projectService serviceInterface="org.jetbrains.kotlin.idea.kdoc.SampleResolutionService"
|
||||||
|
serviceImplementation="org.jetbrains.kotlin.idea.kdoc.IdeSampleResolutionService"/>
|
||||||
|
|
||||||
<errorHandler implementation="org.jetbrains.kotlin.idea.reporter.KotlinReportSubmitter"/>
|
<errorHandler implementation="org.jetbrains.kotlin.idea.reporter.KotlinReportSubmitter"/>
|
||||||
|
|
||||||
<internalFileTemplate name="Kotlin File"/>
|
<internalFileTemplate name="Kotlin File"/>
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2017 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.openapi.project.Project
|
||||||
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
|
||||||
|
import org.jetbrains.kotlin.idea.stubindex.KotlinFunctionShortNameIndex
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
|
|
||||||
|
class IdeSampleResolutionService(val project: Project) : SampleResolutionService {
|
||||||
|
|
||||||
|
override fun resolveSample(context: BindingContext, fromDescriptor: DeclarationDescriptor, resolutionFacade: ResolutionFacade, qualifiedName: List<String>): Collection<DeclarationDescriptor> {
|
||||||
|
val allScope = GlobalSearchScope.projectScope(project)
|
||||||
|
val shortName = qualifiedName.lastOrNull() ?: return emptyList()
|
||||||
|
val functions = KotlinFunctionShortNameIndex.getInstance().get(shortName, project, allScope)
|
||||||
|
val targetFqName = FqName.fromSegments(qualifiedName)
|
||||||
|
|
||||||
|
|
||||||
|
val descriptors = functions.asSequence()
|
||||||
|
.filter { it.fqName == targetFqName }
|
||||||
|
.map { it.resolveToDescriptor(BodyResolveMode.PARTIAL) } // TODO Filter out not visible due dependencies config descriptors
|
||||||
|
.toList()
|
||||||
|
return descriptors
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* @sample samples.sample
|
||||||
|
*/
|
||||||
|
fun some<caret>() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//INFO: <pre><b>public</b> <b>fun</b> some(): Unit <i>defined in</i> root package</pre><br/>
|
||||||
|
//INFO: <dl><dt><b>Samples:</b></dt><dd><a href="psi_element://samples.sample"><code>samples.sample</code></a><pre><code>
|
||||||
|
//INFO: println("Hello, world")
|
||||||
|
//INFO: </code></pre></dd></dl>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package samples
|
||||||
|
|
||||||
|
fun sample() {
|
||||||
|
println("Hello, world")
|
||||||
|
}
|
||||||
+1
-1
@@ -81,7 +81,7 @@ public abstract class AbstractQuickDocProviderTest extends KotlinLightCodeInsigh
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void wrapToFileComparisonFailure(String info, String filePath, String fileData) {
|
public static void wrapToFileComparisonFailure(String info, String filePath, String fileData) {
|
||||||
List<String> infoLines = StringUtil.split(info, "\n");
|
List<String> infoLines = StringUtil.split(info, "\n");
|
||||||
StringBuilder infoBuilder = new StringBuilder();
|
StringBuilder infoBuilder = new StringBuilder();
|
||||||
for (String line : infoLines) {
|
for (String line : infoLines) {
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2017 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.documentation.DocumentationManager
|
||||||
|
import com.intellij.openapi.util.io.FileUtil
|
||||||
|
import com.intellij.openapi.util.text.StringUtil
|
||||||
|
import com.intellij.rt.execution.junit.FileComparisonFailure
|
||||||
|
import org.jetbrains.kotlin.idea.editor.quickDoc.AbstractQuickDocProviderTest.wrapToFileComparisonFailure
|
||||||
|
import org.jetbrains.kotlin.idea.stubs.AbstractMultiModuleTest
|
||||||
|
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class KDocSampleTest : AbstractMultiModuleTest() {
|
||||||
|
|
||||||
|
override val testPath: String = "${super.testPath}/kdoc/multiModuleSamples/"
|
||||||
|
override fun getTestDataPath() = testPath
|
||||||
|
|
||||||
|
fun testSimple() {
|
||||||
|
|
||||||
|
val code = module("code")
|
||||||
|
val samples = module("samples", hasTestRoot = true)
|
||||||
|
|
||||||
|
samples.addDependency(code)
|
||||||
|
|
||||||
|
doTest("simple/code/usage.kt")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun doTest(path: String) {
|
||||||
|
val testDataFile = File(testPath, path)
|
||||||
|
configureByFile(path)
|
||||||
|
val documentationManager = DocumentationManager.getInstance(myProject)
|
||||||
|
val targetElement = documentationManager.findTargetElement(myEditor, file)
|
||||||
|
val originalElement = DocumentationManager.getOriginalElement(targetElement)
|
||||||
|
|
||||||
|
var info = DocumentationManager.getProviderFromElement(targetElement).generateDoc(targetElement, originalElement)
|
||||||
|
if (info != null) {
|
||||||
|
info = StringUtil.convertLineSeparators(info)
|
||||||
|
}
|
||||||
|
if (info != null && !info.endsWith("\n")) {
|
||||||
|
info += "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
val textData = FileUtil.loadFile(testDataFile, true)
|
||||||
|
val directives = InTextDirectivesUtils.findLinesWithPrefixesRemoved(textData, false, "INFO:")
|
||||||
|
|
||||||
|
if (directives.isEmpty()) {
|
||||||
|
throw FileComparisonFailure(
|
||||||
|
"'// INFO:' directive was expected",
|
||||||
|
textData,
|
||||||
|
textData + "\n\n//INFO: " + info,
|
||||||
|
testDataFile.absolutePath)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val expectedInfo = directives.joinToString("\n", postfix = "\n")
|
||||||
|
|
||||||
|
if (expectedInfo.endsWith("...\n")) {
|
||||||
|
if (!info!!.startsWith(expectedInfo.removeSuffix("...\n"))) {
|
||||||
|
wrapToFileComparisonFailure(info, testDataPath, textData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (expectedInfo != info) {
|
||||||
|
wrapToFileComparisonFailure(info, path, textData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user