PY-29717 Display variable type inline with their name in PEP 526 format

Thanks to it we also started to display types for class and instance
attributes. Also, moved reassignment chain to sections part of Quick
Documentation and display it only when it actually makes sense.
This commit is contained in:
Mikhail Golubev
2018-06-09 15:59:35 +03:00
parent 407ed5f127
commit c3246e9286
7 changed files with 52 additions and 59 deletions
@@ -800,11 +800,11 @@ PARSE.expected.formal.param.name=formal parameter name expected
QDOC.copied.from.$0.$1=<i>Documentation is missing.</i> The following is copied from <code>{0}.{1}</code>.
QDOC.copied.from.builtin=<small>(copied from built-in description)</small>
QDOC.copied.from.class.$0=<i>Documentation is missing.</i> The following is copied from class <code>{0}</code>.
QDOC.assigned.to.$0=Assigned to <code>{0}</code>
QDOC.wrapped.in.$0=Wrapped in <code>{0}</code>
QDOC.module.path.unknown=(Module path is unknown)
QDOC.epydoc.python2.sdk.not.found=You need configured Python 2 SDK to render <a href='http://epydoc.sourceforge.net/'>Epydoc</a> docstrings
QDOC.local.sdk.not.found=You need a configured local Python SDK to render docstrings.
QDOC.assigned.to=Assigned to
runcfg.tests.cant_rerun=Can't rerun tests since test IDs can't be resolved. Try to delete run configuration, and create new one using right click.
@@ -32,13 +32,13 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.QualifiedName;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.FactoryMap;
import com.jetbrains.python.*;
import com.jetbrains.python.console.PyConsoleUtil;
import com.jetbrains.python.documentation.docstrings.DocStringUtil;
import com.jetbrains.python.documentation.docstrings.PyStructuredDocstringFormatter;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.impl.PyCallExpressionHelper;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.resolve.QualifiedNameFinder;
@@ -59,6 +59,7 @@ import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -71,10 +72,12 @@ public class PyDocumentationBuilder {
private ChainIterable<String> myResult;
private final ChainIterable<String> myProlog; // sequence for reassignment info, etc
private final ChainIterable<String> myBody; // sequence for doc string
private final ChainIterable<String> mySections;
private final ChainIterable<String> myEpilog; // sequence for doc "copied from" notices and such
private final Map<String, ChainIterable<String>> mySectionsMap = FactoryMap.create(item -> new ChainIterable<String>());
private static final Pattern ourSpacesPattern = Pattern.compile("^\\s+");
private final ChainIterable<String> myReassignmentChain;
public PyDocumentationBuilder(PsiElement element, PsiElement originalElement) {
myElement = element;
@@ -82,11 +85,12 @@ public class PyDocumentationBuilder {
myResult = new ChainIterable<>();
myProlog = new ChainIterable<>();
myBody = new ChainIterable<>();
mySections = new ChainIterable<>();
myEpilog = new ChainIterable<>();
myResult.add(myProlog).add(myBody).add(myEpilog); // pre-assemble; then add stuff to individual cats as needed
myResult.add(myProlog).add(myBody).add(mySections).add(myEpilog); // pre-assemble; then add stuff to individual cats as needed
myResult = wrapInTag("html", wrapInTag("body", myResult));
myReassignmentChain = new ChainIterable<>();
}
@Nullable
@@ -97,24 +101,12 @@ public class PyDocumentationBuilder {
final PsiElement elementDefinition = resolveToDocStringOwner(context);
final boolean isProperty = buildFromProperty(elementDefinition, outerElement, context);
if (myProlog.isEmpty() && !isProperty && !isAttribute()) {
myProlog.add(myReassignmentChain);
}
if (elementDefinition instanceof PyDocStringOwner) {
buildFromDocstring(((PyDocStringOwner)elementDefinition), isProperty);
}
else if (isAttribute()) {
buildFromAttributeDoc();
}
else if (elementDefinition instanceof PyNamedParameter) {
buildFromParameter(context, outerElement, elementDefinition);
}
else if (elementDefinition != null && outerElement instanceof PyReferenceExpression) {
myBody.addItem(combUp("\nInferred type: "));
PythonDocumentationProvider
.describeTypeWithLinks(context.getType((PyReferenceExpression)outerElement), context, outerElement, myBody);
}
if (elementDefinition != null) {
final ASTNode node = elementDefinition.getNode();
@@ -131,6 +123,19 @@ public class PyDocumentationBuilder {
buildForKeyword(documentationName);
}
}
if (!mySectionsMap.isEmpty()) {
mySections.addItem(DocumentationMarkup.SECTIONS_START);
for (Map.Entry<String, ChainIterable<String>> entry : mySectionsMap.entrySet()) {
mySections.addItem(DocumentationMarkup.SECTION_HEADER_START);
mySections.addItem(entry.getKey());
mySections.addItem(DocumentationMarkup.SECTION_SEPARATOR);
mySections.add(entry.getValue());
mySections.addItem(DocumentationMarkup.SECTION_END);
}
mySections.addItem(DocumentationMarkup.SECTIONS_END);
}
final String url = PythonDocumentationProvider.getUrlFor(myElement, myOriginalElement, false);
if (url != null) {
myEpilog.addItem(BR);
@@ -321,6 +326,19 @@ public class PyDocumentationBuilder {
else if (elementDefinition instanceof PyFile) {
addModulePath((PyFile)elementDefinition);
}
else if (elementDefinition instanceof PyTargetExpression) {
PyTargetExpression target = (PyTargetExpression)elementDefinition;
if (isAttribute()) {
final String type = PyUtil.isInstanceAttribute(target) ? "Instance attribute " : "Class attribute ";
myProlog
.addItem(type)
.addWith(TagBold, $().addWith(TagCode, $(elementDefinition.getName())))
.addItem(" of class ")
.addItem(PyDocumentationLink.toContainingClass(WRAP_IN_CODE.apply(target.getContainingClass().getName())))
.addItem(BR);
}
myBody.add(PythonDocumentationProvider.describeTarget(target, context));
}
myBody.addItem(DocumentationMarkup.DEFINITION_END);
if (content != null && !content.isEmpty()) {
@@ -337,32 +355,15 @@ public class PyDocumentationBuilder {
@Nullable
private PsiElement resolveToDocStringOwner(@NotNull TypeEvalContext context) {
// here the ^Q target is already resolved; the resolved element may point to intermediate assignments
if (myElement instanceof PyTargetExpression) {
final String targetName = myElement.getText();
myReassignmentChain.addWith(TagSmall, $(PyBundle.message("QDOC.assigned.to.$0", targetName)).addItem(BR));
if (myElement instanceof PyTargetExpression && ((PyTargetExpression)myElement).getDocStringValue() == null) {
final PyExpression assignedValue = ((PyTargetExpression)myElement).findAssignedValue();
if (assignedValue instanceof PyReferenceExpression) {
final PsiElement resolved = resolveWithoutImplicits((PyReferenceExpression)assignedValue, context);
if (resolved != null) {
if (resolved instanceof PyDocStringOwner) {
mySectionsMap.get(PyBundle.message("QDOC.assigned.to")).addWith(TagCode, $(((PyTargetExpression)myElement).getName()));
return resolved;
}
}
return assignedValue;
}
if (myElement instanceof PyReferenceExpression) {
myReassignmentChain.addWith(TagSmall, $(PyBundle.message("QDOC.assigned.to.$0", myElement.getText())).addItem(BR));
return resolveWithoutImplicits((PyReferenceExpression)myElement, context);
}
// it may be a call to a standard wrapper
if (myElement instanceof PyCallExpression) {
final PyCallExpression call = (PyCallExpression)myElement;
final Pair<String, PyFunction> wrapInfo = PyCallExpressionHelper.interpretAsModifierWrappingCall(call);
if (wrapInfo != null) {
final String wrapperName = wrapInfo.getFirst();
final PyFunction wrappedFunction = wrapInfo.getSecond();
myReassignmentChain.addWith(TagSmall, $(PyBundle.message("QDOC.wrapped.in.$0", wrapperName)).addItem(BR));
return wrappedFunction;
}
}
return myElement;
}
@@ -533,23 +534,6 @@ public class PyDocumentationBuilder {
return Pair.create(type, desc);
}
private void buildFromAttributeDoc() {
final PyClass cls = PsiTreeUtil.getParentOfType(myElement, PyClass.class);
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 ")
.addItem(PyDocumentationLink.toContainingClass(WRAP_IN_CODE.apply(cls.getName())))
.addItem(BR);
final String docString = PyPsiUtils.strValue(getEffectiveDocStringExpression((PyTargetExpression)myElement));
if (docString != null) {
myBody.add(formatDocString(myElement, docString));
}
}
public static String[] removeCommonIndentation(@NotNull final String docstring) {
// detect common indentation
final String[] lines = LineTokenizer.tokenize(docstring, false);
@@ -169,6 +169,15 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i
//}
}
@NotNull
static ChainIterable<String> describeTarget(@NotNull PyTargetExpression target, @NotNull TypeEvalContext context) {
ChainIterable<String> result = new ChainIterable<>();
result.addItem(target.getName());
result.addItem(": ");
describeTypeWithLinks(context.getType(target), context, target, result);
return result;
}
@NotNull
private static ChainIterable<String> describeFunctionWithTypes(@NotNull PyFunction function,
@NotNull Function<String, String> escaper,
+1 -1
View File
@@ -1 +1 @@
<html><body>Class attribute <b><code>the_attr</code></b> of class <a href="psi_element://#class#"><code>C</code></a><br>The&nbsp;documentation&nbsp;for&nbsp;the&nbsp;attribute.&nbsp;</body></html>
<html><body>Class attribute <b><code>the_attr</code></b> of class <a href="psi_element://#class#"><code>C</code></a><br><div class='definition'><pre>the_attr: <a href="psi_element://#typename#str">str</a></pre></div><div class='content'>The&nbsp;documentation&nbsp;for&nbsp;the&nbsp;attribute.&nbsp;</div></body></html>
+1 -1
View File
@@ -1 +1 @@
<html><body>Instance attribute <b><code>foo</code></b> of class <a href="psi_element://#class#"><code>C</code></a><br>The&nbsp;docstring&nbsp;for&nbsp;the&nbsp;attribute&nbsp;foo.&nbsp;</body></html>
<html><body>Instance attribute <b><code>foo</code></b> of class <a href="psi_element://#class#"><code>C</code></a><br><div class='definition'><pre>foo: <a href="psi_element://#typename#str">str</a></pre></div><div class='content'>The&nbsp;docstring&nbsp;for&nbsp;the&nbsp;attribute&nbsp;foo.&nbsp;</div></body></html>
@@ -1 +1 @@
<html><body><small>Assigned to <code>c1</code><br></small><div class='definition'><pre><small>class <a href="psi_element://#class#">list</a>(<a href="psi_element://#typename#typing.MutableSequence">MutableSequence</a>[_T], Generic[_T])</small><br><br>def&nbsp;<b>count</b>(self:&nbsp;<a href="psi_element://#typename#list">list</a>,&nbsp;object:&nbsp;_T)&nbsp;-&gt;&nbsp;<a href="psi_element://#typename#int">int</a></pre></div><div class='content'>L.count(value)&nbsp;-&gt;&nbsp;integer&nbsp;--&nbsp;return&nbsp;number&nbsp;of&nbsp;occurrences&nbsp;of&nbsp;value&nbsp;</div></body></html>
<html><body><div class='definition'><pre><small>class <a href="psi_element://#class#">list</a>(<a href="psi_element://#typename#typing.MutableSequence">MutableSequence</a>[_T], Generic[_T])</small><br><br>def&nbsp;<b>count</b>(self:&nbsp;<a href="psi_element://#typename#list">list</a>,&nbsp;object:&nbsp;_T)&nbsp;-&gt;&nbsp;<a href="psi_element://#typename#int">int</a></pre></div><div class='content'>L.count(value)&nbsp;-&gt;&nbsp;integer&nbsp;--&nbsp;return&nbsp;number&nbsp;of&nbsp;occurrences&nbsp;of&nbsp;value&nbsp;</div><table class='sections'><tr><td valign='top' class='section'><p>Assigned to</td><td valign='top'><code>c1</code></td></table></body></html>
+1 -1
View File
@@ -1 +1 @@
<html><body><small>Assigned to <code>y</code><br></small><code><br>Inferred&nbsp;type:&nbsp;Union[<a href="psi_element://#typename#int">int</a>,&nbsp;<a href="psi_element://#typename#str">str</a>]</code></body></html>
<html><body><div class='definition'><pre>y: <a href="psi_element://#typename#int">int</a></pre></div></body></html>