PY-16761 Prepend vararg parameters with * in generated Google code style docstrings

This commit is contained in:
Mikhail Golubev
2015-09-04 19:08:22 +03:00
parent feb4206ce3
commit 3ce47a1dbb
10 changed files with 76 additions and 71 deletions
@@ -23,6 +23,7 @@ import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReference;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ObjectUtils;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.debugger.PySignature;
import com.jetbrains.python.debugger.PySignatureCacheManager;
@@ -31,6 +32,7 @@ import com.jetbrains.python.documentation.PyDocstringGenerator;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.toolbox.Substring;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* User: ktisha
@@ -57,7 +59,6 @@ public class SpecifyTypeInDocstringIntention extends TypeIntention {
final PsiElement resolved = reference != null ? reference.resolve() : null;
PyParameter parameter = getParameter(problemElement, resolved);
String kind = parameter != null ? "type" : "rtype";
final PyCallable callable;
if (parameter != null) {
@@ -67,29 +68,25 @@ public class SpecifyTypeInDocstringIntention extends TypeIntention {
callable = getCallable(elementAt);
}
if (callable instanceof PyFunction) {
generateDocstring(kind, (PyFunction)callable, problemElement);
generateDocstring(parameter, (PyFunction)callable);
}
}
private static void generateDocstring(String kind, PyFunction pyFunction, PyExpression problemElement) {
private static void generateDocstring(@Nullable PyParameter param, PyFunction pyFunction) {
if (!DocStringUtil.ensureNotPlainDocstringFormat(pyFunction)) {
return;
}
final boolean isReturn = "rtype".equals(kind);
final PyDocstringGenerator docstringGenerator = PyDocstringGenerator.forDocStringOwner(pyFunction);
final PySignature signature = PySignatureCacheManager.getInstance(pyFunction.getProject()).findSignature(pyFunction);
final String name = isReturn ? "" : StringUtil.notNullize(problemElement.getName());
final String type;
if (signature != null) {
type = StringUtil.notNullize(signature.getArgTypeQualifiedName(name), "object");
}
else {
type = "object";
}
if (!isReturn) {
docstringGenerator.withParamTypedByName(name, type);
String type = "object";
if (param != null) {
final String paramName = StringUtil.notNullize(param.getName());
final PySignature signature = PySignatureCacheManager.getInstance(pyFunction.getProject()).findSignature(pyFunction);
if (signature != null) {
type = ObjectUtils.chooseNotNull(signature.getArgTypeQualifiedName(paramName), type);
}
final String docParamName = DocStringUtil.getPreferredParameterName(docstringGenerator.getDocStringFormat(), param);
docstringGenerator.withParamTypedByName(docParamName, type);
}
else {
docstringGenerator.withReturnValue(type);
@@ -31,6 +31,8 @@ import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static com.jetbrains.python.psi.PyUtil.as;
/**
* User: ktisha
*
@@ -98,10 +100,11 @@ public abstract class TypeIntention implements IntentionAction {
@Nullable
protected static PyParameter getParameter(PyExpression problemElement, PsiElement resolved) {
PyParameter parameter = problemElement instanceof PyParameter? (PyParameter)problemElement : null;
if (resolved instanceof PyParameter)
PyParameter parameter = as(problemElement, PyParameter.class);
if (resolved instanceof PyParameter) {
parameter = (PyParameter)resolved;
return parameter;
}
return parameter == null || parameter.isSelf() ? null : parameter;
}
private boolean isAvailableForReturn(@NotNull final PsiElement elementAt) {
@@ -306,4 +306,12 @@ public class DocStringUtil {
}
return true;
}
@NotNull
public static String getPreferredParameterName(@NotNull DocStringFormat format, @NotNull PyParameter parameter) {
if (format == DocStringFormat.GOOGLE && parameter.getAsNamed() != null) {
return parameter.getAsNamed().getRepr(false);
}
return StringUtil.notNullize(parameter.getName());
}
}
@@ -17,7 +17,6 @@ package com.jetbrains.python.documentation;
import com.intellij.openapi.util.Pair;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.toolbox.Substring;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -99,7 +98,7 @@ public class GoogleCodeStyleDocString extends SectionBasedDocString {
type = name;
name = null;
}
if (name != null ? !PyNames.isIdentifierString(name.toString()) : type.isEmpty()) {
if (name != null ? !isValidFieldName(name) : type.isEmpty()) {
return Pair.create(null, lineNum);
}
description = colonSeparatedParts.get(1);
@@ -16,7 +16,6 @@
package com.jetbrains.python.documentation;
import com.intellij.openapi.util.Pair;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.toolbox.Substring;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -83,7 +82,7 @@ public class NumpyDocString extends SectionBasedDocString {
type = name;
name = null;
}
if (name != null? !PyNames.isIdentifierString(name.toString()) : type.isEmpty()) {
if (name != null? !isValidFieldName(name) : type.isEmpty()) {
return Pair.create(null, lineNum);
}
final Pair<List<Substring>, Integer> parsedDescription = parseIndentedBlock(lineNum + 1, getLineIndentSize(lineNum));
@@ -170,12 +170,15 @@ public class PyDocstringGenerator {
public PyDocstringGenerator withInferredParameters(boolean alwaysAddReturn) {
if (myDocStringOwner instanceof PyFunction) {
for (PyParameter param : ((PyFunction)myDocStringOwner).getParameterList().getParameters()) {
if (param.getAsNamed() == null) {
continue;
}
final String paramName = param.getName();
final StructuredDocString docString = getStructuredDocString();
if (StringUtil.isEmpty(paramName) || param.isSelf() || docString != null && docString.getParameters().contains(paramName)) {
continue;
}
withParam(paramName);
withParam(DocStringUtil.getPreferredParameterName(getDocStringFormat(), param));
}
final RaiseVisitor visitor = new RaiseVisitor();
final PyStatementList statementList = ((PyFunction)myDocStringOwner).getStatementList();
@@ -29,6 +29,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
@@ -38,6 +39,7 @@ import java.util.regex.Pattern;
* @see <a href="http://sphinxcontrib-napoleon.readthedocs.org/en/latest/index.html">Napoleon</a>
*/
public abstract class SectionBasedDocString extends DocStringLineParser implements StructuredDocString {
protected static final Pattern FIELD_NAME = Pattern.compile("\\*{1,2}\\s*(\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)");
/**
* Frequently used section types
@@ -281,10 +283,10 @@ public abstract class SectionBasedDocString extends DocStringLineParser implemen
@NotNull
@Override
public List<String> getParameters() {
return ContainerUtil.map(getParameterSubstrings(), new Function<Substring, String>() {
return ContainerUtil.map(getParameterFields(), new Function<SectionField, String>() {
@Override
public String fun(Substring substring) {
return substring.toString();
public String fun(SectionField field) {
return field.getName();
}
});
}
@@ -521,6 +523,19 @@ public abstract class SectionBasedDocString extends DocStringLineParser implemen
return null;
}
@NotNull
protected static String extractFieldName(@NotNull CharSequence s) {
final Matcher matcher = FIELD_NAME.matcher(s);
if (matcher.matches()) {
return matcher.group(1);
}
return s.toString();
}
public static boolean isValidFieldName(@NotNull CharSequence s) {
return FIELD_NAME.matcher(s).matches();
}
public static class Section {
private final Substring myTitle;
private final List<SectionField> myFields;
@@ -585,7 +600,7 @@ public abstract class SectionBasedDocString extends DocStringLineParser implemen
@NotNull
public String getName() {
return myName == null ? "" : myName.toString();
return myName == null ? "" : extractFieldName(myName.toString());
}
@Nullable
@@ -15,10 +15,8 @@
*/
package com.jetbrains.python.inspections;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.intellij.codeInspection.LocalInspectionToolSession;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.lang.ASTNode;
@@ -28,8 +26,9 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.documentation.DocStringFormat;
import com.jetbrains.python.documentation.DocStringUtil;
import com.jetbrains.python.documentation.PlainDocString;
import com.jetbrains.python.inspections.quickfix.DocstringQuickFix;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.testing.PythonUnitTestUtil;
@@ -38,7 +37,9 @@ import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author Alexey.Ivanov
@@ -142,34 +143,24 @@ public class PyDocstringInspection extends PyInspection {
StructuredDocString docString = DocStringUtil.parse(text, node);
if (docString == null) {
return false;
}
List<Substring> docstringParams = docString.getParameterSubstrings();
if (docstringParams == null) {
if (docString instanceof PlainDocString) {
return false;
}
if (pyDocStringOwner instanceof PyFunction) {
PyDecoratorList decoratorList = ((PyFunction)pyDocStringOwner).getDecoratorList();
boolean isClassMethod = false;
if (decoratorList != null) {
isClassMethod = decoratorList.findDecorator(PyNames.CLASSMETHOD) != null;
}
PyParameter[] realParams = ((PyFunction)pyDocStringOwner).getParameterList().getParameters();
List<PyParameter> missingParams = getMissingParams(realParams, docstringParams, isClassMethod);
List<PyParameter> missingParams = getMissingParams(docString, realParams);
boolean registered = false;
if (!missingParams.isEmpty()) {
for (PyParameter param : missingParams) {
registerProblem(param, "Missing parameter " + param.getName() + " in docstring",
new DocstringQuickFix(param.getName(), null));
final DocStringFormat format = DocStringUtil.getConfiguredDocStringFormat(pyDocStringOwner);
final String docParamName = DocStringUtil.getPreferredParameterName(format, param);
registerProblem(param, "Missing parameter " + param.getName() + " in docstring", new DocstringQuickFix(docParamName, null));
}
registered = true;
}
List<Substring> unexpectedParams = getUnexpectedParams(docstringParams, realParams, node);
List<Substring> unexpectedParams = getUnexpectedParams(docString, realParams);
if (!unexpectedParams.isEmpty()) {
for (Substring param : unexpectedParams) {
ProblemsHolder holder = getHolder();
@@ -187,13 +178,11 @@ public class PyDocstringInspection extends PyInspection {
return false;
}
private static List<Substring> getUnexpectedParams(List<Substring> docstringParams,
PyParameter[] realParams,
PyStringLiteralExpression node) {
private static List<Substring> getUnexpectedParams(StructuredDocString docString, PyParameter[] realParams) {
Map<String, Substring> unexpected = Maps.newHashMap();
for (Substring s : docstringParams) {
unexpected.put(s.getValue(), s);
for (Substring s : docString.getParameterSubstrings()) {
unexpected.put(StringUtil.trimLeading(s.getValue(), '*'), s);
}
for (PyParameter p : realParams) {
@@ -204,26 +193,18 @@ public class PyDocstringInspection extends PyInspection {
return Lists.newArrayList(unexpected.values());
}
private static List<PyParameter> getMissingParams(PyParameter[] realParams, List<Substring> docstringParams, boolean isClassMethod) {
private static List<PyParameter> getMissingParams(StructuredDocString docString, PyParameter[] realParams) {
List<PyParameter> missing = new ArrayList<PyParameter>();
Set<String> params = Sets.newHashSet(Lists.transform(docstringParams, new Function<Substring, String>() {
@Override
public String apply(Substring input) {
return input.getValue();
}
}));
boolean hasMissing = false;
for (PyParameter p : realParams) {
String paramText = p.getText();
if ((!isClassMethod && !paramText.equals(PyNames.CANONICAL_SELF)) && !paramText.equals("*") ||
(isClassMethod && !paramText.equals("cls"))) {
if (!params.contains(p.getName())) {
hasMissing = true;
missing.add(p);
}
if (p.isSelf() || p instanceof PySingleStarParameter || p instanceof PyTupleParameter) {
continue;
}
//noinspection ConstantConditions
if (docString.getParameters().contains(p.getName())) {
missing.add(p);
}
}
return hasMissing ? missing : Collections.<PyParameter>emptyList();
return missing;
}
}
}
@@ -40,7 +40,6 @@ import org.jetbrains.annotations.Nullable;
public class DocstringQuickFix implements LocalQuickFix {
String myMissingText;
String myUnexpected;
String myPrefix;
public DocstringQuickFix(String missing, String unexpected) {
myMissingText = missing;
@@ -33,6 +33,7 @@ import com.intellij.util.containers.HashSet;
import com.intellij.util.containers.MultiMap;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PythonLanguage;
import com.jetbrains.python.documentation.DocStringUtil;
import com.jetbrains.python.documentation.PyDocstringGenerator;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.search.PyOverridingMethodsSearch;
@@ -339,7 +340,7 @@ public class PyChangeSignatureUsageProcessor implements ChangeSignatureUsageProc
for (PyParameter p : function.getParameterList().getParameters()) {
final String paramName = p.getName();
if (!names.contains(paramName) && paramName != null) {
generator.withoutParam(paramName);
generator.withoutParam(DocStringUtil.getPreferredParameterName(generator.getDocStringFormat(), p));
}
}
generator.buildAndInsert();