mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
show class quick doc if constructor isn't documented (part of PY-381)
This commit is contained in:
+2
-2
@@ -13,8 +13,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
* User: dcheryasov
|
||||
* Date: Jun 7, 2009 5:06:12 AM
|
||||
*/
|
||||
public class PythonDosStringFinder {
|
||||
private PythonDosStringFinder() {}
|
||||
public class PythonDocStringFinder {
|
||||
private PythonDocStringFinder() {}
|
||||
|
||||
/**
|
||||
* Looks for a doc string under given parent.
|
||||
@@ -166,7 +166,43 @@ public class PythonDocumentationProvider extends QuickDocumentationProvider {
|
||||
|
||||
cat.add(prolog_cat).addWith(TagCode, doc_cat).add(epilog_cat); // pre-assemble; then add stuff to individual cats as needed
|
||||
cat = wrapInTag("html", wrapInTag("body", cat));
|
||||
element = resolveToDocStringOwner(element, originalElement, prolog_cat);
|
||||
|
||||
// now element may contain a doc string
|
||||
if (element instanceof PyDocStringOwner) {
|
||||
String docString = null;
|
||||
PyStringLiteralExpression doc_expr = ((PyDocStringOwner) element).getDocStringExpression();
|
||||
if (doc_expr != null) docString = doc_expr.getStringValue();
|
||||
// doc of what?
|
||||
if (element instanceof PyClass) {
|
||||
PyClass cls = (PyClass)element;
|
||||
doc_cat.addWith(TagSmall, describeClass(cls, TagBold));
|
||||
}
|
||||
else if (element instanceof PyFunction) {
|
||||
PyFunction fun = (PyFunction)element;
|
||||
PyClass cls = fun.getContainingClass();
|
||||
if (cls != null) doc_cat.addWith(TagSmall, $("class ", cls.getName(), BR));
|
||||
doc_cat.add(describeFunction(fun, TagItalic, BR, TagBold, LCombUp));
|
||||
if (docString == null) {
|
||||
addInheritedDocString(fun, cls, doc_cat, epilog_cat);
|
||||
}
|
||||
}
|
||||
else if (element instanceof PyFile) {
|
||||
// what to prepend to a module description??
|
||||
}
|
||||
else { // not a func, not a class
|
||||
doc_cat.add(combUp(PyUtil.getReadableRepr(element, false)));
|
||||
}
|
||||
if (docString != null) {
|
||||
doc_cat.add(BR).add(combUpDocString(docString));
|
||||
}
|
||||
else if (prolog_cat.isEmpty() && doc_cat.isEmpty() && epilog_cat.isEmpty()) return null; // got nothing to say!
|
||||
return cat.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static PsiElement resolveToDocStringOwner(PsiElement element, PsiElement originalElement, ChainIterable<String> prolog_cat) {
|
||||
// here the ^Q target is already resolved; the resolved element may point to intermediate assignments
|
||||
boolean reassignment_marked = false;
|
||||
if (element instanceof PyTargetExpression) {
|
||||
@@ -199,84 +235,66 @@ public class PythonDocumentationProvider extends QuickDocumentationProvider {
|
||||
}
|
||||
|
||||
}
|
||||
// now element may contain a doc string
|
||||
if (element instanceof PyDocStringOwner) {
|
||||
String docString = null;
|
||||
PyStringLiteralExpression doc_expr = ((PyDocStringOwner) element).getDocStringExpression();
|
||||
if (doc_expr != null) docString = doc_expr.getStringValue();
|
||||
// doc of what?
|
||||
if (element instanceof PyClass) {
|
||||
PyClass cls = (PyClass)element;
|
||||
doc_cat.addWith(TagSmall, describeClass(cls, TagBold));
|
||||
if (element instanceof PyFunction && PyNames.INIT.equals(((PyFunction)element).getName())) {
|
||||
final PyStringLiteralExpression expression = ((PyFunction)element).getDocStringExpression();
|
||||
if (expression == null) {
|
||||
PyClass containingClass = ((PyFunction) element).getContainingClass();
|
||||
if (containingClass != null) {
|
||||
element = containingClass;
|
||||
}
|
||||
}
|
||||
else if (element instanceof PyFunction) {
|
||||
PyFunction fun = (PyFunction)element;
|
||||
PyClass cls = fun.getContainingClass();
|
||||
if (cls != null) doc_cat.addWith(TagSmall, $("class ", cls.getName(), BR));
|
||||
doc_cat.add(describeFunction(fun, TagItalic, BR, TagBold, LCombUp));
|
||||
boolean not_found = true;
|
||||
if (docString == null) {
|
||||
String meth_name = fun.getName();
|
||||
if (cls != null && meth_name != null ) {
|
||||
// look for inherited and its doc
|
||||
for (PyClass ancestor : cls.iterateAncestors()) {
|
||||
PyFunction inherited = ancestor.findMethodByName(meth_name, false);
|
||||
if (inherited != null) {
|
||||
PyStringLiteralExpression doc_elt = inherited.getDocStringExpression();
|
||||
if (doc_elt != null) {
|
||||
String inherited_doc = doc_elt.getStringValue();
|
||||
if (inherited_doc.length() > 1) {
|
||||
epilog_cat
|
||||
.add(BR).add(BR)
|
||||
.add(PyBundle.message("QDOC.copied.from.$0.$1", ancestor.getName(), meth_name))
|
||||
.add(BR).add(BR)
|
||||
.addWith(TagCode, $(inherited_doc))
|
||||
;
|
||||
not_found = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
if (not_found) {
|
||||
// above could have not worked because inheritance is not searched down to 'object'.
|
||||
// for well-known methods, copy built-in doc string.
|
||||
// TODO: also handle predefined __xxx__ that are not part of 'object'.
|
||||
if (PyNames.UnderscoredAttributes.contains(meth_name)) {
|
||||
PyClassType objtype = PyBuiltinCache.getInstance(fun).getObjectType(); // old- and new-style classes share the __xxx__ stuff
|
||||
if (objtype != null) {
|
||||
PyClass objcls = objtype.getPyClass();
|
||||
if (objcls != null) {
|
||||
PyFunction obj_underscored = objcls.findMethodByName(meth_name, false);
|
||||
if (obj_underscored != null) {
|
||||
PyStringLiteralExpression predefined_doc_expr = obj_underscored.getDocStringExpression();
|
||||
String predefined_doc = predefined_doc_expr != null? predefined_doc_expr.getStringValue() : null;
|
||||
if (predefined_doc != null && predefined_doc.length() > 1) { // only a real-looking doc string counts
|
||||
doc_cat.add(predefined_doc);
|
||||
epilog_cat.add(BR).add(BR).add(PyBundle.message("QDOC.copied.from.builtin"));
|
||||
}
|
||||
}
|
||||
}
|
||||
private static void addInheritedDocString(PyFunction fun, PyClass cls, ChainIterable<String> doc_cat, ChainIterable<String> epilog_cat) {
|
||||
boolean not_found = true;
|
||||
String meth_name = fun.getName();
|
||||
if (cls != null && meth_name != null ) {
|
||||
// look for inherited and its doc
|
||||
for (PyClass ancestor : cls.iterateAncestors()) {
|
||||
PyFunction inherited = ancestor.findMethodByName(meth_name, false);
|
||||
if (inherited != null) {
|
||||
PyStringLiteralExpression doc_elt = inherited.getDocStringExpression();
|
||||
if (doc_elt != null) {
|
||||
String inherited_doc = doc_elt.getStringValue();
|
||||
if (inherited_doc.length() > 1) {
|
||||
epilog_cat
|
||||
.add(BR).add(BR)
|
||||
.add(PyBundle.message("QDOC.copied.from.$0.$1", ancestor.getName(), meth_name))
|
||||
.add(BR).add(BR)
|
||||
.addWith(TagCode, $(inherited_doc))
|
||||
;
|
||||
not_found = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (not_found) {
|
||||
// above could have not worked because inheritance is not searched down to 'object'.
|
||||
// for well-known methods, copy built-in doc string.
|
||||
// TODO: also handle predefined __xxx__ that are not part of 'object'.
|
||||
if (PyNames.UnderscoredAttributes.contains(meth_name)) {
|
||||
PyClassType objtype = PyBuiltinCache.getInstance(fun).getObjectType(); // old- and new-style classes share the __xxx__ stuff
|
||||
if (objtype != null) {
|
||||
PyClass objcls = objtype.getPyClass();
|
||||
if (objcls != null) {
|
||||
PyFunction obj_underscored = objcls.findMethodByName(meth_name, false);
|
||||
if (obj_underscored != null) {
|
||||
PyStringLiteralExpression predefined_doc_expr = obj_underscored.getDocStringExpression();
|
||||
String predefined_doc = predefined_doc_expr != null? predefined_doc_expr.getStringValue() : null;
|
||||
if (predefined_doc != null && predefined_doc.length() > 1) { // only a real-looking doc string counts
|
||||
doc_cat.add(predefined_doc);
|
||||
epilog_cat.add(BR).add(BR).add(PyBundle.message("QDOC.copied.from.builtin"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (element instanceof PyFile) {
|
||||
// what to prepend to a module description??
|
||||
}
|
||||
else { // not a func, not a class
|
||||
doc_cat.add(combUp(PyUtil.getReadableRepr(element, false)));
|
||||
}
|
||||
if (docString != null) {
|
||||
doc_cat.add(BR).add(combUpDocString(docString));
|
||||
}
|
||||
else if (prolog_cat.isEmpty() && doc_cat.isEmpty() && epilog_cat.isEmpty()) return null; // got nothing to say!
|
||||
return cat.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static FP.Lambda1<String, String> LCombUp = new FP.Lambda1<String, String>() {
|
||||
@@ -332,22 +350,6 @@ public class PythonDocumentationProvider extends QuickDocumentationProvider {
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
private static StringBuilder join(String delimiter, Iterable list, StringBuilder cat) {
|
||||
boolean is_next = false;
|
||||
for (Object item : list) {
|
||||
if (is_next) cat.append(delimiter);
|
||||
else is_next = true;
|
||||
cat.append(item.toString());
|
||||
}
|
||||
return cat;
|
||||
}
|
||||
|
||||
private static String join(String delimiter, List list) {
|
||||
return join(delimiter, list, new StringBuilder()).toString();
|
||||
}
|
||||
*/
|
||||
|
||||
private static <T> Iterable<T> interleave(Iterable<T> source, T filler) {
|
||||
List<T> ret = new LinkedList<T>();
|
||||
boolean is_next = false;
|
||||
|
||||
@@ -7,7 +7,7 @@ import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiWhiteSpace;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.jetbrains.python.PythonDosStringFinder;
|
||||
import com.jetbrains.python.PythonDocStringFinder;
|
||||
import com.jetbrains.python.PythonLanguage;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -40,7 +40,7 @@ class AddImportHelper {
|
||||
}
|
||||
// maybe we arrived at the doc comment stmt; skip over it, too
|
||||
else if (!skipped_over_imports && ! skipped_over_doc && file instanceof PyFile) {
|
||||
PsiElement doc_elt = PythonDosStringFinder.find((PyElement)file); // this gives the literal; its parent is the expr seeker may have encountered
|
||||
PsiElement doc_elt = PythonDocStringFinder.find((PyElement)file); // this gives the literal; its parent is the expr seeker may have encountered
|
||||
if (doc_elt != null && doc_elt.getParent() == seeker) {
|
||||
feeler = seeker.getNextSibling();
|
||||
seeker = feeler; // skip over doc even if there's nothing below it
|
||||
|
||||
@@ -12,7 +12,7 @@ import com.intellij.util.IncorrectOperationException;
|
||||
import com.jetbrains.python.PyElementTypes;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.PyTokenTypes;
|
||||
import com.jetbrains.python.PythonDosStringFinder;
|
||||
import com.jetbrains.python.PythonDocStringFinder;
|
||||
import com.jetbrains.python.codeInsight.dataflow.scope.Scope;
|
||||
import com.jetbrains.python.codeInsight.dataflow.scope.impl.ScopeImpl;
|
||||
import com.jetbrains.python.psi.*;
|
||||
@@ -285,7 +285,7 @@ public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implement
|
||||
}
|
||||
|
||||
public PyStringLiteralExpression getDocStringExpression() {
|
||||
return PythonDosStringFinder.find(getStatementList());
|
||||
return PythonDocStringFinder.find(getStatementList());
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
|
||||
@@ -14,7 +14,7 @@ import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.tree.TokenSet;
|
||||
import com.intellij.util.indexing.FileBasedIndex;
|
||||
import com.jetbrains.python.PyElementTypes;
|
||||
import com.jetbrains.python.PythonDosStringFinder;
|
||||
import com.jetbrains.python.PythonDocStringFinder;
|
||||
import com.jetbrains.python.PythonFileType;
|
||||
import com.jetbrains.python.PythonLanguage;
|
||||
import com.jetbrains.python.codeInsight.dataflow.scope.Scope;
|
||||
@@ -250,7 +250,7 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression {
|
||||
}
|
||||
|
||||
public PyStringLiteralExpression getDocStringExpression() {
|
||||
return PythonDosStringFinder.find(this);
|
||||
return PythonDocStringFinder.find(this);
|
||||
}
|
||||
|
||||
public void subtreeChanged() {
|
||||
|
||||
@@ -27,11 +27,11 @@ import com.intellij.util.Icons;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.jetbrains.python.PyElementTypes;
|
||||
import com.jetbrains.python.PyTokenTypes;
|
||||
import com.jetbrains.python.PythonDosStringFinder;
|
||||
import com.jetbrains.python.PythonDocStringFinder;
|
||||
import com.jetbrains.python.codeInsight.controlflow.PyControlFlowBuilder;
|
||||
import com.jetbrains.python.codeInsight.dataflow.scope.Scope;
|
||||
import com.jetbrains.python.codeInsight.dataflow.scope.impl.ScopeImpl;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.codeInsight.controlflow.PyControlFlowBuilder;
|
||||
import com.jetbrains.python.psi.stubs.PyClassStub;
|
||||
import com.jetbrains.python.psi.stubs.PyFunctionStub;
|
||||
import com.jetbrains.python.toolbox.SingleIterable;
|
||||
@@ -149,7 +149,7 @@ public class PyFunctionImpl extends PyPresentableElementImpl<PyFunctionStub> imp
|
||||
}
|
||||
|
||||
public PyStringLiteralExpression getDocStringExpression() {
|
||||
return PythonDosStringFinder.find(getStatementList());
|
||||
return PythonDocStringFinder.find(getStatementList());
|
||||
}
|
||||
|
||||
protected String getElementLocation() {
|
||||
|
||||
@@ -18,7 +18,7 @@ package com.jetbrains.python.validation;
|
||||
|
||||
import com.intellij.lang.annotation.Annotation;
|
||||
import com.jetbrains.python.PyHighlighter;
|
||||
import com.jetbrains.python.PythonDosStringFinder;
|
||||
import com.jetbrains.python.PythonDocStringFinder;
|
||||
import com.jetbrains.python.psi.PyClass;
|
||||
import com.jetbrains.python.psi.PyFile;
|
||||
import com.jetbrains.python.psi.PyFunction;
|
||||
@@ -31,18 +31,18 @@ public class DocStringAnnotator extends PyAnnotator {
|
||||
|
||||
@Override
|
||||
public void visitPyFile(PyFile node) {
|
||||
annotateDocStringStmt(PythonDosStringFinder.find(node));
|
||||
annotateDocStringStmt(PythonDocStringFinder.find(node));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void visitPyFunction(PyFunction node) {
|
||||
annotateDocStringStmt(PythonDosStringFinder.find(node.getStatementList()));
|
||||
annotateDocStringStmt(PythonDocStringFinder.find(node.getStatementList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyClass(PyClass node) {
|
||||
annotateDocStringStmt(PythonDosStringFinder.find(node.getStatementList()));
|
||||
annotateDocStringStmt(PythonDocStringFinder.find(node.getStatementList()));
|
||||
}
|
||||
|
||||
private void annotateDocStringStmt(PyStringLiteralExpression stmt) {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<html><body><code><small>class <b>Foo</b>(object)</small><br>Doc of Foo.</code></body></html>
|
||||
@@ -0,0 +1,7 @@
|
||||
# direct class doc
|
||||
class Foo(object):
|
||||
"<the_doc>Doc of Foo."
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
<the_ref>Foo()
|
||||
@@ -28,21 +28,22 @@ public class PyQuickDocTest extends LightMarkedTestCase {
|
||||
myProvider = new PythonDocumentationProvider();
|
||||
}
|
||||
|
||||
protected String getTestDataPath() {
|
||||
return PythonTestUtil.getTestDataPath() + "/quickdoc/";
|
||||
}
|
||||
|
||||
private void checkByHTML(String text) throws Exception {
|
||||
assertNotNull(text);
|
||||
String filePath = getTestName(false) + ".html";
|
||||
String filePath = "/quickdoc/" + getTestName(false) + ".html";
|
||||
final String fullPath = getTestDataPath() + filePath;
|
||||
final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(fullPath.replace(File.separatorChar, '/'));
|
||||
assertNotNull("file " + filePath + " not found", vFile);
|
||||
assertNotNull("file " + fullPath + " not found", vFile);
|
||||
|
||||
String fileText = StringUtil.convertLineSeparators(VfsUtil.loadText(vFile), "\n");
|
||||
assertEquals(fileText.trim(), text.trim());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, PsiElement> loadTest() throws Exception {
|
||||
return configureByFile("/quickdoc/" + getTestName(false) + ".py");
|
||||
}
|
||||
|
||||
private void processRefDocPair() throws Exception {
|
||||
Map<String, PsiElement> marks = loadTest();
|
||||
assertEquals(2, marks.size());
|
||||
@@ -53,7 +54,7 @@ public class PyQuickDocTest extends LightMarkedTestCase {
|
||||
|
||||
PsiElement ref_elt = marks.get("<the_ref>").getParent(); // ident -> expr
|
||||
final PyDocStringOwner doc_owner = (PyDocStringOwner)((PyReferenceExpression)ref_elt).resolve();
|
||||
assertEquals(doc_owner.getDocStringExpression(), doc_elt);
|
||||
assertEquals(doc_elt, doc_owner.getDocStringExpression());
|
||||
|
||||
checkByHTML(myProvider.generateDoc(doc_owner, null));
|
||||
}
|
||||
@@ -70,6 +71,13 @@ public class PyQuickDocTest extends LightMarkedTestCase {
|
||||
processRefDocPair();
|
||||
}
|
||||
|
||||
public void testClassUndocumentedConstructor() throws Exception {
|
||||
Map<String, PsiElement> marks = loadTest();
|
||||
PsiElement ref_elt = marks.get("<the_ref>").getParent(); // ident -> expr
|
||||
final PyDocStringOwner doc_owner = (PyDocStringOwner)((PyReferenceExpression)ref_elt).resolve();
|
||||
checkByHTML(myProvider.generateDoc(doc_owner, null));
|
||||
}
|
||||
|
||||
public void testCallFunc() throws Exception {
|
||||
processRefDocPair();
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.intellij.openapi.vfs.VfsUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.testFramework.TestDataFile;
|
||||
import com.intellij.util.containers.HashMap;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
|
||||
@@ -47,12 +48,12 @@ public abstract class LightMarkedTestCase extends PyLightFixtureTestCase {
|
||||
* @return a mapping of markers to PSI elements
|
||||
* @throws Exception
|
||||
*/
|
||||
protected Map<String, PsiElement> configureByFile(@NonNls String filePath, @NonNls String markerRegexp)
|
||||
protected Map<String, PsiElement> configureByFile(@TestDataFile @NonNls String filePath, @NonNls String markerRegexp)
|
||||
throws Exception
|
||||
{
|
||||
final String fullPath = getTestDataPath() + filePath;
|
||||
final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(fullPath.replace(File.separatorChar, '/'));
|
||||
assertNotNull("file " + filePath + " not found", vFile);
|
||||
assertNotNull("file " + fullPath + " not found", vFile);
|
||||
|
||||
String fileText = StringUtil.convertLineSeparators(VfsUtil.loadText(vFile), "\n");
|
||||
|
||||
@@ -111,6 +112,4 @@ public abstract class LightMarkedTestCase extends PyLightFixtureTestCase {
|
||||
String fname = getTestName(false) + ".py";
|
||||
return configureByFile(fname);
|
||||
}
|
||||
|
||||
protected abstract String getTestDataPath();
|
||||
}
|
||||
Reference in New Issue
Block a user