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.memberScopeAsImportingScope
|
||||
import org.jetbrains.kotlin.resolve.source.PsiSourceElement
|
||||
import org.jetbrains.kotlin.utils.ifEmpty
|
||||
|
||||
fun resolveKDocLink(context: BindingContext,
|
||||
resolutionFacade: ResolutionFacade,
|
||||
fromDescriptor: DeclarationDescriptor,
|
||||
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)
|
||||
|
||||
if (qualifiedName.size == 1) {
|
||||
@@ -82,31 +129,6 @@ fun resolveKDocLink(context: BindingContext,
|
||||
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 {
|
||||
return descriptor.containingDeclaration.getPackage(descriptor.fqName).memberScope
|
||||
}
|
||||
|
||||
@@ -314,6 +314,9 @@
|
||||
<projectService serviceInterface="org.jetbrains.kotlin.idea.core.NotPropertiesService"
|
||||
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"/>
|
||||
|
||||
<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");
|
||||
StringBuilder infoBuilder = new StringBuilder();
|
||||
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