diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/IpnbVisitorFilter.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/IpnbVisitorFilter.java index 46bcfdd5a850..add58bd89a00 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/IpnbVisitorFilter.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/IpnbVisitorFilter.java @@ -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; diff --git a/python/resources/inspectionDescriptions/PyDocstringInspection.html b/python/resources/inspectionDescriptions/PyDocstringInspection.html deleted file mode 100644 index f9f48e5e7204..000000000000 --- a/python/resources/inspectionDescriptions/PyDocstringInspection.html +++ /dev/null @@ -1,7 +0,0 @@ - - - - This inspection detects mismatched parameters in docstring, lack of docstring and an empty docstring. - - - \ No newline at end of file diff --git a/python/resources/inspectionDescriptions/PyIncorrectDocstringInspection.html b/python/resources/inspectionDescriptions/PyIncorrectDocstringInspection.html new file mode 100644 index 000000000000..8c99992a005c --- /dev/null +++ b/python/resources/inspectionDescriptions/PyIncorrectDocstringInspection.html @@ -0,0 +1,7 @@ + + + + This inspection detects mismatched parameters in docstring. + + + \ No newline at end of file diff --git a/python/resources/inspectionDescriptions/PyMissingOrEmptyDocstringInspection.html b/python/resources/inspectionDescriptions/PyMissingOrEmptyDocstringInspection.html new file mode 100644 index 000000000000..e17f82b55107 --- /dev/null +++ b/python/resources/inspectionDescriptions/PyMissingOrEmptyDocstringInspection.html @@ -0,0 +1,7 @@ + + + +This inspection detects lack of docstring and an empty docstring. + + + \ No newline at end of file diff --git a/python/src/META-INF/python-core.xml b/python/src/META-INF/python-core.xml index 9aef78114434..d343115692a7 100644 --- a/python/src/META-INF/python-core.xml +++ b/python/src/META-INF/python-core.xml @@ -321,7 +321,8 @@ - + + diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index 4a4bc7fa2b14..672864b648ac 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -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 diff --git a/python/src/com/jetbrains/python/console/ConsoleVisitorFilter.java b/python/src/com/jetbrains/python/console/ConsoleVisitorFilter.java index 83bed83570f5..23cc90f3c11a 100644 --- a/python/src/com/jetbrains/python/console/ConsoleVisitorFilter.java +++ b/python/src/com/jetbrains/python/console/ConsoleVisitorFilter.java @@ -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; } diff --git a/python/src/com/jetbrains/python/documentation/doctest/PyDocstringVisitorFilter.java b/python/src/com/jetbrains/python/documentation/doctest/PyDocstringVisitorFilter.java index 4f8d7496e693..29226b0d2759 100644 --- a/python/src/com/jetbrains/python/documentation/doctest/PyDocstringVisitorFilter.java +++ b/python/src/com/jetbrains/python/documentation/doctest/PyDocstringVisitorFilter.java @@ -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 diff --git a/python/src/com/jetbrains/python/inspections/PyBaseDocstringInspection.java b/python/src/com/jetbrains/python/inspections/PyBaseDocstringInspection.java new file mode 100644 index 000000000000..ae9f371fb401 --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/PyBaseDocstringInspection.java @@ -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; + } + } + } + } +} diff --git a/python/src/com/jetbrains/python/inspections/PyDocstringInspection.java b/python/src/com/jetbrains/python/inspections/PyDocstringInspection.java deleted file mode 100644 index e66cc2d0c731..000000000000 --- a/python/src/com/jetbrains/python/inspections/PyDocstringInspection.java +++ /dev/null @@ -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 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 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 getUnexpectedParams(@NotNull StructuredDocString docString, @NotNull PyParameter[] realParams) { - final Map 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 getMissingParams(@NotNull StructuredDocString docString, @NotNull PyParameter[] realParams) { - final List missing = new ArrayList(); - final List 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; - } - } -} diff --git a/python/src/com/jetbrains/python/inspections/PyIncorrectDocstringInspection.java b/python/src/com/jetbrains/python/inspections/PyIncorrectDocstringInspection.java new file mode 100644 index 000000000000..4d4359a796c5 --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/PyIncorrectDocstringInspection.java @@ -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 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 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 getMissingParams(@NotNull StructuredDocString docString, @NotNull PyParameter[] realParams) { + final List missing = new ArrayList(); + final List 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 getUnexpectedParams(@NotNull StructuredDocString docString, @NotNull PyParameter[] realParams) { + final Map 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()); + } +} diff --git a/python/src/com/jetbrains/python/inspections/PyInspectionsSuppressor.java b/python/src/com/jetbrains/python/inspections/PyInspectionsSuppressor.java index 545c1b37e46d..495577ae4cbb 100644 --- a/python/src/com/jetbrains/python/inspections/PyInspectionsSuppressor.java +++ b/python/src/com/jetbrains/python/inspections/PyInspectionsSuppressor.java @@ -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) diff --git a/python/src/com/jetbrains/python/inspections/PyMissingOrEmptyDocstringInspection.java b/python/src/com/jetbrains/python/inspections/PyMissingOrEmptyDocstringInspection.java new file mode 100644 index 000000000000..18dc367886ec --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/PyMissingOrEmptyDocstringInspection.java @@ -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")); + } + } + }; + } +} diff --git a/python/testData/inspections/PyDocstringInspection/expected.xml b/python/testData/inspections/PyDocstringInspection/expected.xml index 7b6e3c7d5079..a6288b4c4703 100644 --- a/python/testData/inspections/PyDocstringInspection/expected.xml +++ b/python/testData/inspections/PyDocstringInspection/expected.xml @@ -25,4 +25,14 @@ 13 Empty docstring + + test.py + 17 + Missing docstring + + + test.py + 19 + Missing docstring + \ No newline at end of file diff --git a/python/testData/inspections/PyDocstringInspection/src/test.py b/python/testData/inspections/PyDocstringInspection/src/test.py index 42a2cdf8cdf8..3c28c7aa380e 100644 --- a/python/testData/inspections/PyDocstringInspection/src/test.py +++ b/python/testData/inspections/PyDocstringInspection/src/test.py @@ -11,4 +11,18 @@ class B: def bar(): """""" - pass \ No newline at end of file + pass + + +class C: + @property + def x(self): + return 42 + + @x.setter + def x(self, value): + pass + + @x.deleter + def x(self): + pass \ No newline at end of file diff --git a/python/testData/inspections/PyDocstringParametersInspection/test.py b/python/testData/inspections/PyDocstringParametersInspection/test.py index 9e3e31e83b0a..3bd62cf93e11 100644 --- a/python/testData/inspections/PyDocstringParametersInspection/test.py +++ b/python/testData/inspections/PyDocstringParametersInspection/test.py @@ -41,17 +41,4 @@ def foo(a, cC: - @property - def x(self): - return 42 - - @x.setter - def x(self, value): - pass - - @x.deleter - def x(self): - pass \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java index 24c4b90f513b..c76a713877fb 100644 --- a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java +++ b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java @@ -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); } }); } diff --git a/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java b/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java index eebdd4e3262f..b85751460054 100644 --- a/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java +++ b/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java @@ -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); } }); }