PY-12360 Several improvements in pep8 external annotator

* Various E3xx errors about blank lines are reported on corresponding
PSI whitespaces, not at the following elements
* Do not report missing blank line at the end of file if
"Settings | Editor | Other | Ensure line feed on Save" is enabled
* It that option was not enabled report W292 error on the last element
in file
* Suppress pep8 errors about redundant blanks if they conflict with
preferences in Python's code style settings
This commit is contained in:
Mikhail Golubev
2015-04-23 17:45:58 +03:00
parent d66567deed
commit 77e7a79f8d
@@ -47,16 +47,22 @@ import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Consumer;
import com.intellij.util.IncorrectOperationException;
import com.jetbrains.python.PythonFileType;
import com.jetbrains.python.PythonHelpersLocator;
import com.jetbrains.python.PythonLanguage;
import com.jetbrains.python.codeInsight.imports.OptimizeImportsQuickFix;
import com.jetbrains.python.formatter.PyCodeStyleSettings;
import com.jetbrains.python.inspections.PyPep8Inspection;
import com.jetbrains.python.inspections.quickfix.ReformatFix;
import com.jetbrains.python.inspections.quickfix.RemoveTrailingBlankLinesFix;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.jetbrains.python.sdk.PreferredSdkComparator;
import com.jetbrains.python.sdk.PySdkUtil;
import com.jetbrains.python.sdk.PythonSdkType;
@@ -76,6 +82,7 @@ import java.util.regex.Pattern;
*/
public class Pep8ExternalAnnotator extends ExternalAnnotator<Pep8ExternalAnnotator.State, Pep8ExternalAnnotator.Results> {
private static final Logger LOG = Logger.getInstance(Pep8ExternalAnnotator.class);
private static final Pattern E303_LINE_COUNT_PATTERN = Pattern.compile(".*\\((\\d+)\\)$");
public static class Problem {
private final int myLine;
@@ -222,32 +229,41 @@ public class Pep8ExternalAnnotator extends ExternalAnnotator<Pep8ExternalAnnotat
final Document document = PsiDocumentManager.getInstance(project).getDocument(file);
for (Problem problem : annotationResult.problems) {
if (ignoreDueToSettings(project, problem)) continue;
final int line = problem.myLine - 1;
final int column = problem.myColumn - 1;
int offset;
if (document != null) {
offset = line >= document.getLineCount() ? document.getTextLength()-1 : document.getLineStartOffset(line) + column;
offset = line >= document.getLineCount() ? document.getTextLength() - 1 : document.getLineStartOffset(line) + column;
}
else {
offset = StringUtil.lineColToOffset(text, line, column);
}
PsiElement problemElement = file.findElementAt(offset);
if (!(problemElement instanceof PsiWhiteSpace) && !(problem.myCode.startsWith("E3"))) {
final PsiElement elementAfter = file.findElementAt(offset + 1);
if (elementAfter instanceof PsiWhiteSpace) {
problemElement = elementAfter;
// E3xx - blank lines warnings
if (!(problemElement instanceof PsiWhiteSpace) && problem.myCode.startsWith("E3")) {
final PsiElement elementBefore = file.findElementAt(Math.max(0, offset - 1));
if (elementBefore instanceof PsiWhiteSpace) {
problemElement = elementBefore;
}
}
// W292 no newline at end of file
if (problemElement == null && document != null && offset == document.getTextLength() && problem.myCode.equals("W292")) {
problemElement = file.findElementAt(Math.max(0, offset - 1));
}
if (ignoreDueToSettings(project, problem, problemElement)) {
continue;
}
if (problemElement != null) {
TextRange problemRange = problemElement.getTextRange();
if (crossesLineBoundary(document, text, problemRange)) {
int lineEndOffset;
if (!(problemElement instanceof PsiWhiteSpace) && crossesLineBoundary(document, text, problemRange)) {
final int lineEndOffset;
if (document != null) {
lineEndOffset = line >= document.getLineCount() ? document.getTextLength()-1 : document.getLineEndOffset(line);
lineEndOffset = line >= document.getLineCount() ? document.getTextLength() - 1 : document.getLineEndOffset(line);
}
else {
lineEndOffset = StringUtil.lineColToOffset(text, line+1, 0) - 1;
lineEndOffset = StringUtil.lineColToOffset(text, line + 1, 0) - 1;
}
if (offset > lineEndOffset) {
// PSI/document don't match, don't try to highlight random places
@@ -296,15 +312,48 @@ public class Pep8ExternalAnnotator extends ExternalAnnotator<Pep8ExternalAnnotat
return StringUtil.offsetToLineNumber(text, start) != StringUtil.offsetToLineNumber(text, end);
}
private static boolean ignoreDueToSettings(Project project, Problem problem) {
String stripTrailingSpaces = EditorSettingsExternalizable.getInstance().getStripTrailingSpaces();
if (!stripTrailingSpaces.equals(EditorSettingsExternalizable.STRIP_TRAILING_SPACES_NONE)) {
private static boolean ignoreDueToSettings(Project project, Problem problem, PsiElement element) {
final EditorSettingsExternalizable editorSettings = EditorSettingsExternalizable.getInstance();
if (!editorSettings.getStripTrailingSpaces().equals(EditorSettingsExternalizable.STRIP_TRAILING_SPACES_NONE)) {
// ignore trailing spaces errors if they're going to disappear after save
if (problem.myCode.equals("W291") || problem.myCode.equals("W293")) {
return true;
}
}
boolean useTabs = CodeStyleSettingsManager.getSettings(project).useTabCharacter(PythonFileType.INSTANCE);
// W292 no newline at end of file
if (editorSettings.isEnsureNewLineAtEOF() && problem.myCode.equals("W292")) {
return true;
}
final CodeStyleSettings codeStyleSettings = CodeStyleSettingsManager.getSettings(project);
final CommonCodeStyleSettings commonSettings = codeStyleSettings.getCommonSettings(PythonLanguage.getInstance());
final PyCodeStyleSettings pySettings = codeStyleSettings.getCustomSettings(PyCodeStyleSettings.class);
// E303 too many blank lines (num)
if (problem.myCode.equals("E303") && element instanceof PsiWhiteSpace) {
final Matcher matcher = E303_LINE_COUNT_PATTERN.matcher(problem.myDescription);
if (matcher.matches()) {
final int reportedBlanks = Integer.parseInt(matcher.group(1));
final PsiElement nonWhitespaceAfter = PyPsiUtils.getNextNonWhitespaceSibling(element);
final PsiElement nonWhitespaceBefore = PyPsiUtils.getPrevNonWhitespaceSibling(element);
final boolean classNearby = nonWhitespaceBefore instanceof PyClass || nonWhitespaceAfter instanceof PyClass;
final boolean functionNearby = nonWhitespaceBefore instanceof PyFunction || nonWhitespaceAfter instanceof PyFunction;
if (functionNearby || classNearby) {
if (PyUtil.isTopLevel(element)) {
if (reportedBlanks <= pySettings.BLANK_LINES_AROUND_TOP_LEVEL_CLASSES_FUNCTIONS) {
return true;
}
}
else {
// Blanks around classes have priority over blanks around functions as defined in Python spacing builder
if (classNearby && reportedBlanks <= commonSettings.BLANK_LINES_AROUND_CLASS ||
functionNearby && reportedBlanks <= commonSettings.BLANK_LINES_AROUND_METHOD) {
return true;
}
}
}
}
}
final boolean useTabs = codeStyleSettings.useTabCharacter(PythonFileType.INSTANCE);
if (useTabs && problem.myCode.equals("W191")) {
return true;
}