blob: 54f29e1a2fcbbc8825f0cadd31c626f49c439700 [file] [log] [blame]
/*
* Copyright 2000-2013 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.codeInspection.ui.MultipleCheckboxOptionsPanel;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.QualifiedName;
import com.intellij.util.containers.hash.HashMap;
import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache;
import com.jetbrains.python.codeInsight.dataflow.scope.Scope;
import com.jetbrains.python.inspections.quickfix.PyRenameElementQuickFix;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.search.PySuperMethodsSearch;
import com.jetbrains.python.testing.pytest.PyTestUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.Map;
import java.util.regex.Pattern;
/**
*
* User : ktisha
*/
public class PyPep8NamingInspection extends PyInspection {
public boolean ignoreOverriddenFunctions = true;
public boolean ignoreTestFunctions = false;
private static Pattern LOWERCASE_REGEX = Pattern.compile("[_\\p{javaLowerCase}][_\\p{javaLowerCase}0-9]*");
private static Pattern UPPERCASE_REGEX = Pattern.compile("[_\\p{javaUpperCase}][_\\p{javaUpperCase}0-9]*");
private static Pattern MIXEDCASE_REGEX = Pattern.compile("_?[\\p{javaUpperCase}][\\p{javaLowerCase}\\p{javaUpperCase}0-9]*");
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder,
boolean isOnTheFly,
@NotNull LocalInspectionToolSession session) {
if (ApplicationManager.getApplication().isUnitTestMode())
ignoreTestFunctions = true;
return new Visitor(holder, session);
}
public class Visitor extends PyInspectionVisitor {
private final Map<PyFunction, Boolean> myHasSupers = new HashMap<PyFunction, Boolean>();
public Visitor(@NotNull final ProblemsHolder holder, LocalInspectionToolSession session) {
super(holder, session);
}
@Override
public void visitPyAssignmentStatement(PyAssignmentStatement node) {
final PyFunction function = PsiTreeUtil.getParentOfType(node, PyFunction.class, true, PyClass.class);
if (function == null) return;
final Scope scope = ControlFlowCache.getScope(function);
for (PyExpression expression : node.getTargets()) {
final String name = expression.getName();
if (name == null || scope.isGlobal(name)) continue;
if (expression instanceof PyTargetExpression) {
final PyExpression qualifier = ((PyTargetExpression)expression).getQualifier();
if (qualifier != null) {
return;
}
}
if (!LOWERCASE_REGEX.matcher(name).matches() && !name.startsWith("_")) {
registerProblem(expression, "Variable in function should be lowercase", new PyRenameElementQuickFix());
}
}
}
@Override
public void visitPyParameter(PyParameter node) {
final String name = node.getName();
if (name == null) return;
if (!LOWERCASE_REGEX.matcher(name).matches()) {
registerProblem(node, "Argument name should be lowercase", new PyRenameElementQuickFix());
}
}
@Override
public void visitPyFunction(PyFunction node) {
final PyClass containingClass = node.getContainingClass();
if (hasSupers(node) && ignoreOverriddenFunctions) return;
if(containingClass != null && PyTestUtil.isPyTestClass(containingClass) && ignoreTestFunctions) return;
final String name = node.getName();
if (name == null) return;
if (containingClass != null && name.startsWith("__") && name.endsWith("__")) {
return;
}
if (!LOWERCASE_REGEX.matcher(name).matches()) {
final ASTNode nameNode = node.getNameNode();
if (nameNode != null)
registerProblem(nameNode.getPsi(), "Function name should be lowercase", new PyRenameElementQuickFix());
}
}
@Override
public void visitPyClass(PyClass node) {
final String name = node.getName();
if (name == null) return;
if (!MIXEDCASE_REGEX.matcher(name).matches()) {
final ASTNode nameNode = node.getNameNode();
if (nameNode != null)
registerProblem(nameNode.getPsi(), "Class names should use CamelCase convention", new PyRenameElementQuickFix());
}
}
@Override
public void visitPyImportElement(PyImportElement node) {
final String asName = node.getAsName();
final QualifiedName importedQName = node.getImportedQName();
if (importedQName == null) return;
final String name = importedQName.getLastComponent();
if (asName == null || name == null) return;
if (UPPERCASE_REGEX.matcher(name).matches()) {
if (!UPPERCASE_REGEX.matcher(asName).matches()) {
registerProblem(node.getAsNameElement(), "Constant variable imported as non constant", new PyRenameElementQuickFix());
}
}
else if (LOWERCASE_REGEX.matcher(name).matches()) {
if (!LOWERCASE_REGEX.matcher(asName).matches()) {
registerProblem(node.getAsNameElement(), "Lowercase variable imported as non lowercase", new PyRenameElementQuickFix());
}
}
else if (LOWERCASE_REGEX.matcher(asName).matches()) {
registerProblem(node.getAsNameElement(), "CamelCase variable imported as lowercase", new PyRenameElementQuickFix());
}
else if (UPPERCASE_REGEX.matcher(asName).matches()) {
registerProblem(node.getAsNameElement(), "CamelCase variable imported as constant", new PyRenameElementQuickFix());
}
}
private boolean hasSupers(@NotNull PyFunction function) {
final Boolean cached = myHasSupers.get(function);
if (cached != null) {
return cached;
}
final boolean result = PySuperMethodsSearch.search(function).findFirst() != null;
myHasSupers.put(function, result);
return result;
}
}
@Nullable
@Override
public JComponent createOptionsPanel() {
MultipleCheckboxOptionsPanel panel = new MultipleCheckboxOptionsPanel(this);
panel.addCheckbox("Ignore overridden functions", "ignoreOverriddenFunctions");
panel.addCheckbox("Ignore test functions", "ignoreTestFunctions");
return panel;
}
}