From 924ed716d684741ef347b6e32d9fbb6d5d718ad5 Mon Sep 17 00:00:00 2001 From: Semyon Proshev Date: Mon, 14 Aug 2017 19:11:39 +0300 Subject: [PATCH] Move all documentation links processing to separate class --- .../DocumentationBuilderKit.java | 19 +-- .../documentation/PyDocumentationBuilder.java | 24 ++-- .../documentation/PyDocumentationLink.kt | 116 ++++++++++++++++++ .../documentation/PyTypeModelBuilder.java | 10 +- .../PythonDocumentationProvider.java | 65 +--------- .../ClassUndocumentedConstructor.html | 2 +- python/testData/quickdoc/TypeVars.html | 2 +- 7 files changed, 139 insertions(+), 99 deletions(-) create mode 100644 python/src/com/jetbrains/python/documentation/PyDocumentationLink.kt diff --git a/python/src/com/jetbrains/python/documentation/DocumentationBuilderKit.java b/python/src/com/jetbrains/python/documentation/DocumentationBuilderKit.java index 0d4d1aae4a33..b97953955e10 100644 --- a/python/src/com/jetbrains/python/documentation/DocumentationBuilderKit.java +++ b/python/src/com/jetbrains/python/documentation/DocumentationBuilderKit.java @@ -16,7 +16,6 @@ package com.jetbrains.python.documentation; import com.google.common.collect.Lists; -import com.intellij.codeInsight.documentation.DocumentationManagerProtocol; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.xml.CommonXmlStrings; @@ -51,6 +50,9 @@ class DocumentationBuilderKit { @NotNull static final Function WRAP_IN_ITALIC = s -> "" + s + ""; + @NotNull + static final Function WRAP_IN_CODE = s -> "" + s + ""; + private DocumentationBuilderKit() { } @@ -101,19 +103,4 @@ class DocumentationBuilderKit { } } - - static class LinkWrapper implements Function, Iterable> { - private final String myLink; - - LinkWrapper(String link) { - myLink = link; - } - - public Iterable apply(Iterable contents) { - return new ChainIterable() - .addItem("") - .add(contents).addItem("") - ; - } - } } diff --git a/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java b/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java index 51d595a0d147..6eb014bbc04e 100644 --- a/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java +++ b/python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java @@ -43,7 +43,10 @@ import com.jetbrains.python.psi.resolve.PyResolveContext; import com.jetbrains.python.psi.resolve.QualifiedNameFinder; import com.jetbrains.python.psi.resolve.QualifiedResolveResult; import com.jetbrains.python.psi.resolve.RootVisitor; -import com.jetbrains.python.psi.types.*; +import com.jetbrains.python.psi.types.PyClassType; +import com.jetbrains.python.psi.types.PyDynamicallyEvaluatedType; +import com.jetbrains.python.psi.types.PyType; +import com.jetbrains.python.psi.types.TypeEvalContext; import com.jetbrains.python.pyi.PyiUtil; import com.jetbrains.python.toolbox.ChainIterable; import com.jetbrains.python.toolbox.Maybe; @@ -384,10 +387,8 @@ public class PyDocumentationBuilder { if (inheritedDoc.length() > 1) { myEpilog.addItem(BR).addItem(BR); final String ancestorName = ancestor.getName(); - final String marker = - (pyClass == ancestor) ? PythonDocumentationProvider.LINK_TYPE_CLASS : PythonDocumentationProvider.LINK_TYPE_PARENT; final String ancestorLink = - $().addWith(new LinkWrapper(marker + ancestorName), $(ancestorName)).toString(); + pyClass == ancestor ? PyDocumentationLink.toContainingClass(ancestorName) : PyDocumentationLink.toAncestorOfContainingClass(ancestorName); if (isFromClass) { myEpilog.addItem(PyBundle.message("QDOC.copied.from.class.$0", ancestorLink)); } @@ -487,13 +488,7 @@ public class PyDocumentationBuilder { final String description = typeAndDescr.second; if (type != null) { - final PyType pyType = PyTypeParser.getTypeByName(parameter, type, context); - if (pyType instanceof PyClassType) { - myBody.addItem(": ").addWith(new LinkWrapper(PythonDocumentationProvider.LINK_TYPE_PARAM), $(pyType.getName())); - } - else { - myBody.addItem(": ").addItem(type); - } + myBody.addItem(": ").addItem(PyDocumentationLink.toParameterPossibleClass(type, parameter, context)); } if (description != null) { @@ -523,8 +518,11 @@ public class PyDocumentationBuilder { assert cls != null; final String type = PyUtil.isInstanceAttribute((PyExpression)myElement) ? "Instance attribute " : "Class attribute "; myProlog - .addItem(type).addWith(TagBold, $().addWith(TagCode, $(((PyTargetExpression)myElement).getName()))) - .addItem(" of class ").addWith(PythonDocumentationProvider.LinkMyClass, $().addWith(TagCode, $(cls.getName()))).addItem(BR); + .addItem(type) + .addWith(TagBold, $().addWith(TagCode, $(((PyTargetExpression)myElement).getName()))) + .addItem(" of class ") + .addItem(PyDocumentationLink.toContainingClass(WRAP_IN_CODE.apply(cls.getName()))) + .addItem(BR); final String docString = PyPsiUtils.strValue(getEffectiveDocStringExpression((PyTargetExpression)myElement)); if (docString != null) { diff --git a/python/src/com/jetbrains/python/documentation/PyDocumentationLink.kt b/python/src/com/jetbrains/python/documentation/PyDocumentationLink.kt new file mode 100644 index 000000000000..65ec7ffcd3af --- /dev/null +++ b/python/src/com/jetbrains/python/documentation/PyDocumentationLink.kt @@ -0,0 +1,116 @@ +/* + * Copyright 2000-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 com.jetbrains.python.documentation + +import com.intellij.codeInsight.documentation.DocumentationManagerProtocol +import com.intellij.psi.PsiElement +import com.intellij.psi.util.PsiTreeUtil +import com.jetbrains.python.psi.PyClass +import com.jetbrains.python.psi.PyFunction +import com.jetbrains.python.psi.PyNamedParameter +import com.jetbrains.python.psi.types.PyClassType +import com.jetbrains.python.psi.types.PyTypeParser +import com.jetbrains.python.psi.types.TypeEvalContext + +object PyDocumentationLink { + + private const val LINK_TYPE_CLASS = "#class#" + private const val LINK_TYPE_PARENT = "#parent#" + private const val LINK_TYPE_PARAM = "#param#" + private const val LINK_TYPE_TYPENAME = "#typename#" + + @JvmStatic + fun toContainingClass(content: String?): String { + return "$content" + } + + @JvmStatic + fun toAncestorOfContainingClass(ancestor: String?): String { + return "$ancestor" + } + + @JvmStatic + fun toParameterPossibleClass(type: String, anchor: PsiElement, context: TypeEvalContext): String { + val pyType = PyTypeParser.getTypeByName(anchor, type, context) + return when (pyType) { + is PyClassType -> "$type" + else -> type + } + } + + @JvmStatic + fun toPossibleClass(type: String, anchor: PsiElement, context: TypeEvalContext): String { + val pyType = PyTypeParser.getTypeByName(anchor, type, context) + return when (pyType) { + is PyClassType -> "$type" + else -> type + } + } + + @JvmStatic + fun elementForLink(link: String, element: PsiElement, context: TypeEvalContext): PsiElement? { + return if (link == LINK_TYPE_CLASS) { + containingClass(element) + } + else if (link == LINK_TYPE_PARAM) { + parameterPossibleClass(element, context) + } + else if (link.startsWith(LINK_TYPE_PARENT)) { + ancestorOfContainingClass(element, link.substring(LINK_TYPE_PARENT.length), context) + } + else if (link.startsWith(LINK_TYPE_TYPENAME)) { + possibleClass(link.substring(LINK_TYPE_TYPENAME.length), element, context) + } + else { + null + } + } + + @JvmStatic + private fun containingClass(element: PsiElement): PyClass? { + return when (element) { + is PyClass -> element + is PyFunction -> element.containingClass + else -> PsiTreeUtil.getParentOfType(element, PyClass::class.java) + } + } + + @JvmStatic + private fun ancestorOfContainingClass(element: PsiElement, ancestor: String, context: TypeEvalContext): PyClass? { + val ancestors = containingClass(element)?.getAncestorClasses(context) + return ancestors?.find { it.name == ancestor } + } + + @JvmStatic + private fun parameterPossibleClass(parameter: PsiElement, context: TypeEvalContext): PyClass? { + if (parameter is PyNamedParameter) { + val type = context.getType(parameter) + if (type is PyClassType) { + return type.pyClass + } + } + return null + } + + @JvmStatic + private fun possibleClass(type: String, anchor: PsiElement, context: TypeEvalContext): PyClass? { + val pyType = PyTypeParser.getTypeByName(anchor, type, context) + return when (pyType) { + is PyClassType -> pyType.pyClass + else -> null + } + } +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java index 5ad7eb41ef4e..70fb9c84f1ba 100644 --- a/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java +++ b/python/src/com/jetbrains/python/documentation/PyTypeModelBuilder.java @@ -30,7 +30,6 @@ import org.jetbrains.annotations.Nullable; import java.util.*; -import static com.jetbrains.python.documentation.DocumentationBuilderKit.$; import static com.jetbrains.python.documentation.DocumentationBuilderKit.combUp; /** @@ -421,13 +420,8 @@ public class PyTypeModelBuilder { @Override protected void addType(String name) { - final PyType type = PyTypeParser.getTypeByName(myAnchor, name); - if (type instanceof PyClassType) { - myBody.addWith(new DocumentationBuilderKit.LinkWrapper(PythonDocumentationProvider.LINK_TYPE_TYPENAME + name), $(name)); - } - else { - add(name); - } + final TypeEvalContext context = TypeEvalContext.userInitiated(myAnchor.getProject(), myAnchor.getContainingFile()); + myBody.addItem(PyDocumentationLink.toPossibleClass(name, myAnchor, context)); } } diff --git a/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java b/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java index df8a384da030..231e4f48e359 100644 --- a/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java +++ b/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java @@ -44,15 +44,12 @@ import com.jetbrains.python.documentation.docstrings.DocStringUtil; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.resolve.QualifiedNameFinder; -import com.jetbrains.python.psi.types.PyClassType; import com.jetbrains.python.psi.types.PyType; -import com.jetbrains.python.psi.types.PyTypeParser; import com.jetbrains.python.psi.types.TypeEvalContext; import com.jetbrains.python.toolbox.ChainIterable; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.methods.HeadMethod; import org.apache.commons.httpclient.params.HttpConnectionManagerParams; -import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jsoup.Jsoup; @@ -75,11 +72,6 @@ import static com.jetbrains.python.documentation.DocumentationBuilderKit.*; */ public class PythonDocumentationProvider extends AbstractDocumentationProvider implements ExternalDocumentationProvider { - @NonNls static final String LINK_TYPE_CLASS = "#class#"; - @NonNls static final String LINK_TYPE_PARENT = "#parent#"; - @NonNls static final String LINK_TYPE_PARAM = "#param#"; - @NonNls static final String LINK_TYPE_TYPENAME = "#typename#"; - // provides ctrl+hover info @Override @Nullable @@ -282,7 +274,7 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i final String name = cls.getName(); cat.addItem("class "); if (allowHtml && linkOwnName) { - cat.addWith(LinkMyClass, $(name)); + cat.addItem(PyDocumentationLink.toContainingClass(name)); } else { cat.addWith(nameWrapper, $(name)); @@ -303,7 +295,7 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i isNotFirst = true; } if (allowHtml) { - cat.addWith(new LinkWrapper(LINK_TYPE_PARENT + parentName), $(parentName)); + cat.addItem(PyDocumentationLink.toAncestorOfContainingClass(parentName)); } else { cat.addItem(parentName); @@ -344,30 +336,9 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i @Override public PsiElement getDocumentationElementForLink(PsiManager psiManager, @NotNull String link, @NotNull PsiElement context) { - if (link.equals(LINK_TYPE_CLASS)) { - return inferContainingClassOf(context); - } - else if (link.equals(LINK_TYPE_PARAM)) { - return inferClassOfParameter(context); - } - else if (link.startsWith(LINK_TYPE_PARENT)) { - final PyClass cls = inferContainingClassOf(context); - if (cls != null) { - final String desiredName = link.substring(LINK_TYPE_PARENT.length()); - for (PyClass parent : cls.getAncestorClasses(null)) { - final String parentName = parent.getName(); - if (parentName != null && parentName.equals(desiredName)) return parent; - } - } - } - else if (link.startsWith(LINK_TYPE_TYPENAME)) { - final String typeName = link.substring(LINK_TYPE_TYPENAME.length()); - final PyType type = PyTypeParser.getTypeByName(context, typeName); - if (type instanceof PyClassType) { - return ((PyClassType)type).getPyClass(); - } - } - return null; + return PyDocumentationLink.elementForLink(link, + context, + TypeEvalContext.userInitiated(context.getProject(), context.getContainingFile())); } @Override @@ -585,30 +556,4 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i } return super.getCustomDocumentationElement(editor, file, contextElement); } - - @Nullable - private static PyClass inferContainingClassOf(PsiElement context) { - if (context instanceof PyClass) return (PyClass)context; - if (context instanceof PyFunction) { - return ((PyFunction)context).getContainingClass(); - } - else { - return PsiTreeUtil.getParentOfType(context, PyClass.class); - } - } - - @Nullable - private static PyClass inferClassOfParameter(@NotNull PsiElement context) { - if (context instanceof PyNamedParameter) { - final PyType type = TypeEvalContext.userInitiated(context.getProject(), context.getContainingFile()).getType( - (PyNamedParameter)context); - if (type instanceof PyClassType) { - return ((PyClassType)type).getPyClass(); - } - } - return null; - } - - public static final LinkWrapper LinkMyClass = new LinkWrapper(LINK_TYPE_CLASS); - // link item to containing class } diff --git a/python/testData/quickdoc/ClassUndocumentedConstructor.html b/python/testData/quickdoc/ClassUndocumentedConstructor.html index 7613eb191dd7..c97e9abfd67b 100644 --- a/python/testData/quickdoc/ClassUndocumentedConstructor.html +++ b/python/testData/quickdoc/ClassUndocumentedConstructor.html @@ -1 +1 @@ -class Foo(object)

def __init__(self)


Documentation is missing. The following is copied from class Foo.

Doc of Foo. \ No newline at end of file +class Foo(object)

def __init__(self)


Documentation is missing. The following is copied from class Foo.

Doc of Foo. \ No newline at end of file diff --git a/python/testData/quickdoc/TypeVars.html b/python/testData/quickdoc/TypeVars.html index 344bc1b3b4a7..f98757e009e1 100644 --- a/python/testData/quickdoc/TypeVars.html +++ b/python/testData/quickdoc/TypeVars.html @@ -1 +1 @@ -def f(p1: T1, p2: T2, p3: T3)
Inferred type: (p1: TypeVar('T1', int), p2: TypeVar('T2', int, str), p3: TypeVar('T3', List[bool])) -> None
\ No newline at end of file +def f(p1: T1, p2: T2, p3: T3)
Inferred type: (p1: TypeVar('T1', int), p2: TypeVar('T2', int, str), p3: TypeVar('T3', List[bool])) -> None
\ No newline at end of file