PY-17211 Split PyDocstringInspection in two

The first inspection PyIncorrectDocstringInspection checks that
docstring contains valid parameters and it's enabled by default.
Another - PyMissingOrEmptyDocstringInspection - ensures that
docstring exists for every suitable declaration. It often annoys users,
so it's turned off initially.

Additionally I moved messages from these inspections in PyBundle.
This commit is contained in:
Mikhail Golubev
2015-10-14 11:47:21 +03:00
parent abd1f2e8b3
commit 1d671fbcd7
18 changed files with 360 additions and 261 deletions
@@ -16,7 +16,8 @@
package org.jetbrains.plugins.ipnb;
import com.intellij.psi.PsiFile;
import com.jetbrains.python.inspections.PyDocstringInspection;
import com.jetbrains.python.inspections.PyIncorrectDocstringInspection;
import com.jetbrains.python.inspections.PyMissingOrEmptyDocstringInspection;
import com.jetbrains.python.inspections.PyStatementEffectInspection;
import com.jetbrains.python.inspections.PythonVisitorFilter;
import org.jetbrains.annotations.NotNull;
@@ -24,7 +25,9 @@ import org.jetbrains.annotations.NotNull;
public class IpnbVisitorFilter implements PythonVisitorFilter {
@Override
public boolean isSupported(@NotNull final Class visitorClass, @NotNull final PsiFile file) {
if (visitorClass == PyDocstringInspection.class || visitorClass == PyStatementEffectInspection.class) {
if (visitorClass == PyIncorrectDocstringInspection.class ||
visitorClass == PyMissingOrEmptyDocstringInspection.class ||
visitorClass == PyStatementEffectInspection.class) {
return false;
}
return true;
@@ -1,7 +0,0 @@
<html>
<body>
<span style="font-family: verdana,serif;">
This inspection detects mismatched parameters in docstring, lack of docstring and an empty docstring.
</span>
</body>
</html>
@@ -0,0 +1,7 @@
<html>
<body>
<span style="font-family: verdana,serif;">
This inspection detects mismatched parameters in docstring.
</span>
</body>
</html>
@@ -0,0 +1,7 @@
<html>
<body>
<span style="font-family: verdana,serif;">
This inspection detects lack of docstring and an empty docstring.
</span>
</body>
</html>
+2 -1
View File
@@ -321,7 +321,8 @@
<localInspection language="Python" shortName="PyExceptionInheritInspection" suppressId="PyExceptionInherit" bundle="com.jetbrains.python.PyBundle" key="INSP.NAME.exception.not.inherit" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WARNING" implementationClass="com.jetbrains.python.inspections.PyExceptionInheritInspection"/>
<localInspection language="Python" shortName="PyDefaultArgumentInspection" suppressId="PyDefaultArgument" bundle="com.jetbrains.python.PyBundle" key="INSP.NAME.default.argument" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WARNING" implementationClass="com.jetbrains.python.inspections.PyDefaultArgumentInspection"/>
<localInspection language="Python" shortName="PyRaisingNewStyleClassInspection" suppressId="PyRaisingNewStyleClass" bundle="com.jetbrains.python.PyBundle" key="INSP.NAME.raising.new.style.class" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WARNING" implementationClass="com.jetbrains.python.inspections.PyRaisingNewStyleClassInspection"/>
<localInspection language="Python" shortName="PyDocstringInspection" suppressId="PyDocstring" bundle="com.jetbrains.python.PyBundle" key="INSP.NAME.docstring" groupKey="INSP.GROUP.python" enabledByDefault="false" level="WEAK WARNING" implementationClass="com.jetbrains.python.inspections.PyDocstringInspection"/>
<localInspection language="Python" shortName="PyIncorrectDocstringInspection" suppressId="PyIncorrectDocstring" bundle="com.jetbrains.python.PyBundle" key="INSP.NAME.incorrect.docstring" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WEAK WARNING" implementationClass="com.jetbrains.python.inspections.PyIncorrectDocstringInspection"/>
<localInspection language="Python" shortName="PyMissingOrEmptyDocstringInspection" suppressId="PyMissingOrEmptyDocstring" bundle="com.jetbrains.python.PyBundle" key="INSP.NAME.missing.or.empty.docstring" groupKey="INSP.GROUP.python" enabledByDefault="false" level="WEAK WARNING" implementationClass="com.jetbrains.python.inspections.PyMissingOrEmptyDocstringInspection"/>
<localInspection language="Python" shortName="PyUnboundLocalVariableInspection" suppressId="PyUnboundLocalVariable" bundle="com.jetbrains.python.PyBundle" key="INSP.NAME.unbound" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WARNING" implementationClass="com.jetbrains.python.inspections.PyUnboundLocalVariableInspection"/>
<localInspection language="Python" shortName="PyStatementEffectInspection" suppressId="PyStatementEffect" bundle="com.jetbrains.python.PyBundle" key="INSP.NAME.statement.effect" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WARNING" implementationClass="com.jetbrains.python.inspections.PyStatementEffectInspection"/>
<localInspection language="Python" shortName="PySimplifyBooleanCheckInspection" suppressId="PySimplifyBooleanCheck" bundle="com.jetbrains.python.PyBundle" key="INSP.NAME.check.can.be.simplified" groupKey="INSP.GROUP.python" enabledByDefault="true" level="WEAK WARNING" implementationClass="com.jetbrains.python.inspections.PySimplifyBooleanCheckInspection"/>
@@ -414,8 +414,13 @@ INSP.NAME.default.argument=Default argument is mutable
# PyRaisingNewStyleClassInspection
INSP.NAME.raising.new.style.class=Raising a new style class
# PyDocstringInspection
INSP.NAME.docstring=Missing, empty or incorrect docstring
# PyIncorrectDocstringInspection
INSP.NAME.incorrect.docstring=Incorrect docstring
INSP.missing.parameter.in.docstring=Missing parameter {0} in docstring
INSP.unexpected.parameter.in.docstring=Unexpected parameter {0} in docstring
# PyMissingOrEmptyDocstringInspection
INSP.NAME.missing.or.empty.docstring=Missing or empty docstring
INSP.no.docstring=Missing docstring
INSP.empty.docstring=Empty docstring
@@ -33,7 +33,8 @@ public class ConsoleVisitorFilter implements PythonVisitorFilter {
//inspections
if (visitorClass == PyUnusedLocalInspection.class || visitorClass == PyUnboundLocalVariableInspection.class ||
visitorClass == PyStatementEffectInspection.class || visitorClass == PySingleQuotedDocstringInspection.class ||
visitorClass == PyDocstringInspection.class || visitorClass == PyMandatoryEncodingInspection.class) {
visitorClass == PyIncorrectDocstringInspection.class || visitorClass == PyMissingOrEmptyDocstringInspection.class ||
visitorClass == PyMandatoryEncodingInspection.class) {
return false;
}
@@ -38,12 +38,13 @@ public class PyDocstringVisitorFilter implements PythonVisitorFilter {
if (visitorClass == PyArgumentListInspection.class) {
return false;
}
if (visitorClass == PyDocstringInspection.class || visitorClass == PyStatementEffectInspection.class ||
if (visitorClass == PyIncorrectDocstringInspection.class || visitorClass == PyMissingOrEmptyDocstringInspection.class ||
visitorClass == PyUnboundLocalVariableInspection.class || visitorClass == PyUnnecessaryBackslashInspection.class ||
visitorClass == PyByteLiteralInspection.class || visitorClass == PyNonAsciiCharInspection.class ||
visitorClass == PyPackageRequirementsInspection.class || visitorClass == PyMandatoryEncodingInspection.class ||
visitorClass == PyInterpreterInspection.class || visitorClass == PyDocstringTypesInspection.class ||
visitorClass == PySingleQuotedDocstringInspection.class || visitorClass == PyClassHasNoInitInspection.class) {
visitorClass == PySingleQuotedDocstringInspection.class || visitorClass == PyClassHasNoInitInspection.class ||
visitorClass == PyStatementEffectInspection.class) {
return false;
}
//annotators
@@ -0,0 +1,77 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.inspections;
import com.intellij.codeInspection.LocalInspectionToolSession;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.openapi.extensions.Extensions;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.testing.PythonUnitTestUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Mikhail Golubev
*/
public abstract class PyBaseDocstringInspection extends PyInspection {
@NotNull
@Override
public abstract Visitor buildVisitor(@NotNull ProblemsHolder holder,
boolean isOnTheFly,
@NotNull LocalInspectionToolSession session);
protected static abstract class Visitor extends PyInspectionVisitor {
public Visitor(@Nullable ProblemsHolder holder, @NotNull LocalInspectionToolSession session) {
super(holder, session);
}
@Override
public void visitPyFile(@NotNull PyFile node) {
checkDocString(node);
}
@Override
public void visitPyFunction(@NotNull PyFunction node) {
if (PythonUnitTestUtil.isUnitTestCaseFunction(node)) return;
final PyClass containingClass = node.getContainingClass();
if (containingClass != null && PythonUnitTestUtil.isUnitTestCaseClass(containingClass)) return;
final Property property = node.getProperty();
if (property != null && (node == property.getSetter().valueOrNull() || node == property.getDeleter().valueOrNull())) {
return;
}
final String name = node.getName();
if (name != null && !name.startsWith("_")) checkDocString(node);
}
@Override
public void visitPyClass(@NotNull PyClass node) {
if (PythonUnitTestUtil.isUnitTestCaseClass(node)) return;
final String name = node.getName();
if (name == null || name.startsWith("_")) {
return;
}
checkDocString(node);
}
protected void checkDocString(@NotNull PyDocStringOwner node) {
for (PyInspectionExtension extension : Extensions.getExtensions(PyInspectionExtension.EP_NAME)) {
if (extension.ignoreMissingDocstring(node)) {
return;
}
}
}
}
}
@@ -1,213 +0,0 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.inspections;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.intellij.codeInspection.LocalInspectionToolSession;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.util.TextRange;
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.documentation.docstrings.DocStringUtil;
import com.jetbrains.python.documentation.docstrings.PlainDocString;
import com.jetbrains.python.inspections.quickfix.DocstringQuickFix;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.testing.PythonUnitTestUtil;
import com.jetbrains.python.toolbox.Substring;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author Alexey.Ivanov
*/
public class PyDocstringInspection extends PyInspection {
@Nls
@NotNull
@Override
public String getDisplayName() {
return PyBundle.message("INSP.NAME.docstring");
}
@Override
public boolean isEnabledByDefault() {
return false;
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder,
boolean isOnTheFly,
@NotNull LocalInspectionToolSession session) {
return new Visitor(holder, session);
}
public static class Visitor extends PyInspectionVisitor {
public Visitor(@Nullable ProblemsHolder holder, @NotNull LocalInspectionToolSession session) {
super(holder, session);
}
@Override
public void visitPyFile(@NotNull PyFile node) {
checkDocString(node);
}
@Override
public void visitPyFunction(@NotNull PyFunction node) {
if (PythonUnitTestUtil.isUnitTestCaseFunction(node)) return;
final PyClass containingClass = node.getContainingClass();
if (containingClass != null && PythonUnitTestUtil.isUnitTestCaseClass(containingClass)) return;
final Property property = node.getProperty();
if (property != null && (node == property.getSetter().valueOrNull() || node == property.getDeleter().valueOrNull())) {
return;
}
final String name = node.getName();
if (name != null && !name.startsWith("_")) checkDocString(node);
}
@Override
public void visitPyClass(@NotNull PyClass node) {
if (PythonUnitTestUtil.isUnitTestCaseClass(node)) return;
final String name = node.getName();
if (name == null || name.startsWith("_")) {
return;
}
for (PyInspectionExtension extension : Extensions.getExtensions(PyInspectionExtension.EP_NAME)) {
if (extension.ignoreMissingDocstring(node)) {
return;
}
}
checkDocString(node);
}
private void checkDocString(@NotNull PyDocStringOwner node) {
final PyStringLiteralExpression docStringExpression = node.getDocStringExpression();
if (docStringExpression == null) {
PsiElement marker = null;
if (node instanceof PyClass) {
final ASTNode n = ((PyClass)node).getNameNode();
if (n != null) marker = n.getPsi();
}
else if (node instanceof PyFunction) {
final ASTNode n = ((PyFunction)node).getNameNode();
if (n != null) marker = n.getPsi();
}
else if (node instanceof PyFile) {
final TextRange tr = new TextRange(0, 0);
final ProblemsHolder holder = getHolder();
if (holder != null) {
holder.registerProblem(node, tr, PyBundle.message("INSP.no.docstring"));
}
return;
}
if (marker == null) marker = node;
if (node instanceof PyFunction || (node instanceof PyClass && ((PyClass)node).findInitOrNew(false, null) != null)) {
registerProblem(marker, PyBundle.message("INSP.no.docstring"), new DocstringQuickFix(null, null));
}
else {
registerProblem(marker, PyBundle.message("INSP.no.docstring"));
}
}
else {
final boolean registered = checkParameters(node, docStringExpression);
if (!registered && StringUtil.isEmptyOrSpaces(docStringExpression.getStringValue())) {
registerProblem(docStringExpression, PyBundle.message("INSP.empty.docstring"));
}
}
}
private boolean checkParameters(@NotNull PyDocStringOwner pyDocStringOwner, @NotNull PyStringLiteralExpression node) {
final String text = node.getText();
if (text == null) {
return false;
}
final StructuredDocString docString = DocStringUtil.parse(text, node);
if (docString instanceof PlainDocString) {
return false;
}
if (pyDocStringOwner instanceof PyFunction) {
final PyParameter[] realParams = ((PyFunction)pyDocStringOwner).getParameterList().getParameters();
final List<PyNamedParameter> missingParams = getMissingParams(docString, realParams);
boolean registered = false;
if (!missingParams.isEmpty()) {
for (PyNamedParameter param : missingParams) {
registerProblem(param, "Missing parameter " + param.getName() + " in docstring", new DocstringQuickFix(param, null));
}
registered = true;
}
final List<Substring> unexpectedParams = getUnexpectedParams(docString, realParams);
if (!unexpectedParams.isEmpty()) {
for (Substring param : unexpectedParams) {
final ProblemsHolder holder = getHolder();
if (holder != null) {
holder.registerProblem(node, param.getTextRange(),
"Unexpected parameter " + param + " in docstring",
new DocstringQuickFix(null, param.getValue()));
}
}
registered = true;
}
return registered;
}
return false;
}
@NotNull
private static List<Substring> getUnexpectedParams(@NotNull StructuredDocString docString, @NotNull PyParameter[] realParams) {
final Map<String, Substring> unexpected = Maps.newHashMap();
for (Substring s : docString.getParameterSubstrings()) {
unexpected.put(s.toString(), s);
}
for (PyParameter p : realParams) {
if (unexpected.containsKey(p.getName())) {
unexpected.remove(p.getName());
}
}
return Lists.newArrayList(unexpected.values());
}
@NotNull
private static List<PyNamedParameter> getMissingParams(@NotNull StructuredDocString docString, @NotNull PyParameter[] realParams) {
final List<PyNamedParameter> missing = new ArrayList<PyNamedParameter>();
final List<String> docStringParameters = docString.getParameters();
for (PyParameter p : realParams) {
if (p.isSelf() || !(p instanceof PyNamedParameter)) {
continue;
}
if (!docStringParameters.contains(p.getName())) {
missing.add((PyNamedParameter)p);
}
}
return missing;
}
}
}
@@ -0,0 +1,130 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.inspections;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.intellij.codeInspection.LocalInspectionToolSession;
import com.intellij.codeInspection.ProblemsHolder;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.documentation.docstrings.DocStringUtil;
import com.jetbrains.python.documentation.docstrings.PlainDocString;
import com.jetbrains.python.inspections.quickfix.DocstringQuickFix;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.toolbox.Substring;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @author Mikhail Golubev
* @author Alexey.Ivanov
*/
public class PyIncorrectDocstringInspection extends PyBaseDocstringInspection {
@NotNull
@Override
public Visitor buildVisitor(@NotNull ProblemsHolder holder,
boolean isOnTheFly,
@NotNull LocalInspectionToolSession session) {
return new Visitor(holder, session) {
@Override
protected void checkDocString(@NotNull PyDocStringOwner node) {
super.checkDocString(node);
final PyStringLiteralExpression docStringExpression1 = node.getDocStringExpression();
if (docStringExpression1 != null) {
checkParameters(node, docStringExpression1);
}
}
private boolean checkParameters(@NotNull PyDocStringOwner pyDocStringOwner, @NotNull PyStringLiteralExpression node) {
final String text = node.getText();
if (text == null) {
return false;
}
final StructuredDocString docString = DocStringUtil.parse(text, node);
if (docString instanceof PlainDocString) {
return false;
}
if (pyDocStringOwner instanceof PyFunction) {
final PyParameter[] realParams = ((PyFunction)pyDocStringOwner).getParameterList().getParameters();
final List<PyNamedParameter> missingParams = getMissingParams(docString, realParams);
boolean registered = false;
if (!missingParams.isEmpty()) {
for (PyNamedParameter param : missingParams) {
registerProblem(param,
PyBundle.message("INSP.missing.parameter.in.docstring", param.getName()),
new DocstringQuickFix(param, null));
}
registered = true;
}
final List<Substring> unexpectedParams = getUnexpectedParams(docString, realParams);
if (!unexpectedParams.isEmpty()) {
for (Substring param : unexpectedParams) {
final ProblemsHolder holder = getHolder();
if (holder != null) {
holder.registerProblem(node, param.getTextRange(),
PyBundle.message("INSP.unexpected.parameter.in.docstring", param),
new DocstringQuickFix(null, param.getValue()));
}
}
registered = true;
}
return registered;
}
return false;
}
};
}
@NotNull
private static List<PyNamedParameter> getMissingParams(@NotNull StructuredDocString docString, @NotNull PyParameter[] realParams) {
final List<PyNamedParameter> missing = new ArrayList<PyNamedParameter>();
final List<String> docStringParameters = docString.getParameters();
for (PyParameter p : realParams) {
if (p.isSelf() || !(p instanceof PyNamedParameter)) {
continue;
}
if (!docStringParameters.contains(p.getName())) {
missing.add((PyNamedParameter)p);
}
}
return missing;
}
@NotNull
private static List<Substring> getUnexpectedParams(@NotNull StructuredDocString docString, @NotNull PyParameter[] realParams) {
final Map<String, Substring> unexpected = Maps.newHashMap();
for (Substring s : docString.getParameterSubstrings()) {
unexpected.put(s.toString(), s);
}
for (PyParameter p : realParams) {
if (unexpected.containsKey(p.getName())) {
unexpected.remove(p.getName());
}
}
return Lists.newArrayList(unexpected.values());
}
}
@@ -18,12 +18,13 @@ import java.util.regex.Pattern;
public class PyInspectionsSuppressor implements InspectionSuppressor {
private static final Pattern SUPPRESS_PATTERN = Pattern.compile(SuppressionUtil.COMMON_SUPPRESS_REGEXP);
private static final String PY_DOCSTRING_INSPECTION_ID = new PyDocstringInspection().getID();
private static final String PY_INCORRECT_DOCSTRING_INSPECTION_ID = new PyIncorrectDocstringInspection().getID();
private static final String PY_MISSING_OR_EMPTY_DOCSTRING_INSPECTION_ID = new PyMissingOrEmptyDocstringInspection().getID();
@NotNull
@Override
public SuppressQuickFix[] getSuppressActions(@Nullable PsiElement element, @NotNull String toolId) {
if (PY_DOCSTRING_INSPECTION_ID.equals(toolId)) {
if (PY_INCORRECT_DOCSTRING_INSPECTION_ID.equals(toolId) || PY_MISSING_OR_EMPTY_DOCSTRING_INSPECTION_ID.equals(toolId)) {
return new SuppressQuickFix[]{
new PySuppressInspectionFix(toolId, "Suppress for function", PyFunction.class),
new PySuppressInspectionFix(toolId, "Suppress for class", PyClass.class)
@@ -0,0 +1,75 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.inspections;
import com.intellij.codeInspection.LocalInspectionToolSession;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.inspections.quickfix.DocstringQuickFix;
import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.NotNull;
/**
* @author Mikhail Golubev
*/
public class PyMissingOrEmptyDocstringInspection extends PyBaseDocstringInspection {
@NotNull
@Override
public Visitor buildVisitor(@NotNull ProblemsHolder holder,
boolean isOnTheFly,
@NotNull LocalInspectionToolSession session) {
return new Visitor(holder, session) {
@Override
protected void checkDocString(@NotNull PyDocStringOwner node) {
super.checkDocString(node);
final PyStringLiteralExpression docStringExpression = node.getDocStringExpression();
if (docStringExpression == null) {
PsiElement marker = null;
if (node instanceof PyClass) {
final ASTNode n = ((PyClass)node).getNameNode();
if (n != null) marker = n.getPsi();
}
else if (node instanceof PyFunction) {
final ASTNode n = ((PyFunction)node).getNameNode();
if (n != null) marker = n.getPsi();
}
else if (node instanceof PyFile) {
final TextRange tr = new TextRange(0, 0);
final ProblemsHolder holder = getHolder();
if (holder != null) {
holder.registerProblem(node, tr, PyBundle.message("INSP.no.docstring"));
}
return;
}
if (marker == null) marker = node;
if (node instanceof PyFunction || (node instanceof PyClass && ((PyClass)node).findInitOrNew(false, null) != null)) {
registerProblem(marker, PyBundle.message("INSP.no.docstring"), new DocstringQuickFix(null, null));
}
else {
registerProblem(marker, PyBundle.message("INSP.no.docstring"));
}
}
else if (StringUtil.isEmptyOrSpaces(docStringExpression.getStringValue())) {
registerProblem(docStringExpression, PyBundle.message("INSP.empty.docstring"));
}
}
};
}
}
@@ -25,4 +25,14 @@
<line>13</line>
<description>Empty docstring</description>
</problem>
<problem>
<file>test.py</file>
<line>17</line>
<description>Missing docstring</description>
</problem>
<problem>
<file>test.py</file>
<line>19</line>
<description>Missing docstring</description>
</problem>
</problems>
@@ -11,4 +11,18 @@ class B:
def bar():
""""""
pass
pass
class C:
@property
def x(self):
return 42
@x.setter
def x(self, value):
pass
@x.deleter
def x(self):
pass
@@ -41,17 +41,4 @@ def foo(a, <weak_warning descr="Missing parameter c in docstring">c</weak_warnin
@return:
"""
pass
class <weak_warning descr="Missing docstring">C</weak_warning>:
@property
def <weak_warning descr="Missing docstring">x</weak_warning>(self):
return 42
@x.setter
def x(self, value):
pass
@x.deleter
def x(self):
pass
@@ -441,7 +441,7 @@ public class PyQuickFixTest extends PyTestCase {
getIndentOptions().INDENT_SIZE = 2;
runWithDocStringFormat(DocStringFormat.EPYTEXT, new Runnable() {
public void run() {
doInspectionTest(PyDocstringInspection.class, PyBundle.message("QFIX.docstring.add.$0", "b"), true, true);
doInspectionTest(PyIncorrectDocstringInspection.class, PyBundle.message("QFIX.docstring.add.$0", "b"), true, true);
}
});
}
@@ -450,7 +450,7 @@ public class PyQuickFixTest extends PyTestCase {
getIndentOptions().INDENT_SIZE = 2;
runWithDocStringFormat(DocStringFormat.EPYTEXT, new Runnable() {
public void run() {
doInspectionTest(PyDocstringInspection.class, PyBundle.message("QFIX.docstring.remove.$0", "c"), true, true);
doInspectionTest(PyIncorrectDocstringInspection.class, PyBundle.message("QFIX.docstring.remove.$0", "c"), true, true);
}
});
}
@@ -459,7 +459,7 @@ public class PyQuickFixTest extends PyTestCase {
public void testDocstringParams2() {
runWithDocStringFormat(DocStringFormat.EPYTEXT, new Runnable() {
public void run() {
doInspectionTest(PyDocstringInspection.class, PyBundle.message("QFIX.docstring.add.$0", "ham"), true, true);
doInspectionTest(PyIncorrectDocstringInspection.class, PyBundle.message("QFIX.docstring.add.$0", "ham"), true, true);
}
});
}
@@ -469,7 +469,7 @@ public class PyQuickFixTest extends PyTestCase {
runWithDocStringFormat(DocStringFormat.GOOGLE, new Runnable() {
@Override
public void run() {
doInspectionTest(PyDocstringInspection.class, PyBundle.message("QFIX.docstring.add.$0", "b"), true, true);
doInspectionTest(PyIncorrectDocstringInspection.class, PyBundle.message("QFIX.docstring.add.$0", "b"), true, true);
}
});
}
@@ -479,7 +479,7 @@ public class PyQuickFixTest extends PyTestCase {
runWithDocStringFormat(DocStringFormat.GOOGLE, new Runnable() {
@Override
public void run() {
doInspectionTest(PyDocstringInspection.class, PyBundle.message("QFIX.docstring.remove.$0", "c"), true, true);
doInspectionTest(PyIncorrectDocstringInspection.class, PyBundle.message("QFIX.docstring.remove.$0", "c"), true, true);
}
});
}
@@ -489,7 +489,7 @@ public class PyQuickFixTest extends PyTestCase {
runWithDocStringFormat(DocStringFormat.GOOGLE, new Runnable() {
@Override
public void run() {
doInspectionTest(PyDocstringInspection.class, PyBundle.message("QFIX.docstring.remove.$0", "c"), true, true);
doInspectionTest(PyIncorrectDocstringInspection.class, PyBundle.message("QFIX.docstring.remove.$0", "c"), true, true);
}
});
}
@@ -499,7 +499,7 @@ public class PyQuickFixTest extends PyTestCase {
runWithDocStringFormat(DocStringFormat.GOOGLE, new Runnable() {
@Override
public void run() {
doInspectionTest(PyDocstringInspection.class, PyBundle.message("QFIX.docstring.remove.$0", "args"), true, true);
doInspectionTest(PyIncorrectDocstringInspection.class, PyBundle.message("QFIX.docstring.remove.$0", "args"), true, true);
}
});
}
@@ -509,7 +509,7 @@ public class PyQuickFixTest extends PyTestCase {
runWithDocStringFormat(DocStringFormat.GOOGLE, new Runnable() {
@Override
public void run() {
doInspectionTest(PyDocstringInspection.class, PyBundle.message("QFIX.docstring.remove.$0", "kwargs"), true, true);
doInspectionTest(PyIncorrectDocstringInspection.class, PyBundle.message("QFIX.docstring.remove.$0", "kwargs"), true, true);
}
});
}
@@ -519,7 +519,7 @@ public class PyQuickFixTest extends PyTestCase {
runWithDocStringFormat(DocStringFormat.GOOGLE, new Runnable() {
@Override
public void run() {
doInspectionTest(PyDocstringInspection.class, PyBundle.message("QFIX.docstring.add.$0", "args"), true, true);
doInspectionTest(PyIncorrectDocstringInspection.class, PyBundle.message("QFIX.docstring.add.$0", "args"), true, true);
}
});
}
@@ -529,7 +529,7 @@ public class PyQuickFixTest extends PyTestCase {
runWithDocStringFormat(DocStringFormat.GOOGLE, new Runnable() {
@Override
public void run() {
doInspectionTest(PyDocstringInspection.class, PyBundle.message("QFIX.docstring.add.$0", "kwargs"), true, true);
doInspectionTest(PyIncorrectDocstringInspection.class, PyBundle.message("QFIX.docstring.add.$0", "kwargs"), true, true);
}
});
}
@@ -539,7 +539,7 @@ public class PyQuickFixTest extends PyTestCase {
runWithDocStringFormat(DocStringFormat.NUMPY, new Runnable() {
@Override
public void run() {
doInspectionTest(PyDocstringInspection.class, PyBundle.message("QFIX.docstring.remove.$0", "x"), true, true);
doInspectionTest(PyIncorrectDocstringInspection.class, PyBundle.message("QFIX.docstring.remove.$0", "x"), true, true);
}
});
}
@@ -549,7 +549,7 @@ public class PyQuickFixTest extends PyTestCase {
runWithDocStringFormat(DocStringFormat.NUMPY, new Runnable() {
@Override
public void run() {
doInspectionTest(PyDocstringInspection.class, PyBundle.message("QFIX.docstring.remove.$0", "y"), true, true);
doInspectionTest(PyIncorrectDocstringInspection.class, PyBundle.message("QFIX.docstring.remove.$0", "y"), true, true);
}
});
}
@@ -559,7 +559,7 @@ public class PyQuickFixTest extends PyTestCase {
runWithDocStringFormat(DocStringFormat.NUMPY, new Runnable() {
@Override
public void run() {
doInspectionTest(PyDocstringInspection.class, PyBundle.message("QFIX.docstring.remove.$0", "z"), true, true);
doInspectionTest(PyIncorrectDocstringInspection.class, PyBundle.message("QFIX.docstring.remove.$0", "z"), true, true);
}
});
}
@@ -569,7 +569,7 @@ public class PyQuickFixTest extends PyTestCase {
runWithDocStringFormat(DocStringFormat.NUMPY, new Runnable() {
@Override
public void run() {
doInspectionTest(PyDocstringInspection.class, PyBundle.message("QFIX.docstring.remove.$0", "args"), true, true);
doInspectionTest(PyIncorrectDocstringInspection.class, PyBundle.message("QFIX.docstring.remove.$0", "args"), true, true);
}
});
}
@@ -143,7 +143,7 @@ public class PythonInspectionsTest extends PyTestCase {
}
public void testPyDocstringInspection() {
LocalInspectionTool inspection = new PyDocstringInspection();
LocalInspectionTool inspection = new PyMissingOrEmptyDocstringInspection();
doTest(getTestName(false), inspection);
}
@@ -151,7 +151,7 @@ public class PythonInspectionsTest extends PyTestCase {
public void testPyDocstringParametersInspection() {
runWithDocStringFormat(DocStringFormat.EPYTEXT, new Runnable() {
public void run() {
doHighlightingTest(PyDocstringInspection.class, LanguageLevel.PYTHON33);
doHighlightingTest(PyIncorrectDocstringInspection.class, LanguageLevel.PYTHON33);
}
});
}
@@ -160,7 +160,7 @@ public class PythonInspectionsTest extends PyTestCase {
public void testGoogleDocstringParametersInspection() {
runWithDocStringFormat(DocStringFormat.GOOGLE, new Runnable() {
public void run() {
doHighlightingTest(PyDocstringInspection.class, LanguageLevel.PYTHON33);
doHighlightingTest(PyIncorrectDocstringInspection.class, LanguageLevel.PYTHON33);
}
});
}