mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-05 01:50:56 +07:00
Enhance quick doc generation; copy inherited docs for overridden methods. Add module quickdoc (PY-166). Add tests.
This commit is contained in:
@@ -12,6 +12,7 @@ ACT.NAME.add.import=Add import
|
||||
ACT.NAME.use.import=Find in imported modules
|
||||
|
||||
ACT.CMD.use.import=Use an imported module
|
||||
ACT.qualify.with.module=Qualify with an imported module
|
||||
|
||||
### Quick fixes ###
|
||||
QFIX.add.parameter.self=Add parameter 'self'
|
||||
@@ -106,6 +107,10 @@ PARSE.expected.statement.break=Statement break expected
|
||||
PARSE.expected.@.or.def='@' or 'def' expected
|
||||
PARSE.expected.formal.param.name=formal parameter name expected
|
||||
|
||||
### qiuck doc generator
|
||||
QDOC.copied.from.$0.$1=<i>Documentation is missing.</i> The following if copied from <code>{0}.{1}</code>.
|
||||
QDOC.copied.from.builtin=<small>(copied from built-in description)</small>
|
||||
|
||||
### unittest run configuration
|
||||
runcfg.unittest.display_name=Python's unittest
|
||||
runcfg.unittest.description=Python's unittest run configuration
|
||||
@@ -138,4 +143,3 @@ runcfg.labels.interpreter_options=Interpreter &options:
|
||||
runcfg.labels.working_directory=&Working directory:
|
||||
runcfg.captions.script_parameters_dialog=Enter script parameters
|
||||
runcfg.captions.interpreter_options_dialog=Enter interpreter options
|
||||
ACT.qualify.with.module=Qualify with an imported module
|
||||
@@ -77,12 +77,16 @@ public class PythonDocumentationProvider extends QuickDocumentationProvider {
|
||||
|
||||
|
||||
public String generateDoc(final PsiElement element, final PsiElement originalElement) {
|
||||
StringBuffer cat = new StringBuffer("<html><body><code>");
|
||||
final StringBuffer cat = new StringBuffer("<html><body><code>");
|
||||
if (element instanceof PyDocStringOwner) {
|
||||
String docString = ((PyDocStringOwner) element).getDocString();
|
||||
String docString = null;
|
||||
boolean prepended_something = false;
|
||||
PyStringLiteralExpression doc_expr = ((PyDocStringOwner) element).getDocStringExpression();
|
||||
if (doc_expr != null) docString = doc_expr.getStringValue();
|
||||
if (element instanceof PyClass) {
|
||||
PyClass cls = (PyClass)element;
|
||||
describeClass(cls, cat);
|
||||
prepended_something = true;
|
||||
}
|
||||
else if (element instanceof PyFunction) {
|
||||
PyFunction fun = (PyFunction)element;
|
||||
@@ -91,25 +95,56 @@ public class PythonDocumentationProvider extends QuickDocumentationProvider {
|
||||
cat.append("<small>class ").append(cls.getName()).append("</small>").append(BR);
|
||||
}
|
||||
describeFunction(fun, cat);
|
||||
prepended_something = true;
|
||||
boolean not_found = true;
|
||||
if (docString == null) {
|
||||
// for well-known methods, copy built-in doc string
|
||||
// TODO: also handle predefined __xxx__ that are not part of 'object'.
|
||||
String meth_name = fun.getName();
|
||||
if (cls != null && meth_name != null && PyNames.UnderscoredNames.contains(meth_name)) {
|
||||
PyClassType objtype = PyBuiltinCache.getInstance(fun.getProject()).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);
|
||||
if (obj_underscored != null) {
|
||||
String predefined_doc = obj_underscored.getDocString();
|
||||
if (predefined_doc != null && predefined_doc.length() > 1) { // only a real-looking doc string counts
|
||||
if (cls != null && meth_name != null ) {
|
||||
// look for inherited
|
||||
for (PyClass ancestor : cls.iterateAncestors()) {
|
||||
PyFunction inherited = ancestor.findMethodByName(meth_name);
|
||||
if (inherited != null) {
|
||||
PyStringLiteralExpression doc_elt = inherited.getDocStringExpression();
|
||||
if (doc_elt != null) {
|
||||
String inherited_doc = doc_elt.getStringValue();
|
||||
if (inherited_doc.length() > 1) {
|
||||
cat
|
||||
.append(BR).append(BR).append("</code>")
|
||||
.append(PyBundle.message("QDOC.copied.from.$0.$1", ancestor.getName(), meth_name))
|
||||
.append(BR).append(BR)
|
||||
.append(predefined_doc)
|
||||
.append(BR)
|
||||
.append("</code><small>(copied from built-in description)</small><code>")
|
||||
.append(inherited_doc)
|
||||
.append("<code>")
|
||||
;
|
||||
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.UnderscoredNames.contains(meth_name)) {
|
||||
PyClassType objtype = PyBuiltinCache.getInstance(fun.getProject()).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);
|
||||
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
|
||||
cat
|
||||
.append(BR).append(BR).append("</code>")
|
||||
.append(predefined_doc)
|
||||
.append(BR)
|
||||
.append(PyBundle.message("QDOC.copied.from.builtin"))
|
||||
.append("<code>")
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,12 +152,17 @@ public class PythonDocumentationProvider extends QuickDocumentationProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (element instanceof PyFile) {
|
||||
// what to prepend to a module description??
|
||||
}
|
||||
else { // not a func, not a class
|
||||
cat.append(combUp(PyUtil.getReadableRepr(element, false)));
|
||||
prepended_something = true;
|
||||
}
|
||||
cat.append("</code>");
|
||||
if (docString != null) {
|
||||
cat.append(BR).append(BR).append(combUp(docString));
|
||||
if (prepended_something) cat.append(BR).append(BR);
|
||||
cat.append(combUp(docString.trim()));
|
||||
}
|
||||
return cat.append("</body></html>").toString();
|
||||
}
|
||||
|
||||
33
python/src/com/jetbrains/python/PythonDosStringFinder.java
Normal file
33
python/src/com/jetbrains/python/PythonDosStringFinder.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package com.jetbrains.python;
|
||||
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.jetbrains.python.psi.PyElement;
|
||||
import com.jetbrains.python.psi.PyExpressionStatement;
|
||||
import com.jetbrains.python.psi.PyStringLiteralExpression;
|
||||
import com.jetbrains.python.psi.PyUtil;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
// TODO: find a better place for this.
|
||||
/**
|
||||
* Utility class for finding an expression which would fit as a doc string.
|
||||
* User: dcheryasov
|
||||
* Date: Jun 7, 2009 5:06:12 AM
|
||||
*/
|
||||
public class PythonDosStringFinder {
|
||||
private PythonDosStringFinder() {}
|
||||
|
||||
/**
|
||||
* Looks for a doc string under given parent.
|
||||
* @param parent where to look. For classes and functions, this would be PyStatementList, for modules, PyFile.
|
||||
* @return the defining expression, or null.
|
||||
*/
|
||||
@Nullable
|
||||
public static PyStringLiteralExpression find(PyElement parent) {
|
||||
if (parent != null) {
|
||||
PsiElement seeker = PyUtil.getFirstNonCommentAfter(parent.getFirstChild());
|
||||
if (seeker instanceof PyExpressionStatement) seeker = PyUtil.getFirstNonCommentAfter(seeker.getFirstChild());
|
||||
if (seeker instanceof PyStringLiteralExpression) return (PyStringLiteralExpression)seeker;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,7 @@ package com.jetbrains.python.psi;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
*/
|
||||
public interface PyDocStringOwner {
|
||||
public interface PyDocStringOwner extends PyElement {
|
||||
@Nullable
|
||||
String getDocString();
|
||||
PyStringLiteralExpression getDocStringExpression();
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface PyFile extends PyElement, PsiFile {
|
||||
public interface PyFile extends PyElement, PsiFile, PyDocStringOwner {
|
||||
|
||||
Key<Boolean> KEY_IS_DIRECTORY = Key.create("Dir impersonated by __init__.py");
|
||||
Key<Boolean> KEY_EXCLUDE_BUILTINS = Key.create("Don't include builtins to processDeclaration results");
|
||||
|
||||
@@ -26,12 +26,12 @@ 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.psi.*;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil;
|
||||
import com.jetbrains.python.psi.resolve.VariantsProcessor;
|
||||
import com.jetbrains.python.psi.stubs.PyClassStub;
|
||||
import com.jetbrains.python.psi.stubs.PyFunctionStub;
|
||||
import com.jetbrains.python.validation.DocStringAnnotator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -172,7 +172,7 @@ public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implement
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
//To change body of implemented methods use File | Settings | File Templates.
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -186,7 +186,7 @@ public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implement
|
||||
// maybe a bare old-style class?
|
||||
// TODO: depend on language version: py3k does not do old style classes
|
||||
PsiElement paren = PsiTreeUtil.getChildOfType(this, PyParenthesizedExpression.class).getFirstChild(); // no NPE, we always have the par expr
|
||||
if (paren != null && "(".equals(paren.getText())) { // no, we have "()" after class name, it's new style
|
||||
if (paren != null && "(".equals(paren.getText())) { // "()" after class name, it's new style
|
||||
for(PsiElement element: superClassElements) {
|
||||
if (element instanceof PyClass) {
|
||||
result.add((PyClass) element);
|
||||
@@ -309,16 +309,6 @@ public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implement
|
||||
PsiElement lastParent,
|
||||
@NotNull PsiElement place)
|
||||
{
|
||||
/*
|
||||
for(PyFunction func: getMethods()) {
|
||||
if (func == lastParent) continue;
|
||||
if (!processor.execute(func, substitutor)) return false;
|
||||
}
|
||||
for(PyTargetExpression expr: getClassAttributes()) {
|
||||
if (expr == lastParent) continue;
|
||||
if (!processor.execute(expr, substitutor)) return false;
|
||||
}
|
||||
*/
|
||||
// class level
|
||||
final PsiElement the_psi = getNode().getPsi();
|
||||
PyResolveUtil.treeCrawlUp(processor, true, the_psi, the_psi);
|
||||
@@ -339,8 +329,8 @@ public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implement
|
||||
return name != null ? name.getStartOffset() : super.getTextOffset();
|
||||
}
|
||||
|
||||
public String getDocString() {
|
||||
return DocStringAnnotator.findDocString(getStatementList());
|
||||
public PyStringLiteralExpression getDocStringExpression() {
|
||||
return PythonDosStringFinder.find(getStatementList());
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.intellij.psi.tree.TokenSet;
|
||||
import com.jetbrains.python.PyElementTypes;
|
||||
import com.jetbrains.python.PythonFileType;
|
||||
import com.jetbrains.python.PythonLanguage;
|
||||
import com.jetbrains.python.PythonDosStringFinder;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil;
|
||||
import com.jetbrains.python.psi.resolve.ResolveProcessor;
|
||||
@@ -235,4 +236,8 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression {
|
||||
}
|
||||
return myType;
|
||||
}
|
||||
|
||||
public PyStringLiteralExpression getDocStringExpression() {
|
||||
return PythonDosStringFinder.find(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,10 @@ 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.psi.*;
|
||||
import com.jetbrains.python.psi.stubs.PyClassStub;
|
||||
import com.jetbrains.python.psi.stubs.PyFunctionStub;
|
||||
import com.jetbrains.python.validation.DocStringAnnotator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -146,8 +146,8 @@ public class PyFunctionImpl extends PyPresentableElementImpl<PyFunctionStub> imp
|
||||
node.getTreeParent().removeChild(node);
|
||||
}
|
||||
|
||||
public String getDocString() {
|
||||
return DocStringAnnotator.findDocString(getStatementList());
|
||||
public PyStringLiteralExpression getDocStringExpression() {
|
||||
return PythonDosStringFinder.find(getStatementList());
|
||||
}
|
||||
|
||||
protected String getElementLocation() {
|
||||
|
||||
2
python/testData/quickdoc/DirectClass.html
Normal file
2
python/testData/quickdoc/DirectClass.html
Normal file
@@ -0,0 +1,2 @@
|
||||
<html><body><code>class <b>Foo</b>(object)</code><br><br>Doc of Foo.</body></html>
|
||||
|
||||
6
python/testData/quickdoc/DirectClass.py
Normal file
6
python/testData/quickdoc/DirectClass.py
Normal file
@@ -0,0 +1,6 @@
|
||||
# direct class doc
|
||||
class Foo(object):
|
||||
"<the_doc>Doc of Foo."
|
||||
pass
|
||||
|
||||
<the_ref>Foo
|
||||
2
python/testData/quickdoc/DirectFunc.html
Normal file
2
python/testData/quickdoc/DirectFunc.html
Normal file
@@ -0,0 +1,2 @@
|
||||
<html><body><code>def <b>foo</b>()</code><br><br>Doc of foo.</body></html>
|
||||
|
||||
6
python/testData/quickdoc/DirectFunc.py
Normal file
6
python/testData/quickdoc/DirectFunc.py
Normal file
@@ -0,0 +1,6 @@
|
||||
# directly in function
|
||||
def foo():
|
||||
"<the_doc>Doc of foo."
|
||||
pass
|
||||
|
||||
<the_ref>foo
|
||||
1
python/testData/quickdoc/InheritedMethod.html
Normal file
1
python/testData/quickdoc/InheritedMethod.html
Normal file
@@ -0,0 +1 @@
|
||||
<html><body><code><small>class B</small><br>def <b>foo</b>(self)<br><br></code><i>Documentation is missing.</i> The following if copied from <code>A.foo</code>.<br><br>Doc from A.foo.<code></code></body></html>
|
||||
12
python/testData/quickdoc/InheritedMethod.py
Normal file
12
python/testData/quickdoc/InheritedMethod.py
Normal file
@@ -0,0 +1,12 @@
|
||||
# copied doc of inherited method
|
||||
class A:
|
||||
def foo(self):
|
||||
"<the_doc>Doc from A.foo."
|
||||
pass
|
||||
|
||||
class B(A):
|
||||
def foo(self):
|
||||
return None
|
||||
|
||||
b = B()
|
||||
b.<the_ref>foo()
|
||||
1
python/testData/quickdoc/Method.html
Normal file
1
python/testData/quickdoc/Method.html
Normal file
@@ -0,0 +1 @@
|
||||
<html><body><code><small>class Foo</small><br>@<i>deco</i>()<br>def <b>meth</b>(self)</code><br><br>Doc of meth.</body></html>
|
||||
10
python/testData/quickdoc/Method.py
Normal file
10
python/testData/quickdoc/Method.py
Normal file
@@ -0,0 +1,10 @@
|
||||
# just method
|
||||
class Foo:
|
||||
@deco
|
||||
def meth(self):
|
||||
"""<the_doc>
|
||||
Doc of meth.
|
||||
"""
|
||||
|
||||
f = Foo()
|
||||
f.<the_ref>meth
|
||||
1
python/testData/quickdoc/Module.html
Normal file
1
python/testData/quickdoc/Module.html
Normal file
@@ -0,0 +1 @@
|
||||
<html><body><code></code>Module's doc.</body></html>
|
||||
7
python/testData/quickdoc/Module.py
Normal file
7
python/testData/quickdoc/Module.py
Normal file
@@ -0,0 +1,7 @@
|
||||
# module. reuse ourselves
|
||||
"""
|
||||
<the_doc>Module's doc.
|
||||
"""
|
||||
|
||||
import <the_ref>Module
|
||||
|
||||
89
python/testSrc/com/jetbrains/python/PyQuickDocTest.java
Normal file
89
python/testSrc/com/jetbrains/python/PyQuickDocTest.java
Normal file
@@ -0,0 +1,89 @@
|
||||
package com.jetbrains.python;
|
||||
|
||||
import com.intellij.openapi.application.PathManager;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VfsUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.jetbrains.python.psi.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* TODO: Add description
|
||||
* User: dcheryasov
|
||||
* Date: Jun 7, 2009 12:31:07 PM
|
||||
*/
|
||||
public class PyQuickDocTest extends MarkedTestCase {
|
||||
private PythonDocumentationProvider myProvider;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
// the provider is stateless, can be reused, as in real life
|
||||
myProvider = new PythonDocumentationProvider();
|
||||
}
|
||||
|
||||
protected String getTestDataPath() {
|
||||
return PathManager.getHomePath() + "/plugins/python/testData/quickdoc/";
|
||||
}
|
||||
|
||||
private void checkByHTML(String text) throws Exception {
|
||||
assertNotNull(text);
|
||||
String filePath = getTestName(false) + ".html";
|
||||
final String fullPath = getTestDataPath() + filePath;
|
||||
final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(fullPath.replace(File.separatorChar, '/'));
|
||||
assertNotNull("file " + filePath + " not found", vFile);
|
||||
|
||||
String fileText = StringUtil.convertLineSeparators(VfsUtil.loadText(vFile), "\n");
|
||||
assertEquals(fileText.trim(), text.trim());
|
||||
}
|
||||
|
||||
private void processRefDocPair() throws Exception {
|
||||
Map<String, PsiElement> marks = loadTest();
|
||||
assertEquals(2, marks.size());
|
||||
PsiElement doc_elt = marks.get("<the_doc>").getParent(); // ident -> expr
|
||||
assertTrue(doc_elt instanceof PyStringLiteralExpression);
|
||||
String doc_text = ((PyStringLiteralExpression)doc_elt).getStringValue();
|
||||
assertNotNull(doc_text);
|
||||
|
||||
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);
|
||||
|
||||
checkByHTML(myProvider.generateDoc(doc_owner, null));
|
||||
}
|
||||
|
||||
public void testDirectFunc() throws Exception {
|
||||
processRefDocPair();
|
||||
}
|
||||
|
||||
public void testDirectClass() throws Exception {
|
||||
processRefDocPair();
|
||||
}
|
||||
|
||||
public void testModule() throws Exception {
|
||||
processRefDocPair();
|
||||
}
|
||||
|
||||
public void testMethod() throws Exception {
|
||||
processRefDocPair();
|
||||
}
|
||||
|
||||
public void testInheritedMethod() throws Exception {
|
||||
Map<String, PsiElement> marks = loadTest();
|
||||
assertEquals(2, marks.size());
|
||||
PsiElement doc_elt = marks.get("<the_doc>").getParent(); // ident -> expr
|
||||
assertTrue(doc_elt instanceof PyStringLiteralExpression);
|
||||
String doc_text = ((PyStringLiteralExpression)doc_elt).getStringValue();
|
||||
assertNotNull(doc_text);
|
||||
|
||||
PsiElement ref_elt = marks.get("<the_ref>").getParent(); // ident -> expr
|
||||
final PyDocStringOwner doc_owner = (PyDocStringOwner)((PyReferenceExpression)ref_elt).resolve();
|
||||
assertNull(doc_owner.getDocStringExpression()); // no direct doc!
|
||||
|
||||
checkByHTML(myProvider.generateDoc(doc_owner, null));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user