PY-33341, PY-56416, PY-28900: Render attributes and init params description in the class documentation

PY-33341: Now we render the “Attributes” section in the class documentation, it also allows to describe inherited attributes. (Previously we didn’t render it at all)
PY-56416: In the attribute documentation popup we are able to render attribute description from class docstring. (Previously we took documentation only explicitly from attribute one-line docstring)
PY-33341: We render the “Parameters” section in the class documentation for init parameters, described in the class docstring. (Previously user couldn't use class docstring to describe init parameters)
PY-28900: For the init parameter documentation we take the description from the class docstring if init doesn't have its own docstring. (Previously we took parameter description only from init docstring)

GitOrigin-RevId: d67bf49c72cf7a3634805a6e310c943f1ea848d1
This commit is contained in:
Irina.Fediaeva
2022-08-26 14:29:59 +03:00
committed by intellij-monorepo-bot
parent 6e8d9adaf8
commit cb8edc622b
52 changed files with 584 additions and 77 deletions

View File

@@ -95,12 +95,20 @@ public interface StructuredDocString {
@Nullable
String getRaisedExceptionDescription(@Nullable String exceptionName); // for formatter
// getAttributes
// getAttributeSubstrings
// getAttributeType(name)
// getAttributeTypeSubstring(name)
@Nullable
String getAttributeDescription(); // for formatter
@Nullable
String getAttributeDescription(@Nullable String name);
@NotNull
List<String> getAttributes();
@NotNull
List<Substring> getAttributeSubstrings();
// getAttributeType(name)
// getAttributeTypeSubstring(name)
// Tags related methods
}

View File

@@ -244,6 +244,7 @@ QDOC.raises=Raises:
QDOC.keyword.args=Keyword args:
QDOC.returns=Returns:
QDOC.params=Params:
QDOC.attributes=Attributes:
### Formatter
formatter.panel.dict.alignment.do.not.align=Do not align

View File

@@ -7,6 +7,7 @@ import com.intellij.lang.documentation.DocumentationMarkup;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.LineTokenizer;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.util.text.Strings;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
@@ -20,7 +21,6 @@ import com.jetbrains.python.*;
import com.jetbrains.python.documentation.docstrings.DocStringUtil;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.resolve.QualifiedNameFinder;
import com.jetbrains.python.psi.resolve.QualifiedResolveResult;
@@ -96,7 +96,8 @@ public class PyDocumentationBuilder {
if (!mySectionsMap.isEmpty()) {
mySections.addItem(DocumentationMarkup.SECTIONS_START);
final List<String> firstSections = Lists.newArrayList(PyPsiBundle.message("QDOC.params"),
final List<String> firstSections = Lists.newArrayList(PyPsiBundle.message("QDOC.attributes"),
PyPsiBundle.message("QDOC.params"),
PyPsiBundle.message("QDOC.keyword.args"),
PyPsiBundle.message("QDOC.returns"),
PyPsiBundle.message("QDOC.raises"));
@@ -207,7 +208,11 @@ public class PyDocumentationBuilder {
}
if (func != null) {
final PyStringLiteralExpression docString = getEffectiveDocStringExpression(func);
final PyClass containingClass = func.getContainingClass();
final PyStringLiteralExpression functionDocstring = getEffectiveDocStringExpression(func);
final PyStringLiteralExpression docString = functionDocstring == null && containingClass != null ?
addFunctionInheritedDocString(func, containingClass) :
functionDocstring;
if (docString != null) {
final StructuredDocString structuredDocString = DocStringUtil.parse(docString.getStringValue());
final String description = structuredDocString.getParamDescription(parameter.getName());
@@ -328,20 +333,15 @@ public class PyDocumentationBuilder {
}
}
if (elementDefinition instanceof PyClass) {
final PyClass pyClass = (PyClass)elementDefinition;
if (elementDefinition instanceof PyClass pyClass) {
myBody.add(PythonDocumentationProvider.describeDecorators(pyClass, WRAP_IN_ITALIC, ESCAPE_AND_SAVE_NEW_LINES_AND_SPACES, BR, BR));
myBody
.add(PythonDocumentationProvider.describeClass(pyClass, WRAP_IN_BOLD, ESCAPE_AND_SAVE_NEW_LINES_AND_SPACES, false, true, myContext));
if (docStringExpression == null) {
final PyFunction constructor = pyClass.findMethodByName(PyNames.INIT, false, myContext);
if (constructor != null) {
docStringExpression = constructor.getDocStringExpression();
}
}
myBody.add(
PythonDocumentationProvider.describeClass(pyClass, WRAP_IN_BOLD, ESCAPE_AND_SAVE_NEW_LINES_AND_SPACES, false, true, myContext));
docStringExpression = addClassDocumentation(docStringExpression, pyClass);
}
else if (elementDefinition instanceof PyFunction) {
final PyFunction pyFunction = (PyFunction)elementDefinition;
else if (elementDefinition instanceof PyFunction pyFunction) {
myBody.add(PythonDocumentationProvider.describeDecorators(pyFunction, WRAP_IN_ITALIC, ESCAPE_AND_SAVE_NEW_LINES_AND_SPACES, BR, BR));
myBody.add(PythonDocumentationProvider.describeFunction(pyFunction, myContext, false));
final PyClass pyClass = pyFunction.getContainingClass();
if (!isProperty && pyClass != null) {
final String link = getLinkToClass(pyClass, true);
@@ -349,30 +349,13 @@ public class PyDocumentationBuilder {
myProlog.addItem(link);
}
}
myBody.add(PythonDocumentationProvider.describeDecorators(pyFunction, WRAP_IN_ITALIC, ESCAPE_AND_SAVE_NEW_LINES_AND_SPACES, BR, BR));
myBody.add(PythonDocumentationProvider.describeFunction(pyFunction, myContext, false));
if (docStringExpression == null && pyClass != null && !isProperty) {
docStringExpression = addInheritedDocString(pyFunction, pyClass);
}
if (docStringExpression != null) {
addFunctionSpecificSections(docStringExpression, pyFunction);
}
docStringExpression = addFunctionDocumentation(docStringExpression, pyFunction, pyClass, isProperty);
}
else if (elementDefinition instanceof PyFile) {
addModulePath((PyFile)elementDefinition);
else if (elementDefinition instanceof PyFile pyFile) {
addModulePath(pyFile);
}
else if (elementDefinition instanceof PyTargetExpression) {
final PyTargetExpression target = (PyTargetExpression)elementDefinition;
if (isAttribute() && !isProperty) {
@SuppressWarnings("ConstantConditions") final String link = getLinkToClass(target.getContainingClass(), true);
if (link != null) {
myProlog.addItem(PyUtil.isInstanceAttribute(target) ? "Instance attribute " : "Class attribute ")
.addWith(TagBold, $(elementDefinition.getName()))
.addItem(" of ")
.addItem(link);
}
}
myBody.add(PythonDocumentationProvider.describeTarget(target, myContext));
else if (elementDefinition instanceof PyTargetExpression target) {
addTargetDocumentation(target, isProperty);
}
if (docStringExpression != null && !isProperty) {
@@ -380,6 +363,91 @@ public class PyDocumentationBuilder {
}
}
private void addTargetDocumentation(@NotNull PyTargetExpression target, boolean isProperty) {
if (isAttribute() && !isProperty) {
final PyClass containingClass = target.getContainingClass();
@SuppressWarnings("ConstantConditions") final String link = getLinkToClass(containingClass, true);
if (link != null) {
myProlog.addItem(PyUtil.isInstanceAttribute(target) ? "Instance attribute " : "Class attribute ")
.addWith(TagBold, $(target.getName()))
.addItem(" of ")
.addItem(link);
}
// if there is no separate doc for attribute we will try to take it from class doc
if (getEffectiveDocStringExpression(target) == null) {
final PyStringLiteralExpression docString = getEffectiveDocStringExpression(containingClass);
if (docString != null) {
final StructuredDocString structuredDocString = DocStringUtil.parse(docString.getStringValue());
final String description = structuredDocString.getAttributeDescription(target.getName());
if (StringUtil.isNotEmpty(description)) {
myContent.add($(description));
}
}
}
}
myBody.add(PythonDocumentationProvider.describeTarget(target, myContext));
}
@Nullable
private PyStringLiteralExpression addFunctionDocumentation(@Nullable PyStringLiteralExpression docStringExpression,
@NotNull PyFunction pyFunction,
@Nullable PyClass pyClass,
boolean isProperty) {
// if we have containing class, but don't have a function doc
if (docStringExpression == null && pyClass != null) {
if (!isProperty) {
// add docstring from the parent class or function
docStringExpression = addFunctionInheritedDocString(pyFunction, pyClass);
}
if (PyUtil.isInitOrNewMethod(pyFunction)) {
final PyStringLiteralExpression classDocstring = getEffectiveDocStringExpression(pyClass);
if (classDocstring != null) {
// we should add attributes section from class doc because it won't be added with function sections in the end
addAttributesSection(classDocstring, pyClass);
}
}
}
if (docStringExpression != null) {
addFunctionSpecificSections(docStringExpression, pyFunction);
}
return docStringExpression;
}
@Nullable
private PyStringLiteralExpression addClassDocumentation(@Nullable PyStringLiteralExpression docStringExpression,
@NotNull PyClass pyClass) {
final PyFunction init = pyClass.findMethodByName(PyNames.INIT, false, myContext);
if (docStringExpression != null) {
addAttributesSection(docStringExpression, pyClass);
if (init != null) {
// add init parameters described in the class doc
addFunctionSpecificSections(docStringExpression, init);
}
return docStringExpression;
}
// if class doesn't have any doc add init doc without other sections
if (init != null) {
return getEffectiveDocStringExpression(init);
}
return null;
}
private void addAttributesSection(@NotNull PyStringLiteralExpression docstring, @NotNull PyClass cls) {
final StructuredDocString structured = DocStringUtil.parseDocString(docstring);
final List<String> documentedAttributes = structured.getAttributes();
final String allAttributes = StreamEx.of(documentedAttributes)
.map(name -> {
final String description = structured.getAttributeDescription(name);
return "<p><code>" + name + "</code> &ndash; " + Strings.notNullize(description) + "</p>";
})
.joining();
if (!allAttributes.isEmpty()) {
mySectionsMap.get(PyPsiBundle.message("QDOC.attributes")).addItem(allAttributes);
}
}
private void addFunctionSpecificSections(@NotNull PyStringLiteralExpression docstring, @NotNull PyFunction function) {
final StructuredDocString structured = DocStringUtil.parseDocString(docstring);
@@ -387,13 +455,12 @@ public class PyDocumentationBuilder {
final List<String> actualNames = ContainerUtil.mapNotNull(parameters, PyCallableParameter::getName);
// Retain the actual order of parameters
final String paramList = StreamEx.of(actualNames)
.filter(name -> structured.getParamDescription(name) != null)
.map(name -> {
final String description = structured.getParamDescription(name);
return "<p><code>" + name + "</code> &ndash; " + description + "</p>";
})
.joining();
.filter(name -> structured.getParamDescription(name) != null)
.map(name -> {
final String description = structured.getParamDescription(name);
return "<p><code>" + name + "</code> &ndash; " + Strings.notNullize(description) + "</p>";
})
.joining();
if (!paramList.isEmpty()) {
mySectionsMap.get(PyPsiBundle.message("QDOC.params")).addItem(paramList);
@@ -404,26 +471,27 @@ public class PyDocumentationBuilder {
allKeywordArgs.retainAll(new HashSet<>(actualNames));
}
final String keywordArgsList = StreamEx.of(allKeywordArgs)
.map(name -> {
final String description = structured.getKeywordArgumentDescription(name);
return "<p><code>" + name + "</code> &ndash; " + StringUtil.notNullize(description) + "</p>";
})
.joining();
.map(name -> {
final String description = structured.getKeywordArgumentDescription(name);
return "<p><code>" + name + "</code> &ndash; " + StringUtil.notNullize(description) + "</p>";
})
.joining();
if (!keywordArgsList.isEmpty()) {
mySectionsMap.get(PyPsiBundle.message("QDOC.keyword.args")).addItem(keywordArgsList);
}
final String returnDescription = structured.getReturnDescription();
if (returnDescription != null) {
mySectionsMap.get(PyPsiBundle.message("QDOC.returns")).addItem(returnDescription);
if (returnDescription != null && !mySectionsMap.containsKey(PyPsiBundle.message("QDOC.returns"))) {
mySectionsMap.get(PyPsiBundle.message("QDOC.returns")).addItem(Strings.notNullize(returnDescription));
}
final String exceptionList = StreamEx.of(structured.getRaisedExceptions())
.map(name -> {
final String description = structured.getRaisedExceptionDescription(name);
return "<p><code>" + name + "</code>" +(StringUtil.isNotEmpty(description) ? " &ndash; " + description : "") + "</p>";
})
.joining();
.map(name -> {
final String description = structured.getRaisedExceptionDescription(name);
return "<p><code>" + name + "</code>" +
(StringUtil.isNotEmpty(description) ? " &ndash; " + description : "") + "</p>";
})
.joining();
if (!exceptionList.isEmpty()) {
mySectionsMap.get(PyPsiBundle.message("QDOC.raises")).addItem(exceptionList);
@@ -465,7 +533,7 @@ public class PyDocumentationBuilder {
}
@Nullable
private PyStringLiteralExpression addInheritedDocString(@NotNull PyFunction pyFunction, @NotNull PyClass pyClass) {
private PyStringLiteralExpression addFunctionInheritedDocString(@NotNull PyFunction pyFunction, @NotNull PyClass pyClass) {
final String methodName = pyFunction.getName();
if (methodName == null) {
return null;
@@ -632,7 +700,7 @@ public class PyDocumentationBuilder {
@Nullable
static PyStringLiteralExpression getEffectiveDocStringExpression(@NotNull PyDocStringOwner owner) {
final PyStringLiteralExpression expression = owner.getDocStringExpression();
if (expression != null && StringUtil.isNotEmpty(PyPsiUtils.strValue(expression))) {
if (expression != null) {
return expression;
}
final PsiElement original = PyiUtil.getOriginalElement(owner);

View File

@@ -173,10 +173,11 @@ public final class DocStringUtil {
public static boolean isLikeSphinxDocString(@NotNull String text) {
return text.contains(":param ") ||
text.contains(":key ") || text.contains(":keyword ") ||
text.contains(":key ") || text.contains(":keyword ") ||
text.contains(":return:") || text.contains(":returns:") ||
text.contains(":raise ") || text.contains(":raises ") || text.contains(":except ") || text.contains(":exception ") ||
text.contains(":rtype") || text.contains(":type");
text.contains(":rtype") || text.contains(":type") ||
text.contains(":var") || text.contains(":ivar") || text.contains(":cvar");
}
public static boolean isLikeEpydocDocString(@NotNull String text) {
@@ -184,7 +185,8 @@ public final class DocStringUtil {
text.contains("@kwarg ") || text.contains("@keyword ") || text.contains("@kwparam ") ||
text.contains("@raise ") || text.contains("@raises ") || text.contains("@except ") || text.contains("@exception ") ||
text.contains("@return:") ||
text.contains("@rtype") || text.contains("@type");
text.contains("@rtype") || text.contains("@type") ||
text.contains("@var") || text.contains("@ivar") || text.contains("@cvar");
}
public static boolean isLikeGoogleDocString(@NotNull String text) {

View File

@@ -42,12 +42,6 @@ public class EpydocString extends TagBasedDocString {
return html;
}
@NotNull
@Override
public List<String> getParameters() {
return toUniqueStrings(getParameterSubstrings());
}
@NotNull
@Override
public List<String> getKeywordArguments() {
@@ -289,4 +283,10 @@ public class EpydocString extends TagBasedDocString {
public Substring getParamTypeSubstring(@Nullable String paramName) {
return paramName == null ? getTagValue("type") : getTagValue("type", paramName);
}
@Nullable
@Override
public String getAttributeDescription(@Nullable String attrName) {
return attrName != null ? inlineMarkupToHTML(getTagValue(VARIABLE_TAGS, attrName)) : null;
}
}

View File

@@ -151,4 +151,20 @@ public class PlainDocString extends DocStringLineParser implements StructuredDoc
public String getAttributeDescription() {
return null;
}
@Nullable
@Override
public String getAttributeDescription(@Nullable String name) {
return null;
}
@Override
public @NotNull List<String> getAttributes() {
return Collections.emptyList();
}
@Override
public @NotNull List<Substring> getAttributeSubstrings() {
return Collections.emptyList();
}
}

View File

@@ -520,6 +520,39 @@ public abstract class SectionBasedDocString extends DocStringLineParser implemen
return null;
}
@Nullable
@Override
public String getAttributeDescription(@Nullable String name) {
if (name != null) {
final SectionField field = getFirstFieldForAttribute(name);
if (field != null) {
return field.getDescription();
}
}
return null;
}
@Nullable
public SectionField getFirstFieldForAttribute(@NotNull String name) {
return ContainerUtil.find(getAttributeFields(), field -> field.getNames().contains(name));
}
@NotNull
@Override
public List<String> getAttributes() {
return ContainerUtil.map(getAttributeSubstrings(), substring -> substring.toString());
}
@NotNull
@Override
public List<Substring> getAttributeSubstrings() {
final List<Substring> result = new ArrayList<>();
for (SectionField field : getAttributeFields()) {
ContainerUtil.addAllNotNull(result, field.getNamesAsSubstrings());
}
return result;
}
@NotNull
protected static Substring cleanUpName(@NotNull Substring name) {
int firstNotStar = 0;

View File

@@ -38,12 +38,6 @@ public class SphinxDocString extends TagBasedDocString {
return s != null ? s.concatTrimmedLines(" ") : null;
}
@NotNull
@Override
public List<String> getParameters() {
return toUniqueStrings(getParameterSubstrings());
}
@NotNull
@Override
public List<String> getKeywordArguments() {
@@ -126,4 +120,10 @@ public class SphinxDocString extends TagBasedDocString {
public String getDescription() {
return myDescription.replaceAll("\n", "<br/>");
}
@Nullable
@Override
public String getAttributeDescription(@Nullable String attrName) {
return attrName != null ? concatTrimmedLines(getTagValue(VARIABLE_TAGS, attrName)) : null;
}
}

View File

@@ -201,6 +201,12 @@ public abstract class TagBasedDocString extends DocStringLineParser implements S
return result;
}
@NotNull
@Override
public List<String> getParameters() {
return toUniqueStrings(getParameterSubstrings());
}
@NotNull
@Override
public List<Substring> getParameterSubstrings() {
@@ -210,6 +216,16 @@ public abstract class TagBasedDocString extends DocStringLineParser implements S
return results;
}
@Override
public @NotNull List<String> getAttributes() {
return toUniqueStrings(getAttributeSubstrings());
}
@Override
public @NotNull List<Substring> getAttributeSubstrings() {
return new ArrayList<>(getTagArguments(VARIABLE_TAGS));
}
@Override
protected boolean isBlockEnd(int lineNum) {
return getLine(lineNum).trimLeft().startsWith(myTagPrefix);

View File

@@ -0,0 +1 @@
<html><body><div class='definition'><pre><a href="psi_element://#module#ArgsDescriptionFromClassAndInitNotMixedGoogle">ArgsDescriptionFromClassAndInitNotMixedGoogle</a><br>class&nbsp;<b>MyClass</b></pre></div><div class='content'>Unittest placeholder</div><table class='sections'><tr><td valign='top' class='section'><p>Params:</td><td valign='top'><p><code>param1</code> &ndash; param1 description</p><p><code>param2</code> &ndash; param2 description</p></td><tr><td valign='top' class='section'><p>Keyword args:</td><td valign='top'><p><code>kw</code> &ndash; kw description</p></td></table></body></html>

View File

@@ -0,0 +1,17 @@
class My<the_ref>Class:
"""
Args:
param1: param1 description
param2: param2 description
Keyword Args:
kw: kw description
"""
def __init__(self, param1, param2, **kw):
"""
Args:
param1: param1 description from init
param2: param2 description from init
Keyword Args:
kw: kw description from init
"""
pass

View File

@@ -0,0 +1,4 @@
<html><body><div class='definition'><pre><a href="psi_element://#typename#ArgsDescriptionFromClassDocstringGoogle.Parent">ArgsDescriptionFromClassDocstringGoogle.Parent</a><br>def <b>__init__</b>(self,
p1: Any,
p2: Any,
**kwargs: Any) -&gt; None</pre></div><div class='content'>Unittest placeholder</div><table class='sections'><tr><td valign='top' class='section'><p>Params:</td><td valign='top'><p><code>p1</code> &ndash; p1 doc from class</p><p><code>p2</code> &ndash; p2 doc from class</p></td><tr><td valign='top' class='section'><p>Keyword args:</td><td valign='top'><p><code>k</code> &ndash; k doc from class</p></td><tr><td valign='top' class='section'><p>Documentation is copied from:</td><td valign='top'><code><a href="psi_element://#typename#ArgsDescriptionFromClassDocstringGoogle.Parent">Parent</a></code></td></table></body></html>

View File

@@ -0,0 +1,11 @@
class Parent:
"""
Args:
p1: p1 doc from class
p2: p2 doc from class
Keyword Args:
k: k doc from class
"""
def __in<the_ref>it__(self, p1, p2, **kwargs):
pass

View File

@@ -0,0 +1 @@
<html><body><div class='definition'><pre><a href="psi_element://#module#ArgsFromInitNotTakenInClassDocstringGoogle">ArgsFromInitNotTakenInClassDocstringGoogle</a><br>class&nbsp;<b>Parent</b></pre></div><div class='content'>Unittest placeholder</div></body></html>

View File

@@ -0,0 +1,10 @@
class Par<the_ref>ent:
def __init__(self, p1, p2, **kwargs):
"""
Args:
p1: p1 doc from class
p2: p2 doc from class
Keyword Args:
k: k doc from class
"""
pass

View File

@@ -0,0 +1 @@
<html><body><div class='definition'><pre><a href="psi_element://#module#ArgsInClassDocGoogle">ArgsInClassDocGoogle</a><br>class&nbsp;<b>Parent</b></pre></div><div class='content'>Unittest placeholder</div><table class='sections'><tr><td valign='top' class='section'><p>Params:</td><td valign='top'><p><code>p1</code> &ndash; p1 doc from class</p><p><code>p2</code> &ndash; p2 doc from class</p></td><tr><td valign='top' class='section'><p>Keyword args:</td><td valign='top'><p><code>k</code> &ndash; k doc from class</p></td></table></body></html>

View File

@@ -0,0 +1,11 @@
class Par<the_ref>ent:
"""
Args:
p1: p1 doc from class
p2: p2 doc from class
Keyword Args:
k: k doc from class
"""
def __init__(self, p1, p2, **kwargs):
pass

View File

@@ -0,0 +1 @@
<html><body><div class='definition'><pre><a href="psi_element://#module#AttributeDescriptionEmptyGoogle">AttributeDescriptionEmptyGoogle</a><br>class&nbsp;<b>MyClass</b></pre></div><div class='content'>Unittest placeholder</div><table class='sections'><tr><td valign='top' class='section'><p>Attributes:</td><td valign='top'><p><code>attr</code> &ndash; </p></td></table></body></html>

View File

@@ -0,0 +1,7 @@
class My<the_ref>Class:
"""
Attributes:
attr (int) :
"""
attr = 1

View File

@@ -0,0 +1 @@
<html><body><div class='definition'><pre>Class attribute <b>attr</b> of <a href="psi_element://#typename#AttributeDocsNotMixed.MyClass">AttributeDocsNotMixed.MyClass</a><br>attr: <a href="psi_element://#typename#int">int</a> = 1</pre></div><div class='content'>Docstring&nbsp;from&nbsp;attr</div></body></html>

View File

@@ -0,0 +1,9 @@
class MyClass:
"""This is the class docstring.
Attributes:
attr: Docstring from class
"""
a<the_ref>ttr = 1
"""Docstring from attr"""

View File

@@ -0,0 +1 @@
<html><body><div class='definition'><pre><a href="psi_element://#typename#AttributesDescriptionFromClassDocstringGoogle.Parent">AttributesDescriptionFromClassDocstringGoogle.Parent</a><br>def <b>__init__</b>(self) -&gt; None</pre></div><div class='content'>Unittest placeholder</div><table class='sections'><tr><td valign='top' class='section'><p>Attributes:</td><td valign='top'><p><code>a1</code> &ndash; a1 doc from parent</p><p><code>a2</code> &ndash; a2 doc from parent</p></td><tr><td valign='top' class='section'><p>Documentation is copied from:</td><td valign='top'><code><a href="psi_element://#typename#AttributesDescriptionFromClassDocstringGoogle.Parent">Parent</a></code></td></table></body></html>

View File

@@ -0,0 +1,11 @@
class Parent:
"""
Attributes:
a1: a1 doc from parent
a2: a2 doc from parent
"""
a1 = 0
def __in<the_ref>it__(self):
self.a2 = 0

View File

@@ -0,0 +1 @@
<html><body><div class='definition'><pre><a href="psi_element://#module#AttributesOrderGoogle">AttributesOrderGoogle</a><br>class&nbsp;<b>Parent</b></pre></div><div class='content'>Unittest placeholder</div><table class='sections'><tr><td valign='top' class='section'><p>Attributes:</td><td valign='top'><p><code>a1</code> &ndash; a1 doc</p><p><code>a2</code> &ndash; a2 doc</p><p><code>a3</code> &ndash; a3 doc</p><p><code>a4</code> &ndash; a4 doc</p></td></table></body></html>

View File

@@ -0,0 +1,15 @@
class Par<the_ref>ent:
"""
Attributes:
a1: a1 doc
a2: a2 doc
a3: a3 doc
a4: a4 doc
"""
a4 = 0
a3 = 0
def __init__(self):
self.a2 = 0
self.a1 = 0

View File

@@ -0,0 +1 @@
<html><body><div class='definition'><pre><a href="psi_element://#module#ClassAndInstanceAttributesInOneSectionEpydoc">ClassAndInstanceAttributesInOneSectionEpydoc</a><br>class&nbsp;<b>MyClass</b></pre></div><div class='content'>Unittest placeholder</div><table class='sections'><tr><td valign='top' class='section'><p>Attributes:</td><td valign='top'><p><code>msg</code> &ndash; Message</p><p><code>code</code> &ndash; Code</p><p><code>state</code> &ndash; State</p></td></table></body></html>

View File

@@ -0,0 +1,12 @@
class My<the_ref>Class:
"""
@ivar msg: Message
@cvar code: Code
@var state: State
"""
code = 404
def __init__(self):
self.msg = 'test'
self.state = 0

View File

@@ -0,0 +1 @@
<html><body><div class='definition'><pre><a href="psi_element://#module#ClassAndInstanceAttributesInOneSectionGoogle">ClassAndInstanceAttributesInOneSectionGoogle</a><br>class&nbsp;<b>MyClass</b></pre></div><div class='content'>Unittest placeholder</div><table class='sections'><tr><td valign='top' class='section'><p>Attributes:</td><td valign='top'><p><code>msg</code> &ndash; Message</p><p><code>code</code> &ndash; Code</p></td></table></body></html>

View File

@@ -0,0 +1,11 @@
class My<the_ref>Class:
"""
Attributes:
msg: Message
code: Code
"""
code = 404
def __init__(self):
self.msg = 'test'

View File

@@ -0,0 +1 @@
<html><body><div class='definition'><pre><a href="psi_element://#module#ClassAndInstanceAttributesInOneSectionNumpy">ClassAndInstanceAttributesInOneSectionNumpy</a><br>class&nbsp;<b>MyClass</b></pre></div><div class='content'>Unittest placeholder</div><table class='sections'><tr><td valign='top' class='section'><p>Attributes:</td><td valign='top'><p><code>msg</code> &ndash; Message</p><p><code>code</code> &ndash; Code</p></td></table></body></html>

View File

@@ -0,0 +1,14 @@
class My<the_ref>Class:
"""
Attributes
----------
msg:
Message
code:
Code
"""
code = 404
def __init__(self):
self.msg = 'test'

View File

@@ -0,0 +1 @@
<html><body><div class='definition'><pre><a href="psi_element://#module#ClassAndInstanceAttributesInOneSectionRest">ClassAndInstanceAttributesInOneSectionRest</a><br>class&nbsp;<b>MyClass</b></pre></div><div class='content'>Unittest placeholder</div><table class='sections'><tr><td valign='top' class='section'><p>Attributes:</td><td valign='top'><p><code>msg</code> &ndash; Message</p><p><code>code</code> &ndash; Code</p><p><code>state</code> &ndash; State</p></td></table></body></html>

View File

@@ -0,0 +1,12 @@
class My<the_ref>Class:
"""
:ivar msg: Message
:cvar code: Code
:var state: State
"""
code = 404
def __init__(self):
self.msg = 'test'
self.state = 0

View File

@@ -0,0 +1 @@
<html><body><div class='definition'><pre><a href="psi_element://#module#ClassAndInstanceAttributesWithSameNameNotDuplicatedGoogle">ClassAndInstanceAttributesWithSameNameNotDuplicatedGoogle</a><br>class&nbsp;<b>MyClass</b></pre></div><div class='content'>Unittest placeholder</div><table class='sections'><tr><td valign='top' class='section'><p>Attributes:</td><td valign='top'><p><code>a1</code> &ndash; attr1 doc</p><p><code>a2</code> &ndash; attr2 doc</p></td></table></body></html>

View File

@@ -0,0 +1,15 @@
class My<the_ref>Class:
"""
Class doc
Attributes:
a1: attr1 doc
a2: attr2 doc
"""
a1 = 0
a2 = 0
def __init__(self, a1, a2):
self.a1 = a1
self.a2 = a2

View File

@@ -0,0 +1 @@
<html><body><div class='definition'><pre>Class attribute <b>a1</b> of <a href="psi_element://#typename#ClassAttributeDescriptionFromClassDocstringGoogle.Parent">ClassAttributeDescriptionFromClassDocstringGoogle.Parent</a><br>a1: <a href="psi_element://#typename#int">int</a> = 0</pre></div><div class='content'>a1 doc</div></body></html>

View File

@@ -0,0 +1,7 @@
class Parent:
"""
Attributes:
a1: a1 doc
"""
a<the_ref>1 = 0

View File

@@ -0,0 +1 @@
<html><body><div class='definition'><pre>Class attribute <b>attr</b> of <a href="psi_element://#typename#ClassAttributeDescriptionFromClassDocstringRest.MyClass">ClassAttributeDescriptionFromClassDocstringRest.MyClass</a><br>attr: <a href="psi_element://#typename#int">int</a> = 0</pre></div><div class='content'>attr description</div></body></html>

View File

@@ -0,0 +1,5 @@
class MyClass:
"""
:cvar attr: attr description
"""
attr<the_ref> = 0

View File

@@ -0,0 +1 @@
<html><body><div class='definition'><pre><a href="psi_element://#module#InheritedAttributesGoogle">InheritedAttributesGoogle</a><br>class&nbsp;<b>Child</b>(<a href="psi_element://#typename#InheritedAttributesGoogle.Parent">Parent</a>)</pre></div><div class='content'>Unittest placeholder</div><table class='sections'><tr><td valign='top' class='section'><p>Attributes:</td><td valign='top'><p><code>a1</code> &ndash; a1 doc from child</p><p><code>a2</code> &ndash; a2 doc from child</p></td></table></body></html>

View File

@@ -0,0 +1,21 @@
class Parent:
"""
Attributes:
a1: a1 doc from parent
a2: a2 doc from parent
"""
a1 = 0
def __init__(self):
self.a2 = 0
class Chi<the_ref>ld(Parent):
"""
Attributes:
a1: a1 doc from child
a2: a2 doc from child
"""
def __init__(self):
super().__init__()

View File

@@ -0,0 +1 @@
<html><body><div class='definition'><pre><a href="psi_element://#typename#InitEmptyDocstringOverClassDocstringGoogle.MyClass">InitEmptyDocstringOverClassDocstringGoogle.MyClass</a><br>def <b>__init__</b>(self) -&gt; None</pre></div><div class='content'></div></body></html>

View File

@@ -0,0 +1,10 @@
class MyClass:
"""
Attributes:
a1: a1 doc
a2: a2 doc
"""
def __in<the_ref>it__(self):
""""""
self.a1 = 0
self.a2 = 0

View File

@@ -0,0 +1 @@
<html><body><div class='definition'><pre>Instance attribute <b>a1</b> of <a href="psi_element://#typename#InstanceAttributeDescriptionFromClassDocstringGoogle.Parent">InstanceAttributeDescriptionFromClassDocstringGoogle.Parent</a><br>a1: <a href="psi_element://#typename#int">int</a> = 0</pre></div><div class='content'>a1 doc</div></body></html>

View File

@@ -0,0 +1,9 @@
class Parent:
"""
Attributes:
a1: a1 doc
"""
def __init__(self):
self.a1 = 0
Parent().a<the_ref>1

View File

@@ -0,0 +1 @@
<html><body><div class='definition'><pre>Instance attribute <b>attr</b> of <a href="psi_element://#typename#InstanceAttributeDescriptionFromClassDocstringRest.MyClass">InstanceAttributeDescriptionFromClassDocstringRest.MyClass</a><br>attr: <a href="psi_element://#typename#int">int</a> = 0</pre></div><div class='content'>attr description</div></body></html>

View File

@@ -0,0 +1,7 @@
class MyClass:
"""
:ivar attr: attr description
"""
def __init__(self):
self.attr<the_ref> = 0

View File

@@ -0,0 +1 @@
<html><body><div class='definition'><pre>Parameter <b>name</b> of <a href="psi_element://#func#ParameterDescriptionFromClassDocstringGoogle.MyClass.__init__">ParameterDescriptionFromClassDocstringGoogle.MyClass.__init__</a><br>name: Any</pre></div><div class='content'>parameter doc example</div><table class='sections'><tr><td valign='top' class='section'><p>Documentation is copied from:</td><td valign='top'><code><a href="psi_element://#typename#ParameterDescriptionFromClassDocstringGoogle.MyClass">MyClass</a></code></td></table></body></html>

View File

@@ -0,0 +1,8 @@
class MyClass:
"""
Args:
name: parameter doc example
"""
def __init__(self, nam<the_ref>e):
pass

View File

@@ -0,0 +1 @@
<html><body><div class='definition'><pre>Parameter <b>name</b> of <a href="psi_element://#func#ParameterDescriptionFromInitDocstringOverClassDocstringGoogle.MyClass.__init__">ParameterDescriptionFromInitDocstringOverClassDocstringGoogle.MyClass.__init__</a><br>name: Any</pre></div><div class='content'>parameter doc example from init</div></body></html>

View File

@@ -0,0 +1,12 @@
class MyClass:
"""
Args:
name: parameter doc example from class
"""
def __init__(self, nam<the_ref>e):
"""
Args:
name: parameter doc example from init
"""
pass

View File

@@ -738,6 +738,111 @@ public class PyQuickDocTest extends LightMarkedTestCase {
runWithLanguageLevel(LanguageLevel.getLatest(), this::checkHTMLOnly);
}
// PY-33341
public void testClassAndInstanceAttributesInOneSectionGoogle() {
checkHTMLOnly();
}
// PY-33341
public void testClassAndInstanceAttributesInOneSectionNumpy() {
checkHTMLOnly();
}
// PY-33341
public void testClassAndInstanceAttributesInOneSectionRest() {
runWithDocStringFormat(DocStringFormat.REST, () -> checkHTMLOnly());
}
// PY-33341
public void testClassAndInstanceAttributesInOneSectionEpydoc() {
runWithDocStringFormat(DocStringFormat.EPYTEXT, () -> checkHTMLOnly());
}
// PY-33341
public void testAttributesOrderGoogle() {
checkHTMLOnly();
}
// PY-33341
public void testClassAndInstanceAttributesWithSameNameNotDuplicatedGoogle() {
checkHTMLOnly();
}
// PY-33341
public void testInheritedAttributesGoogle() {
checkHTMLOnly();
}
// PY-33341
public void testAttributesDescriptionFromClassDocstringGoogle() {
checkHTMLOnly();
}
// PY-33341
public void testArgsDescriptionFromClassAndInitNotMixedGoogle() {
checkHTMLOnly();
}
// PY-33341
public void testArgsDescriptionFromClassDocstringGoogle() {
checkHTMLOnly();
}
// PY-33341
public void testArgsFromInitNotTakenInClassDocstringGoogle() {
checkHTMLOnly();
}
// PY-33341
public void testArgsInClassDocGoogle() {
checkHTMLOnly();
}
// PY-33341
public void testInitEmptyDocstringOverClassDocstringGoogle() {
checkHTMLOnly();
}
// PY-33341
public void testAttributeDescriptionEmptyGoogle() {
checkHTMLOnly();
}
// PY-56416
public void testInstanceAttributeDescriptionFromClassDocstringGoogle() {
checkHTMLOnly();
}
// PY-56416
public void testClassAttributeDescriptionFromClassDocstringGoogle() {
checkHTMLOnly();
}
// PY-56416
public void testClassAttributeDescriptionFromClassDocstringRest() {
checkHTMLOnly();
}
// PY-56416
public void testInstanceAttributeDescriptionFromClassDocstringRest() {
checkHTMLOnly();
}
// PY-56416
public void testAttributeDocsNotMixed() {
checkHTMLOnly();
}
// PY-28900
public void testParameterDescriptionFromClassDocstringGoogle() {
checkHTMLOnly();
}
// PY-28900
public void testParameterDescriptionFromInitDocstringOverClassDocstringGoogle() {
checkHTMLOnly();
}
@Override
protected String getTestDataPath() {
return super.getTestDataPath() + "/quickdoc/";