Use documentation from Python stubs for Quick Documentation view (PY-22685)
authorAndrey Vlasovskikh <andrey.vlasovskikh@jetbrains.com>
Thu, 23 Mar 2017 18:21:47 +0000 (21:21 +0300)
committerAndrey Vlasovskikh <andrey.vlasovskikh@jetbrains.com>
Fri, 31 Mar 2017 17:28:39 +0000 (20:28 +0300)
python/src/com/jetbrains/python/documentation/PyDocumentationBuilder.java
python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java
python/testData/quickdoc/BuiltinLen.html [new file with mode: 0644]
python/testData/quickdoc/BuiltinLen.py [new file with mode: 0644]
python/testSrc/com/jetbrains/python/PyQuickDocTest.java

index d0eb4b77dfc11cdc7e996f46a4c2fa685c56e2db..8e64f8d24bceceb45f7e37d433e00d1d0be33045 100644 (file)
@@ -42,6 +42,7 @@ import com.jetbrains.python.psi.resolve.QualifiedResolveResult;
 import com.jetbrains.python.psi.resolve.RootVisitor;
 import com.jetbrains.python.psi.resolve.RootVisitorHost;
 import com.jetbrains.python.psi.types.*;
+import com.jetbrains.python.pyi.PyiUtil;
 import com.jetbrains.python.toolbox.ChainIterable;
 import com.jetbrains.python.toolbox.Maybe;
 import org.jetbrains.annotations.NotNull;
@@ -232,7 +233,7 @@ public class PyDocumentationBuilder {
       final PyCallable getter = property.getGetter().valueOrNull();
       if (getter != null && getter != myElement && getter instanceof PyFunction) {
         // not in getter, getter's doc comment may be useful
-        final PyStringLiteralExpression docstring = ((PyFunction)getter).getDocStringExpression();
+        final PyStringLiteralExpression docstring = getEffectiveDocStringExpression((PyFunction)getter);
         if (docstring != null) {
           myProlog
             .addItem(BR).addWith(TagItalic, $("Copied from getter:")).addItem(BR)
@@ -275,7 +276,7 @@ public class PyDocumentationBuilder {
 
   private void buildFromDocstring(@NotNull final PsiElement elementDefinition, boolean isProperty) {
     PyClass pyClass = null;
-    final PyStringLiteralExpression docStringExpression = ((PyDocStringOwner)elementDefinition).getDocStringExpression();
+    final PyStringLiteralExpression docStringExpression = getEffectiveDocStringExpression((PyDocStringOwner)elementDefinition);
 
     if (elementDefinition instanceof PyClass) {
       pyClass = (PyClass)elementDefinition;
@@ -363,7 +364,7 @@ public class PyDocumentationBuilder {
       PyStringLiteralExpression docstringElement = null;
       PyFunction inherited = null;
       boolean isFromClass = false;
-      if (isConstructor) docstringElement = pyClass.getDocStringExpression();
+      if (isConstructor) docstringElement = getEffectiveDocStringExpression(pyClass);
       if (docstringElement != null) {
         isFromClass = true;
       }
@@ -371,7 +372,7 @@ public class PyDocumentationBuilder {
         inherited = ancestor.findMethodByName(methodName, false, null);
       }
       if (inherited != null) {
-        docstringElement = inherited.getDocStringExpression();
+        docstringElement = getEffectiveDocStringExpression(inherited);
       }
       if (docstringElement != null) {
         final String inheritedDoc = docstringElement.getStringValue();
@@ -414,7 +415,7 @@ public class PyDocumentationBuilder {
       final PyClass objectClass = objectType.getPyClass();
       final PyFunction predefinedMethod = objectClass.findMethodByName(mothodName, false, null);
       if (predefinedMethod != null) {
-        final PyStringLiteralExpression predefinedDocstring = predefinedMethod.getDocStringExpression();
+        final PyStringLiteralExpression predefinedDocstring = getEffectiveDocStringExpression(predefinedMethod);
         final String predefinedDoc = predefinedDocstring != null ? predefinedDocstring.getStringValue() : null;
         if (predefinedDoc != null && predefinedDoc.length() > 1) { // only a real-looking doc string counts
           addFormattedDocString(fun, predefinedDoc, myBody, myBody);
@@ -472,7 +473,7 @@ public class PyDocumentationBuilder {
   private boolean addTypeAndDescriptionFromDocstring(@NotNull final PyNamedParameter parameter) {
     final PyFunction function = PsiTreeUtil.getParentOfType(parameter, PyFunction.class);
     if (function != null) {
-      final String docString = PyPsiUtils.strValue(function.getDocStringExpression());
+      final String docString = PyPsiUtils.strValue(getEffectiveDocStringExpression(function));
       final Pair<String, String> typeAndDescr = getTypeAndDescription(docString, parameter);
 
       final String type = typeAndDescr.first;
@@ -518,7 +519,7 @@ public class PyDocumentationBuilder {
       .addItem(type).addWith(TagBold, $().addWith(TagCode, $(((PyTargetExpression)myElement).getName())))
       .addItem(" of class ").addWith(PythonDocumentationProvider.LinkMyClass, $().addWith(TagCode, $(cls.getName()))).addItem(BR);
 
-    final String docString = ((PyTargetExpression)myElement).getDocStringValue();
+    final String docString = PyPsiUtils.strValue(getEffectiveDocStringExpression((PyTargetExpression)myElement));
     if (docString != null) {
       addFormattedDocString(myElement, docString, myBody, myEpilog);
     }
@@ -583,6 +584,17 @@ public class PyDocumentationBuilder {
     }
   }
 
+  @Nullable
+  static PyStringLiteralExpression getEffectiveDocStringExpression(@NotNull PyDocStringOwner owner) {
+    final PyStringLiteralExpression expression = owner.getDocStringExpression();
+    if (expression != null && StringUtil.isNotEmpty(PyPsiUtils.strValue(expression))) {
+      return expression;
+    }
+    final PsiElement original = PyiUtil.getOriginalElement(owner);
+    final PyDocStringOwner originalOwner = PyUtil.as(original, PyDocStringOwner.class);
+    return originalOwner != null ? originalOwner.getDocStringExpression() : null;
+  }
+
   private static class RootFinder implements RootVisitor {
     private String myResult;
     private final String myPath;
index 52736b30c3afee0e915e595eaa04ac7da1da47fe..e3e2ff29b7416cbd63a7a0c54290d60f008084ab 100644 (file)
@@ -91,7 +91,7 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i
         // It would be nice to have class import info here, but we don't know the ctrl+hovered reference and context
       }
       String summary = "";
-      final PyStringLiteralExpression docStringExpression = func.getDocStringExpression();
+      final PyStringLiteralExpression docStringExpression = PyDocumentationBuilder.getEffectiveDocStringExpression(func);
       if (docStringExpression != null) {
         final StructuredDocString docString = DocStringUtil.parse(docStringExpression.getStringValue(), docStringExpression);
         summary = docString.getSummary();
@@ -102,11 +102,11 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i
     else if (element instanceof PyClass) {
       final PyClass cls = (PyClass)element;
       String summary = "";
-      PyStringLiteralExpression docStringExpression = cls.getDocStringExpression();
+      PyStringLiteralExpression docStringExpression = PyDocumentationBuilder.getEffectiveDocStringExpression(cls);
       if (docStringExpression == null) {
         final PyFunction initOrNew = cls.findInitOrNew(false, null);
         if (initOrNew != null) {
-          docStringExpression = initOrNew.getDocStringExpression();
+          docStringExpression = PyDocumentationBuilder.getEffectiveDocStringExpression(initOrNew);
         }
       }
       if (docStringExpression != null) {
diff --git a/python/testData/quickdoc/BuiltinLen.html b/python/testData/quickdoc/BuiltinLen.html
new file mode 100644 (file)
index 0000000..db51bb7
--- /dev/null
@@ -0,0 +1 @@
+<html><body><code>def <b>len</b>(o:&nbsp;Sized)<br>Inferred&nbsp;type:&nbsp;(o:&nbsp;Sized)&nbsp;-&gt;&nbsp;<a href="psi_element://#typename#int">int</a><br><br><br>len(object)&nbsp;-&gt;&nbsp;integer<br><br>Return&nbsp;the&nbsp;number&nbsp;of&nbsp;items&nbsp;of&nbsp;a&nbsp;sequence&nbsp;or&nbsp;collection.<br></code><br><b>External documentation:</b><br><a href="http://docs.python.org/2.7 Mock SDK/library/__builtin__.html#__builtin__.len">http://docs.python.org/2.7 Mock SDK/library/__builtin__.html#__builtin__.len</a></body></html>
diff --git a/python/testData/quickdoc/BuiltinLen.py b/python/testData/quickdoc/BuiltinLen.py
new file mode 100644 (file)
index 0000000..f66f65f
--- /dev/null
@@ -0,0 +1 @@
+<the_ref>len(0)
index d4f4a6041c9ffaa74ae301937cc7db5a06536ad5..1da1d86c7ecc7efc4d042ec9ec9504f86a376c4e 100644 (file)
@@ -288,4 +288,9 @@ public class PyQuickDocTest extends LightMarkedTestCase {
     myFixture.copyDirectoryToProject("typing", "");
     runWithLanguageLevel(LanguageLevel.PYTHON36, this::checkHTMLOnly);
   }
+
+  // PY-22685
+  public void testBuiltinLen() {
+    checkHTMLOnly();
+  }
 }