KT-14710 Sample references aren't resolved in IDE

#KT-14710 fixed
This commit is contained in:
Simon Ogorodnik
2017-01-17 18:18:31 +03:00
committed by Simon Ogorodnik
parent cd73d17bd4
commit bf3d4471cd
8 changed files with 232 additions and 29 deletions
@@ -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
}
+3
View File
@@ -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")
}
@@ -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)
}
}
}
}