PY-17211 Split PyDocstringInspection in two appcode/144.321 clion/144.320
authorMikhail Golubev <mikhail.golubev@jetbrains.com>
Tue, 13 Oct 2015 14:07:33 +0000 (17:07 +0300)
committerMikhail Golubev <mikhail.golubev@jetbrains.com>
Wed, 14 Oct 2015 08:47:21 +0000 (11:47 +0300)
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.

18 files changed:
python/ipnb/src/org/jetbrains/plugins/ipnb/IpnbVisitorFilter.java
python/resources/inspectionDescriptions/PyDocstringInspection.html [deleted file]
python/resources/inspectionDescriptions/PyIncorrectDocstringInspection.html [new file with mode: 0644]
python/resources/inspectionDescriptions/PyMissingOrEmptyDocstringInspection.html [new file with mode: 0644]
python/src/META-INF/python-core.xml
python/src/com/jetbrains/python/PyBundle.properties
python/src/com/jetbrains/python/console/ConsoleVisitorFilter.java
python/src/com/jetbrains/python/documentation/doctest/PyDocstringVisitorFilter.java
python/src/com/jetbrains/python/inspections/PyBaseDocstringInspection.java [new file with mode: 0644]
python/src/com/jetbrains/python/inspections/PyDocstringInspection.java [deleted file]
python/src/com/jetbrains/python/inspections/PyIncorrectDocstringInspection.java [new file with mode: 0644]
python/src/com/jetbrains/python/inspections/PyInspectionsSuppressor.java
python/src/com/jetbrains/python/inspections/PyMissingOrEmptyDocstringInspection.java [new file with mode: 0644]
python/testData/inspections/PyDocstringInspection/expected.xml
python/testData/inspections/PyDocstringInspection/src/test.py
python/testData/inspections/PyDocstringParametersInspection/test.py
python/testSrc/com/jetbrains/python/PyQuickFixTest.java
python/testSrc/com/jetbrains/python/PythonInspectionsTest.java

index 46bcfdd5a8506cab905dac110587871b64e4c6e1..add58bd89a00c21312925a8fe684b5646438619d 100644 (file)
@@ -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 (file)
index f9f48e5..0000000
+++ /dev/null
@@ -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>
\ No newline at end of file
diff --git a/python/resources/inspectionDescriptions/PyIncorrectDocstringInspection.html b/python/resources/inspectionDescriptions/PyIncorrectDocstringInspection.html
new file mode 100644 (file)
index 0000000..8c99992
--- /dev/null
@@ -0,0 +1,7 @@
+<html>
+<body>
+<span style="font-family: verdana,serif;">
+  This inspection detects mismatched parameters in docstring.
+</span>
+</body>
+</html>
\ No newline at end of file
diff --git a/python/resources/inspectionDescriptions/PyMissingOrEmptyDocstringInspection.html b/python/resources/inspectionDescriptions/PyMissingOrEmptyDocstringInspection.html
new file mode 100644 (file)
index 0000000..e17f82b
--- /dev/null
@@ -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>
\ No newline at end of file
index 9aef78114434e035ac26a501c4ca5c0f0ea0f84f..d343115692a7c1bc34ed534f6235b5513ad860dd 100644 (file)
     <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"/>
index 4a4bc7fa2b14165bb5f146308b32c1d7bd2297e4..672864b648ac58075972adb638be62b1f9107e46 100644 (file)
@@ -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
 
index 83bed83570f5be4096e4061fa71c11f7ecba805b..23cc90f3c11ae2bd12216cbd0733a21772dc1113 100644 (file)
@@ -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;
       }
 
index 4f8d7496e693deae433575005dda0d3a7f03808e..29226b0d2759c73252fff0947689ad077ece7061 100644 (file)
@@ -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 (file)
index 0000000..ae9f371
--- /dev/null
@@ -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 (file)
index e66cc2d..0000000
+++ /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<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;
-    }
-  }
-}
diff --git a/python/src/com/jetbrains/python/inspections/PyIncorrectDocstringInspection.java b/python/src/com/jetbrains/python/inspections/PyIncorrectDocstringInspection.java
new file mode 100644 (file)
index 0000000..4d4359a
--- /dev/null
@@ -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());
+  }
+}
index 545c1b37e46d832ec89cb609c1fc1e74827b5cb0..495577ae4cbbf3a26651304a1f99177ad5a764b3 100644 (file)
@@ -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 (file)
index 0000000..18dc367
--- /dev/null
@@ -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"));
+        }
+      }
+    };
+  }
+}
index 7b6e3c7d5079863682a27f8e9f4705f276b955cd..a6288b4c470363b8582f9a7354a965bb7faa1fef 100644 (file)
     <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>
\ No newline at end of file
index 42a2cdf8cdf842b54c5d0b12b0d8d1a0dc3a47fc..3c28c7aa380e6d094ab444af65fc05748dd4407b 100644 (file)
@@ -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
index 9e3e31e83b0a0567242acd8605adbdf0142bf4da..3bd62cf93e11eabb50810bd3dba980ca4b300ff8 100644 (file)
@@ -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
   
\ No newline at end of file
index 24c4b90f513b76525771de81dae43e7d5e29380f..c76a713877fbbf71fbf8d037782898da1db0cadc 100644 (file)
@@ -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);
       }
     });
   }
index eebdd4e3262f6a288882d0f9a43ed26ffaacc7e2..b8575146005472bdca0638e6cc8895374a5cf634 100644 (file)
@@ -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);
       }
     });
   }