diff --git a/python/src/com/jetbrains/python/PythonDosStringFinder.java b/python/src/com/jetbrains/python/PythonDocStringFinder.java similarity index 93% rename from python/src/com/jetbrains/python/PythonDosStringFinder.java rename to python/src/com/jetbrains/python/PythonDocStringFinder.java index 6d3d03f4e780..f9a25a7cadfb 100644 --- a/python/src/com/jetbrains/python/PythonDosStringFinder.java +++ b/python/src/com/jetbrains/python/PythonDocStringFinder.java @@ -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. diff --git a/python/src/com/jetbrains/python/PythonDocumentationProvider.java b/python/src/com/jetbrains/python/PythonDocumentationProvider.java index cb26042cd1cc..0681154a8c86 100644 --- a/python/src/com/jetbrains/python/PythonDocumentationProvider.java +++ b/python/src/com/jetbrains/python/PythonDocumentationProvider.java @@ -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 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 doc_cat, ChainIterable 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 LCombUp = new FP.Lambda1() { @@ -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 Iterable interleave(Iterable source, T filler) { List ret = new LinkedList(); boolean is_next = false; diff --git a/python/src/com/jetbrains/python/actions/AddImportHelper.java b/python/src/com/jetbrains/python/actions/AddImportHelper.java index e85caf071f5d..0b149cc83127 100644 --- a/python/src/com/jetbrains/python/actions/AddImportHelper.java +++ b/python/src/com/jetbrains/python/actions/AddImportHelper.java @@ -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 diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index b24505357513..cc21c2da389d 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -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 implement } public PyStringLiteralExpression getDocStringExpression() { - return PythonDosStringFinder.find(getStatementList()); + return PythonDocStringFinder.find(getStatementList()); } public String toString() { diff --git a/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java b/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java index bb02e373c51f..a4adc88198c4 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java @@ -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() { diff --git a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java index e449da049135..a90015d22c41 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java @@ -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 imp } public PyStringLiteralExpression getDocStringExpression() { - return PythonDosStringFinder.find(getStatementList()); + return PythonDocStringFinder.find(getStatementList()); } protected String getElementLocation() { diff --git a/python/src/com/jetbrains/python/validation/DocStringAnnotator.java b/python/src/com/jetbrains/python/validation/DocStringAnnotator.java index e9840e1d53d3..e3bb9b7335e0 100644 --- a/python/src/com/jetbrains/python/validation/DocStringAnnotator.java +++ b/python/src/com/jetbrains/python/validation/DocStringAnnotator.java @@ -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) { diff --git a/python/testData/quickdoc/ClassUndocumentedConstructor.html b/python/testData/quickdoc/ClassUndocumentedConstructor.html new file mode 100644 index 000000000000..8991500da1d8 --- /dev/null +++ b/python/testData/quickdoc/ClassUndocumentedConstructor.html @@ -0,0 +1 @@ +class Foo(object)
Doc of Foo.
diff --git a/python/testData/quickdoc/ClassUndocumentedConstructor.py b/python/testData/quickdoc/ClassUndocumentedConstructor.py new file mode 100644 index 000000000000..6ad68e58ac2b --- /dev/null +++ b/python/testData/quickdoc/ClassUndocumentedConstructor.py @@ -0,0 +1,7 @@ +# direct class doc +class Foo(object): + "Doc of Foo." + def __init__(self): + pass + +Foo() diff --git a/python/testSrc/com/jetbrains/python/PyQuickDocTest.java b/python/testSrc/com/jetbrains/python/PyQuickDocTest.java index 511ae3acce9a..5f3a6fb0b7d3 100644 --- a/python/testSrc/com/jetbrains/python/PyQuickDocTest.java +++ b/python/testSrc/com/jetbrains/python/PyQuickDocTest.java @@ -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 loadTest() throws Exception { + return configureByFile("/quickdoc/" + getTestName(false) + ".py"); + } + private void processRefDocPair() throws Exception { Map marks = loadTest(); assertEquals(2, marks.size()); @@ -53,7 +54,7 @@ public class PyQuickDocTest extends LightMarkedTestCase { PsiElement ref_elt = marks.get("").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 marks = loadTest(); + PsiElement ref_elt = marks.get("").getParent(); // ident -> expr + final PyDocStringOwner doc_owner = (PyDocStringOwner)((PyReferenceExpression)ref_elt).resolve(); + checkByHTML(myProvider.generateDoc(doc_owner, null)); + } + public void testCallFunc() throws Exception { processRefDocPair(); } diff --git a/python/testSrc/com/jetbrains/python/fixtures/LightMarkedTestCase.java b/python/testSrc/com/jetbrains/python/fixtures/LightMarkedTestCase.java index 888b51e6bf5b..479e1474c7be 100644 --- a/python/testSrc/com/jetbrains/python/fixtures/LightMarkedTestCase.java +++ b/python/testSrc/com/jetbrains/python/fixtures/LightMarkedTestCase.java @@ -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 configureByFile(@NonNls String filePath, @NonNls String markerRegexp) + protected Map 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(); } \ No newline at end of file