blob: 9be15944f37ef2b2ed6ba7b0ac113bdd43b1c75c [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.validation;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.inspections.quickfix.*;
import com.jetbrains.python.psi.*;
import com.intellij.psi.util.QualifiedName;
import com.jetbrains.python.psi.impl.PyStringLiteralExpressionImpl;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* User : catherine
*/
public abstract class CompatibilityVisitor extends PyAnnotator {
protected List<LanguageLevel> myVersionsToProcess;
private String myCommonMessage = "Python version ";
private static final Map<LanguageLevel, Set<String>> AVAILABLE_PREFIXES = Maps.newHashMap();
static {
AVAILABLE_PREFIXES.put(LanguageLevel.PYTHON24, Sets.newHashSet("R", "U", "UR"));
AVAILABLE_PREFIXES.put(LanguageLevel.PYTHON25, Sets.newHashSet("R", "U", "UR"));
AVAILABLE_PREFIXES.put(LanguageLevel.PYTHON26, Sets.newHashSet("R", "U", "UR", "B", "BR"));
AVAILABLE_PREFIXES.put(LanguageLevel.PYTHON27, Sets.newHashSet("R", "U", "UR", "B", "BR"));
AVAILABLE_PREFIXES.put(LanguageLevel.PYTHON30, Sets.newHashSet("R", "B"));
AVAILABLE_PREFIXES.put(LanguageLevel.PYTHON31, Sets.newHashSet("R", "B", "BR"));
AVAILABLE_PREFIXES.put(LanguageLevel.PYTHON32, Sets.newHashSet("R", "B", "BR"));
AVAILABLE_PREFIXES.put(LanguageLevel.PYTHON33, Sets.newHashSet("R", "U", "B", "BR", "RB"));
AVAILABLE_PREFIXES.put(LanguageLevel.PYTHON34, Sets.newHashSet("R", "U", "B", "BR", "RB"));
}
public CompatibilityVisitor(List<LanguageLevel> versionsToProcess) {
myVersionsToProcess = versionsToProcess;
}
@Override
public void visitPyDictCompExpression(PyDictCompExpression node) {
super.visitPyDictCompExpression(node);
int len = 0;
StringBuilder message = new StringBuilder(myCommonMessage);
for (int i = 0; i != myVersionsToProcess.size(); ++i) {
LanguageLevel languageLevel = myVersionsToProcess.get(i);
if (!languageLevel.supportsSetLiterals()) {
len = appendLanguageLevel(message, len, languageLevel);
}
}
commonRegisterProblem(message, " not support dictionary comprehensions", len, node, new ConvertDictCompQuickFix(), false);
}
@Override
public void visitPySetLiteralExpression(PySetLiteralExpression node) {
super.visitPySetLiteralExpression(node);
int len = 0;
StringBuilder message = new StringBuilder(myCommonMessage);
for (int i = 0; i != myVersionsToProcess.size(); ++i) {
LanguageLevel languageLevel = myVersionsToProcess.get(i);
if (!languageLevel.supportsSetLiterals()) {
len = appendLanguageLevel(message, len, languageLevel);
}
}
commonRegisterProblem(message, " not support set literal expressions", len, node, new ConvertSetLiteralQuickFix(), false);
}
@Override
public void visitPySetCompExpression(PySetCompExpression node) {
super.visitPySetCompExpression(node);
int len = 0;
StringBuilder message = new StringBuilder(myCommonMessage);
for (int i = 0; i != myVersionsToProcess.size(); ++i) {
LanguageLevel languageLevel = myVersionsToProcess.get(i);
if (!languageLevel.supportsSetLiterals()) {
len = appendLanguageLevel(message, len, languageLevel);
}
}
commonRegisterProblem(message, " not support set comprehensions", len, node, null, false);
}
@Override
public void visitPyExceptBlock(PyExceptPart node) {
super.visitPyExceptBlock(node);
PyExpression exceptClass = node.getExceptClass();
if (exceptClass != null) {
if (myVersionsToProcess.contains(LanguageLevel.PYTHON24) || myVersionsToProcess.contains(LanguageLevel.PYTHON25)) {
PsiElement element = exceptClass.getNextSibling();
while (element instanceof PsiWhiteSpace) {
element = element.getNextSibling();
}
if (element != null && "as".equals(element.getText())) {
registerProblem(node, myCommonMessage + "2.4, 2.5 do not support this syntax.");
}
}
int len = 0;
StringBuilder message = new StringBuilder(myCommonMessage);
for (int i = 0; i != myVersionsToProcess.size(); ++i) {
LanguageLevel languageLevel = myVersionsToProcess.get(i);
if (languageLevel.isPy3K()) {
PsiElement element = exceptClass.getNextSibling();
while (element instanceof PsiWhiteSpace) {
element = element.getNextSibling();
}
if (element != null && ",".equals(element.getText())) {
len = appendLanguageLevel(message, len, languageLevel);
}
}
}
commonRegisterProblem(message, " not support this syntax.", len, node, new ReplaceExceptPartQuickFix());
}
}
@Override
public void visitPyImportStatement(PyImportStatement node) {
super.visitPyImportStatement(node);
PyIfStatement ifParent = PsiTreeUtil.getParentOfType(node, PyIfStatement.class);
if (ifParent != null)
return;
PyImportElement[] importElements = node.getImportElements();
int len = 0;
String moduleName = "";
StringBuilder message = new StringBuilder(myCommonMessage);
for (int i = 0; i != myVersionsToProcess.size(); ++i) {
LanguageLevel languageLevel = myVersionsToProcess.get(i);
for (PyImportElement importElement : importElements) {
final QualifiedName qName = importElement.getImportedQName();
if (qName != null) {
if (!languageLevel.isPy3K()) {
if (qName.matches("builtins")) {
len = appendLanguageLevel(message, len, languageLevel);
moduleName = "builtins";
}
}
else {
if (qName.matches("__builtin__")) {
len = appendLanguageLevel(message, len, languageLevel);
moduleName = "__builtin__";
}
}
}
}
}
commonRegisterProblem(message, " not have module " + moduleName, len, node, new ReplaceBuiltinsQuickFix());
}
@Override
public void visitPyStarExpression(PyStarExpression node) {
super.visitPyStarExpression(node);
int len = 0;
StringBuilder message = new StringBuilder(myCommonMessage);
for (int i = 0; i != myVersionsToProcess.size(); ++i) {
LanguageLevel languageLevel = myVersionsToProcess.get(i);
if (!languageLevel.isPy3K()) {
len = appendLanguageLevel(message, len, languageLevel);
}
}
commonRegisterProblem(message, " not support this syntax. Starred expressions are not allowed as assignment targets in Python 2",
len, node, null);
}
@Override
public void visitPyBinaryExpression(PyBinaryExpression node) {
super.visitPyBinaryExpression(node);
int len = 0;
if (node.isOperator("<>")) {
StringBuilder message = new StringBuilder(myCommonMessage);
for (int i = 0; i != myVersionsToProcess.size(); ++i) {
LanguageLevel languageLevel = myVersionsToProcess.get(i);
if (languageLevel.isPy3K()) {
len = appendLanguageLevel(message, len, languageLevel);
}
}
commonRegisterProblem(message, " not support <>, use != instead.", len, node, new ReplaceNotEqOperatorQuickFix());
}
}
@Override
public void visitPyNumericLiteralExpression(final PyNumericLiteralExpression node) {
super.visitPyNumericLiteralExpression(node);
int len = 0;
LocalQuickFix quickFix = null;
StringBuilder message = new StringBuilder(myCommonMessage);
String suffix = "";
for (int i = 0; i != myVersionsToProcess.size(); ++i) {
LanguageLevel languageLevel = myVersionsToProcess.get(i);
if (languageLevel.isPy3K()) {
if (!node.isIntegerLiteral()) {
continue;
}
final String text = node.getText();
if (text.endsWith("l") || text.endsWith("L")) {
len = appendLanguageLevel(message, len, languageLevel);
suffix = " not support a trailing \'l\' or \'L\'.";
quickFix = new RemoveTrailingLQuickFix();
}
if (text.length() > 1 && text.charAt(0) == '0') {
final char c = Character.toLowerCase(text.charAt(1));
if (c != 'o' && c != 'b' && c != 'x') {
boolean isNull = true;
for (char a : text.toCharArray()) {
if ( a != '0') {
isNull = false;
break;
}
}
if (!isNull) {
len = appendLanguageLevel(message, len, languageLevel);
quickFix = new ReplaceOctalNumericLiteralQuickFix();
suffix = " not support this syntax. It requires '0o' prefix for octal literals";
}
}
}
}
}
commonRegisterProblem(message, suffix, len, node, quickFix);
}
@Override
public void visitPyStringLiteralExpression(final PyStringLiteralExpression node) {
super.visitPyStringLiteralExpression(node);
List<ASTNode> stringNodes = node.getStringNodes();
for (ASTNode stringNode : stringNodes) {
int len = 0;
StringBuilder message = new StringBuilder(myCommonMessage);
String nodeText = stringNode.getText();
int index = PyStringLiteralExpressionImpl.getPrefixLength(nodeText);
String prefix = nodeText.substring(0, index).toUpperCase();
final TextRange range = TextRange.create(stringNode.getStartOffset(), stringNode.getStartOffset() + index);
for (int i = 0; i != myVersionsToProcess.size(); ++i) {
LanguageLevel languageLevel = myVersionsToProcess.get(i);
if (prefix.isEmpty()) continue;
final Set<String> prefixes = AVAILABLE_PREFIXES.get(languageLevel);
if (!prefixes.contains(prefix))
len = appendLanguageLevel(message, len, languageLevel);
}
commonRegisterProblem(message, " not support a '" + prefix + "' prefix", len, node, range, new RemovePrefixQuickFix(prefix));
}
}
@Override
public void visitPyListCompExpression(final PyListCompExpression node) {
super.visitPyListCompExpression(node);
int len = 0;
StringBuilder message = new StringBuilder(myCommonMessage);
for (int i = 0; i != myVersionsToProcess.size(); ++i) {
LanguageLevel languageLevel = myVersionsToProcess.get(i);
boolean tmp = UnsupportedFeaturesUtil.visitPyListCompExpression(node, languageLevel);
if (tmp) {
len = appendLanguageLevel(message, len, languageLevel);
}
}
for (ComprhForComponent forComponent : node.getForComponents()) {
final PyExpression iteratedList = forComponent.getIteratedList();
commonRegisterProblem(message, " not support this syntax in list comprehensions.", len, iteratedList,
new ReplaceListComprehensionsQuickFix());
}
}
@Override
public void visitPyRaiseStatement(PyRaiseStatement node) {
super.visitPyRaiseStatement(node);
// empty raise
int len = 0;
StringBuilder message = new StringBuilder(myCommonMessage);
for (int i = 0; i != myVersionsToProcess.size(); ++i) {
LanguageLevel languageLevel = myVersionsToProcess.get(i);
boolean hasNoArgs = UnsupportedFeaturesUtil.raiseHasNoArgs(node, languageLevel);
if (hasNoArgs) {
len = appendLanguageLevel(message, len, languageLevel);
}
}
commonRegisterProblem(message, " not support this syntax. Raise with no arguments can only be used in an except block",
len, node, null, false);
// raise 1, 2, 3
len = 0;
message = new StringBuilder(myCommonMessage);
for (int i = 0; i != myVersionsToProcess.size(); ++i) {
LanguageLevel languageLevel = myVersionsToProcess.get(i);
boolean hasTwoArgs = UnsupportedFeaturesUtil.raiseHasMoreThenOneArg(node, languageLevel);
if (hasTwoArgs) {
len = appendLanguageLevel(message, len, languageLevel);
}
}
commonRegisterProblem(message, " not support this syntax.",
len, node, new ReplaceRaiseStatementQuickFix());
// raise exception from cause
len = 0;
message = new StringBuilder(myCommonMessage);
for (int i = 0; i != myVersionsToProcess.size(); ++i) {
final LanguageLevel languageLevel = myVersionsToProcess.get(i);
final boolean hasFrom = UnsupportedFeaturesUtil.raiseHasFromKeyword(node, languageLevel);
if (hasFrom) {
len = appendLanguageLevel(message, len, languageLevel);
}
}
commonRegisterProblem(message, " not support this syntax.",
len, node, new ReplaceRaiseStatementQuickFix());
}
@Override
public void visitPyReprExpression(PyReprExpression node) {
super.visitPyReprExpression(node);
int len = 0;
StringBuilder message = new StringBuilder(myCommonMessage);
for (int i = 0; i != myVersionsToProcess.size(); ++i) {
LanguageLevel languageLevel = myVersionsToProcess.get(i);
if (languageLevel.isPy3K()) {
len = appendLanguageLevel(message, len, languageLevel);
}
}
commonRegisterProblem(message, " not support backquotes, use repr() instead",
len, node, new ReplaceBackquoteExpressionQuickFix());
}
@Override
public void visitPyWithStatement(PyWithStatement node) {
super.visitPyWithStatement(node);
Set<PyWithItem> problemItems = new HashSet<PyWithItem>();
StringBuilder message = new StringBuilder(myCommonMessage);
for (int i = 0; i != myVersionsToProcess.size(); ++i) {
LanguageLevel languageLevel = myVersionsToProcess.get(i);
if (languageLevel == LanguageLevel.PYTHON24) {
registerProblem(node, "Python version 2.4 doesn't support this syntax.");
}
else if (!languageLevel.supportsSetLiterals()) {
final PyWithItem[] items = node.getWithItems();
if (items.length > 1) {
for (int j = 1; j < items.length; j++) {
if (!problemItems.isEmpty())
message.append(", ");
message.append(languageLevel.toString());
problemItems.add(items [j]);
}
}
}
}
message.append(" do not support multiple context managers");
for (PyWithItem item : problemItems) {
registerProblem(item, message.toString());
}
}
@Override
public void visitPyClass(PyClass node) { //PY-2719
super.visitPyClass(node);
if (myVersionsToProcess.contains(LanguageLevel.PYTHON24)) {
PyArgumentList list = node.getSuperClassExpressionList();
if (list != null && list.getArguments().length == 0)
registerProblem(list, "Python version 2.4 does not support this syntax.");
}
}
@Override
public void visitPyPrintStatement(PyPrintStatement node) {
super.visitPyPrintStatement(node);
if (shouldBeCompatibleWithPy3()) {
boolean hasProblem = false;
PsiElement[] arguments = node.getChildren();
for (PsiElement element : arguments) {
if (!((element instanceof PyParenthesizedExpression) || (element instanceof PyTupleExpression))) {
hasProblem = true;
break;
}
}
if (hasProblem || arguments.length == 0)
registerProblem(node, "Python version >= 3.0 do not support this syntax. The print statement has been replaced with a print() function",
new CompatibilityPrintCallQuickFix());
}
}
@Override
public void visitPyFromImportStatement(PyFromImportStatement node) {
super.visitPyFromImportStatement(node);
PyReferenceExpression importSource = node.getImportSource();
if (importSource != null) {
if (myVersionsToProcess.contains(LanguageLevel.PYTHON24)) { //PY-2793
PsiElement prev = importSource.getPrevSibling();
if (prev != null && prev.getNode().getElementType() == PyTokenTypes.DOT)
registerProblem(node, "Python version 2.4 doesn't support this syntax.");
}
}
else {
if (myVersionsToProcess.contains(LanguageLevel.PYTHON24))
registerProblem(node, "Python version 2.4 doesn't support this syntax.");
}
}
@Override
public void visitPyAssignmentStatement(PyAssignmentStatement node) {
super.visitPyAssignmentStatement(node);
if (myVersionsToProcess.contains(LanguageLevel.PYTHON24)) {
PyExpression assignedValue = node.getAssignedValue();
Stack<PsiElement> st = new Stack<PsiElement>(); // PY-2796
if (assignedValue != null)
st.push(assignedValue);
while (!st.isEmpty()) {
PsiElement el = st.pop();
if (el instanceof PyYieldExpression)
registerProblem(node, "Python version 2.4 doesn't support this syntax. " +
"In Python <= 2.4, yield was a statement; it didn't return any value.");
else {
for (PsiElement e : el.getChildren())
st.push(e);
}
}
}
}
@Override
public void visitPyConditionalExpression(PyConditionalExpression node) { //PY-4293
super.visitPyConditionalExpression(node);
if (myVersionsToProcess.contains(LanguageLevel.PYTHON24)) {
registerProblem(node, "Python version 2.4 doesn't support this syntax.");
}
}
@Override
public void visitPyTryExceptStatement(PyTryExceptStatement node) { // PY-2795
super.visitPyTryExceptStatement(node);
if (myVersionsToProcess.contains(LanguageLevel.PYTHON24)) {
PyExceptPart[] excepts = node.getExceptParts();
PyFinallyPart finallyPart = node.getFinallyPart();
if (excepts.length != 0 && finallyPart != null)
registerProblem(node, "Python version 2.4 doesn't support this syntax. You could use a finally block to ensure " +
"that code is always executed, or one or more except blocks to catch specific exceptions.");
}
}
@Override
public void visitPyCallExpression(PyCallExpression node) {
super.visitPyCallExpression(node);
int len = 0;
StringBuilder message = new StringBuilder(myCommonMessage);
if (myVersionsToProcess.contains(LanguageLevel.PYTHON24) || myVersionsToProcess.contains(LanguageLevel.PYTHON25)) {
boolean hasStar = false;
for (PyExpression argument : node.getArguments()) {
if (hasStar && argument instanceof PyKeywordArgument) {
registerProblem(argument, "Python version < 2.6 doesn't support this syntax. Named parameter cannot appear past *arg or **kwarg.");
}
if (argument instanceof PyStarArgument) hasStar = true;
}
}
for (int i = 0; i != myVersionsToProcess.size(); ++i) {
LanguageLevel languageLevel = myVersionsToProcess.get(i);
if (!languageLevel.isPy3K()) {
final PsiElement firstChild = node.getFirstChild();
if (firstChild != null) {
final String name = firstChild.getText();
if (PyNames.SUPER.equals(name)) {
final PyArgumentList argumentList = node.getArgumentList();
if (argumentList != null && argumentList.getArguments().length == 0) {
len = appendLanguageLevel(message, len, languageLevel);
}
}
}
}
}
commonRegisterProblem(message, " not support this syntax. super() should have arguments in Python 2",
len, node, null);
}
@Override
public void visitPyYieldExpression(PyYieldExpression node) {
super.visitPyYieldExpression(node);
if (!node.isDelegating()) {
return;
}
for (LanguageLevel level : myVersionsToProcess) {
if (level.isOlderThan(LanguageLevel.PYTHON33)) {
registerProblem(node, "Python versions < 3.3 do not support this syntax. Delegating to a subgenerator is available since " +
"Python 3.3; use explicit iteration over subgenerator instead.");
break;
}
}
}
@Override
public void visitPyReturnStatement(PyReturnStatement node) {
boolean allowed = true;
for (LanguageLevel level : myVersionsToProcess) {
if (level.isOlderThan(LanguageLevel.PYTHON33)) {
allowed = false;
break;
}
}
if (allowed) {
return;
}
final PyFunction function = PsiTreeUtil.getParentOfType(node, PyFunction.class, false, PyClass.class);
if (function != null && node.getExpression() != null) {
final YieldVisitor visitor = new YieldVisitor();
function.acceptChildren(visitor);
if (visitor.haveYield()) {
registerProblem(node, "Python versions < 3.3 do not allow 'return' with argument inside generator.");
}
}
}
@Override
public void visitPyNoneLiteralExpression(PyNoneLiteralExpression node) {
if (shouldBeCompatibleWithPy2() && node.isEllipsis()) {
final PySubscriptionExpression subscription = PsiTreeUtil.getParentOfType(node, PySubscriptionExpression.class);
if (subscription != null && PsiTreeUtil.isAncestor(subscription.getIndexExpression(), node, false)) {
return;
}
final PySliceItem sliceItem = PsiTreeUtil.getParentOfType(node, PySliceItem.class);
if (sliceItem != null) {
return;
}
registerProblem(node, "Python versions < 3.0 do not support '...' outside of sequence slicings.");
}
}
private static class YieldVisitor extends PyElementVisitor {
private boolean _haveYield = false;
public boolean haveYield() {
return _haveYield;
}
@Override
public void visitPyYieldExpression(final PyYieldExpression node) {
_haveYield = true;
}
@Override
public void visitPyElement(final PyElement node) {
if (!_haveYield) {
node.acceptChildren(this);
}
}
@Override
public void visitPyFunction(final PyFunction node) {
// do not go to nested functions
}
}
protected abstract void registerProblem(PsiElement node, String s, @Nullable LocalQuickFix localQuickFix, boolean asError);
protected abstract void registerProblem(PsiElement node, TextRange range, String s, @Nullable LocalQuickFix localQuickFix, boolean asError);
protected void registerProblem(final PsiElement node, final String s, @Nullable final LocalQuickFix localQuickFix) {
registerProblem(node, s, localQuickFix, true);
}
protected void registerProblem(final PsiElement node, final String s) {
registerProblem(node, s, null);
}
protected void setVersionsToProcess(List<LanguageLevel> versionsToProcess) {
myVersionsToProcess = versionsToProcess;
}
protected void commonRegisterProblem(StringBuilder initMessage, String suffix,
int len, PyElement node, LocalQuickFix localQuickFix) {
commonRegisterProblem(initMessage, suffix, len, node, node.getTextRange(), localQuickFix, true);
}
protected void commonRegisterProblem(StringBuilder initMessage, String suffix,
int len, PyElement node, TextRange range, LocalQuickFix localQuickFix) {
commonRegisterProblem(initMessage, suffix, len, node, range, localQuickFix, true);
}
protected void commonRegisterProblem(StringBuilder initMessage, String suffix,
int len, PyElement node, TextRange range, @Nullable LocalQuickFix localQuickFix, boolean asError) {
initMessage.append(" do");
if (len == 1)
initMessage.append("es");
initMessage.append(suffix);
if (len != 0)
registerProblem(node, range, initMessage.toString(), localQuickFix, asError);
}
protected void commonRegisterProblem(StringBuilder initMessage, String suffix,
int len, PyElement node, @Nullable LocalQuickFix localQuickFix, boolean asError) {
initMessage.append(" do");
if (len == 1)
initMessage.append("es");
initMessage.append(suffix);
if (len != 0)
registerProblem(node, node.getTextRange(), initMessage.toString(), localQuickFix, asError);
}
protected static int appendLanguageLevel(StringBuilder message, int len, LanguageLevel languageLevel) {
if (len != 0)
message.append(", ");
message.append(languageLevel.toString());
return ++len;
}
@Override
public void visitPyNonlocalStatement(final PyNonlocalStatement node) {
if (shouldBeCompatibleWithPy2()) {
registerProblem(node, "nonlocal keyword available only since py3", null, false);
}
}
protected boolean shouldBeCompatibleWithPy2() {
for (LanguageLevel level : myVersionsToProcess) {
if (level.isOlderThan(LanguageLevel.PYTHON30)) {
return true;
}
}
return false;
}
protected boolean shouldBeCompatibleWithPy3() {
for (LanguageLevel level : myVersionsToProcess) {
if (level.isPy3K()) {
return true;
}
}
return false;
}
}