diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInDocstringIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInDocstringIntention.java index 55f4d0534764..fc3b191a1fd0 100644 --- a/python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInDocstringIntention.java +++ b/python/src/com/jetbrains/python/codeInsight/intentions/SpecifyTypeInDocstringIntention.java @@ -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); diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/TypeIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/TypeIntention.java index f5197ecc8ba7..78679a96f21f 100644 --- a/python/src/com/jetbrains/python/codeInsight/intentions/TypeIntention.java +++ b/python/src/com/jetbrains/python/codeInsight/intentions/TypeIntention.java @@ -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) { diff --git a/python/src/com/jetbrains/python/documentation/DocStringUtil.java b/python/src/com/jetbrains/python/documentation/DocStringUtil.java index 6fa1a3c415cb..6ebaf6a7328b 100644 --- a/python/src/com/jetbrains/python/documentation/DocStringUtil.java +++ b/python/src/com/jetbrains/python/documentation/DocStringUtil.java @@ -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()); + } } diff --git a/python/src/com/jetbrains/python/documentation/GoogleCodeStyleDocString.java b/python/src/com/jetbrains/python/documentation/GoogleCodeStyleDocString.java index 03fc40001614..a5bf2960e4ed 100644 --- a/python/src/com/jetbrains/python/documentation/GoogleCodeStyleDocString.java +++ b/python/src/com/jetbrains/python/documentation/GoogleCodeStyleDocString.java @@ -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); diff --git a/python/src/com/jetbrains/python/documentation/NumpyDocString.java b/python/src/com/jetbrains/python/documentation/NumpyDocString.java index ae8ae037894f..fd18f12bee0e 100644 --- a/python/src/com/jetbrains/python/documentation/NumpyDocString.java +++ b/python/src/com/jetbrains/python/documentation/NumpyDocString.java @@ -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, Integer> parsedDescription = parseIndentedBlock(lineNum + 1, getLineIndentSize(lineNum)); diff --git a/python/src/com/jetbrains/python/documentation/PyDocstringGenerator.java b/python/src/com/jetbrains/python/documentation/PyDocstringGenerator.java index ff70752b7e96..6f47959e40c2 100644 --- a/python/src/com/jetbrains/python/documentation/PyDocstringGenerator.java +++ b/python/src/com/jetbrains/python/documentation/PyDocstringGenerator.java @@ -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(); diff --git a/python/src/com/jetbrains/python/documentation/SectionBasedDocString.java b/python/src/com/jetbrains/python/documentation/SectionBasedDocString.java index 98a2d7e092fa..7c829a322894 100644 --- a/python/src/com/jetbrains/python/documentation/SectionBasedDocString.java +++ b/python/src/com/jetbrains/python/documentation/SectionBasedDocString.java @@ -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 Napoleon */ 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 getParameters() { - return ContainerUtil.map(getParameterSubstrings(), new Function() { + return ContainerUtil.map(getParameterFields(), new Function() { @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 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 diff --git a/python/src/com/jetbrains/python/inspections/PyDocstringInspection.java b/python/src/com/jetbrains/python/inspections/PyDocstringInspection.java index fad9046bcc88..3e2e8bf6c949 100644 --- a/python/src/com/jetbrains/python/inspections/PyDocstringInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyDocstringInspection.java @@ -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 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 missingParams = getMissingParams(realParams, docstringParams, isClassMethod); + List 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 unexpectedParams = getUnexpectedParams(docstringParams, realParams, node); + List 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 getUnexpectedParams(List docstringParams, - PyParameter[] realParams, - PyStringLiteralExpression node) { + private static List getUnexpectedParams(StructuredDocString docString, PyParameter[] realParams) { Map 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 getMissingParams(PyParameter[] realParams, List docstringParams, boolean isClassMethod) { + private static List getMissingParams(StructuredDocString docString, PyParameter[] realParams) { List missing = new ArrayList(); - Set params = Sets.newHashSet(Lists.transform(docstringParams, new Function() { - @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.emptyList(); + return missing; } } } diff --git a/python/src/com/jetbrains/python/inspections/quickfix/DocstringQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/DocstringQuickFix.java index 853b18760351..0af35cfb21e1 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/DocstringQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/DocstringQuickFix.java @@ -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; diff --git a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureUsageProcessor.java b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureUsageProcessor.java index 890f74f9dbb4..8af2df94f5ce 100644 --- a/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureUsageProcessor.java +++ b/python/src/com/jetbrains/python/refactoring/changeSignature/PyChangeSignatureUsageProcessor.java @@ -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();