diff --git a/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java b/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java index 0c4b178d2b58..e000cfd80a37 100644 --- a/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java +++ b/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java @@ -425,7 +425,7 @@ public class PyDocumentationBuilder { : null; if (ancestorLink != null) { final ChainIterable link = mySectionsMap.get(PyBundle.message("QDOC.documentation.is.copied.from")); - link.addWith(TagCode, isFromClass ? $(ancestorLink) : $(ancestorLink).addItem("." + methodName)); + link.addWith(TagCode, isFromClass ? $(ancestorLink) : $(PyDocumentationLink.toFunction(inherited))); } myContent.add(formatDocString(pyFunction, inheritedDoc)); return; diff --git a/python/src/com/jetbrains/python/documentation/PyDocumentationLink.kt b/python/src/com/jetbrains/python/documentation/PyDocumentationLink.kt index 8111c460ed9d..5e451765020d 100644 --- a/python/src/com/jetbrains/python/documentation/PyDocumentationLink.kt +++ b/python/src/com/jetbrains/python/documentation/PyDocumentationLink.kt @@ -18,9 +18,11 @@ package com.jetbrains.python.documentation import com.intellij.codeInsight.documentation.DocumentationManagerProtocol import com.intellij.psi.PsiElement import com.intellij.psi.util.PsiTreeUtil +import com.intellij.psi.util.QualifiedName import com.jetbrains.python.psi.PyClass import com.jetbrains.python.psi.PyFunction import com.jetbrains.python.psi.PyNamedParameter +import com.jetbrains.python.psi.PyPsiFacade import com.jetbrains.python.psi.types.PyClassType import com.jetbrains.python.psi.types.PyTypeParser import com.jetbrains.python.psi.types.TypeEvalContext @@ -30,6 +32,7 @@ object PyDocumentationLink { private const val LINK_TYPE_CLASS = "#class#" private const val LINK_TYPE_PARAM = "#param#" private const val LINK_TYPE_TYPENAME = "#typename#" + private const val LINK_TYPE_FUNC = "#func#" @JvmStatic fun toContainingClass(content: String?): String { @@ -57,20 +60,46 @@ object PyDocumentationLink { } } + @JvmStatic + fun toFunction(func: PyFunction): String = toFunction(func.qualifiedName ?: func.name.orEmpty(), func) + + @JvmStatic + fun toFunction(content: String, func: PyFunction): String { + val qualifiedName = func.qualifiedName + return when { + qualifiedName != null -> "$content" + else -> content + } + } + @JvmStatic fun elementForLink(link: String, element: PsiElement, context: TypeEvalContext): PsiElement? { - return if (link == LINK_TYPE_CLASS) { - containingClass(element) + return when { + link == LINK_TYPE_CLASS -> containingClass(element) + link == LINK_TYPE_PARAM -> parameterPossibleClass(element, context) + link.startsWith(LINK_TYPE_TYPENAME) -> possibleClass(link.substring(LINK_TYPE_TYPENAME.length), element, context) + link.startsWith(LINK_TYPE_FUNC) -> possibleFunction(link.substring(LINK_TYPE_FUNC.length), element) + else -> null } - else if (link == LINK_TYPE_PARAM) { - parameterPossibleClass(element, context) - } - else if (link.startsWith(LINK_TYPE_TYPENAME)) { - possibleClass(link.substring(LINK_TYPE_TYPENAME.length), element, context) - } - else { - null + } + + @JvmStatic + private fun possibleFunction(qualifiedName: String, element: PsiElement): PyFunction? { + // TODO a better, more general way to resolve qualified names of function + val facade = PyPsiFacade.getInstance(element.project) + val qName = QualifiedName.fromDottedString(qualifiedName) + + val resolveContext = facade.createResolveContextFromFoothold(element).copyWithMembers() + val topLevel = facade.resolveQualifiedName(qName, resolveContext).filterIsInstance().firstOrNull() + if (topLevel != null) { + return topLevel } + + return facade.resolveQualifiedName(qName.removeLastComponent(), resolveContext) + .asSequence() + .filterIsInstance() + .map { it.findMethodByName(qName.lastComponent, false, null) } + .firstOrNull() } @JvmStatic diff --git a/python/testData/quickdoc/ClassUndocumentedEmptyConstructor.html b/python/testData/quickdoc/ClassUndocumentedEmptyConstructor.html index 245940f8064b..18739cd5e3a6 100644 --- a/python/testData/quickdoc/ClassUndocumentedEmptyConstructor.html +++ b/python/testData/quickdoc/ClassUndocumentedEmptyConstructor.html @@ -1 +1 @@ -
class Foo(Moo)

def __init__(self: Foo) -> None
Doc of Moo()

Documentation is copied from:

Moo.__init__
\ No newline at end of file +
class Foo(Moo)

def __init__(self: Foo) -> None
Doc of Moo()

Documentation is copied from:

ClassUndocumentedEmptyConstructor.Moo.__init__
\ No newline at end of file diff --git a/python/testData/quickdoc/InheritedMethod.html b/python/testData/quickdoc/InheritedMethod.html index 01592659c985..350748180969 100644 --- a/python/testData/quickdoc/InheritedMethod.html +++ b/python/testData/quickdoc/InheritedMethod.html @@ -1 +1 @@ -
class B(A)

def foo(self: B) -> None
Doc from A.foo.

Documentation is copied from:

A.foo
\ No newline at end of file +
class B(A)

def foo(self: B) -> None
Doc from A.foo.

Documentation is copied from:

InheritedMethod.A.foo
\ No newline at end of file