diff --git a/python/src/com/jetbrains/python/actions/RemoveTrailingLQuickFix.java b/python/src/com/jetbrains/python/actions/RemoveTrailingLQuickFix.java new file mode 100644 index 000000000000..3d8f52945305 --- /dev/null +++ b/python/src/com/jetbrains/python/actions/RemoveTrailingLQuickFix.java @@ -0,0 +1,39 @@ +package com.jetbrains.python.actions; + +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.psi.PyElementGenerator; +import com.jetbrains.python.psi.PyNumericLiteralExpression; +import org.jetbrains.annotations.NotNull; + +/** + * Created by IntelliJ IDEA. + * Author: Alexey.Ivanov + * Date: 06.03.2010 + * Time: 16:50:53 + */ +public class RemoveTrailingLQuickFix implements LocalQuickFix { + @NotNull + @Override + public String getName() { + return PyBundle.message("INTN.remove.trailing.l"); + } + + @NotNull + public String getFamilyName() { + return PyBundle.message("INTN.Family.migration.to.python3"); + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PsiElement numericLiteralExpression = descriptor.getPsiElement(); + if (numericLiteralExpression instanceof PyNumericLiteralExpression) { + PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); + String text = numericLiteralExpression.getText(); + numericLiteralExpression.replace(elementGenerator.createExpressionFromText(text.substring(0, text.length() - 1))); + } + } +} diff --git a/python/src/com/jetbrains/python/actions/ReplaceBuiltinsQuickFix.java b/python/src/com/jetbrains/python/actions/ReplaceBuiltinsQuickFix.java index 2cb4227c9465..b3bd943835fe 100644 --- a/python/src/com/jetbrains/python/actions/ReplaceBuiltinsQuickFix.java +++ b/python/src/com/jetbrains/python/actions/ReplaceBuiltinsQuickFix.java @@ -34,14 +34,11 @@ public class ReplaceBuiltinsQuickFix implements LocalQuickFix { for (PyImportElement importElement : ((PyImportStatement)importStatement).getImportElements()) { PyReferenceExpression importReference = importElement.getImportReference(); if (importReference != null) { - if (LanguageLevel.forFile(importStatement.getContainingFile().getVirtualFile()).isPy3K()) { - if ("__builtin__".equals(importReference.getName())) { - importReference.replace(elementGenerator.createFromText(LanguageLevel.getDefault(), PyReferenceExpression.class, "builtins")); - } - } else { - if ("builtins".equals(importReference.getName())) { - importReference.replace(elementGenerator.createFromText(LanguageLevel.getDefault(), PyReferenceExpression.class, "__builtin__")); - } + if ("__builtin__".equals(importReference.getName())) { + importReference.replace(elementGenerator.createFromText(LanguageLevel.getDefault(), PyReferenceExpression.class, "builtins")); + } + if ("builtins".equals(importReference.getName())) { + importReference.replace(elementGenerator.createFromText(LanguageLevel.getDefault(), PyReferenceExpression.class, "__builtin__")); } } } diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/RemoveTrailingLIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/RemoveTrailingLIntention.java deleted file mode 100644 index 364c1e716ecb..000000000000 --- a/python/src/com/jetbrains/python/codeInsight/intentions/RemoveTrailingLIntention.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.jetbrains.python.codeInsight.intentions; - -import com.intellij.codeInsight.intention.IntentionAction; -import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiFile; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.util.IncorrectOperationException; -import com.jetbrains.python.PyBundle; -import com.jetbrains.python.PythonLanguage; -import com.jetbrains.python.psi.PyElementGenerator; -import com.jetbrains.python.psi.PyNumericLiteralExpression; -import org.jetbrains.annotations.NotNull; - -/** - * Created by IntelliJ IDEA. - * Author: Alexey.Ivanov - * Date: 06.03.2010 - * Time: 16:50:53 - */ -public class RemoveTrailingLIntention implements IntentionAction { - @NotNull - public String getText() { - return PyBundle.message("INTN.remove.trailing.l"); - } - - @NotNull - public String getFamilyName() { - return PyBundle.message("INTN.Family.migration.to.python3"); - } - - public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { - return true; - } - - public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { - PyNumericLiteralExpression numericLiteralExpression = - PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyNumericLiteralExpression.class); - if (numericLiteralExpression == null) { - return; - } - PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project); - String text = numericLiteralExpression.getText(); - numericLiteralExpression.replace(elementGenerator.createExpressionFromText(text.substring(0, text.length() - 1))); - } - - public boolean startInWriteAction() { - return true; - } -} diff --git a/python/src/com/jetbrains/python/inspections/PyCompatibilityInspection.java b/python/src/com/jetbrains/python/inspections/PyCompatibilityInspection.java index 049d16a37893..d5a4d89afdbc 100644 --- a/python/src/com/jetbrains/python/inspections/PyCompatibilityInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyCompatibilityInspection.java @@ -1,20 +1,17 @@ package com.jetbrains.python.inspections; +import com.intellij.codeInspection.LocalQuickFix; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.roots.ProjectFileIndex; -import com.intellij.openapi.roots.ProjectRootManager; -import com.intellij.psi.*; -import com.intellij.util.containers.HashSet; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiElementVisitor; import com.jetbrains.python.PyBundle; -import com.jetbrains.python.PyNames; -import com.jetbrains.python.PyTokenTypes; -import com.jetbrains.python.actions.*; -import com.jetbrains.python.psi.*; -import com.jetbrains.python.psi.impl.PyQualifiedName; +import com.jetbrains.python.psi.LanguageLevel; +import com.jetbrains.python.validation.CompatibilityVisitor; import com.jetbrains.python.validation.UnsupportedFeaturesUtil; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; @@ -22,8 +19,6 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.List; -import java.util.Set; -import java.util.Stack; /** * User: catherine @@ -111,521 +106,23 @@ public class PyCompatibilityInspection extends PyInspection { return new Visitor(holder, myVersionsToProcess); } - - private static class Visitor extends PyInspectionVisitor { - List myVersionsToProcess; - private String myCommonMessage = "Python versions "; - - public Visitor(final ProblemsHolder holder, List versionsToProcess) { - super(holder); - myVersionsToProcess = versionsToProcess; + private static class Visitor extends CompatibilityVisitor { + private final ProblemsHolder myHolder; + public Visitor(ProblemsHolder holder, List versionsToProcess) { + super(versionsToProcess); + myHolder = holder; } - @Override - public void visitPyDictCompExpression(PyDictCompExpression node) { - super.visitPyDictCompExpression(node); - boolean hasProblem = false; - StringBuilder message = new StringBuilder(myCommonMessage); - for (int i = 0; i != myVersionsToProcess.size(); ++i) { - LanguageLevel languageLevel = myVersionsToProcess.get(i); - if (!languageLevel.supportsSetLiterals()) { - if (hasProblem) - message.append(", "); - message.append(languageLevel.toString()); - hasProblem = true; - } - } - message.append(" do not support dictionary comprehensions"); - if (hasProblem) - registerProblem(node, message.toString(), new ConvertDictCompQuickFix()); - } - - @Override - public void visitPySetLiteralExpression(PySetLiteralExpression node) { - super.visitPySetLiteralExpression(node); - boolean hasProblem = false; - StringBuilder message = new StringBuilder(myCommonMessage); - for (int i = 0; i != myVersionsToProcess.size(); ++i) { - LanguageLevel languageLevel = myVersionsToProcess.get(i); - if (!languageLevel.supportsSetLiterals()) { - if (hasProblem) - message.append(", "); - message.append(languageLevel.toString()); - hasProblem = true; - } - } - message.append(" do not support set literal expressions"); - if (hasProblem) - registerProblem(node, message.toString(), new ConvertSetLiteralQuickFix()); - } - - @Override - public void visitPySetCompExpression(PySetCompExpression node) { - super.visitPySetCompExpression(node); - boolean hasProblem = false; - StringBuilder message = new StringBuilder(myCommonMessage); - for (int i = 0; i != myVersionsToProcess.size(); ++i) { - LanguageLevel languageLevel = myVersionsToProcess.get(i); - if (!languageLevel.supportsSetLiterals()) { - if (hasProblem) - message.append(", "); - message.append(languageLevel.toString()); - hasProblem = true; - } - } - message.append(" do not support set comprehensions"); - if (hasProblem) - registerProblem(node, message.toString()); - } - - @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."); - } - } - - boolean hasProblem = false; - 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())) { - if (hasProblem) - message.append(", "); - message.append(languageLevel.toString()); - hasProblem = true; - } - } - } - message.append(" do not this syntax."); - if (hasProblem) - registerProblem(node, message.toString(), new ReplaceExceptPartQuickFix()); - } - } - - @Override - public void visitPyImportStatement(PyImportStatement node) { - super.visitPyImportStatement(node); - PyImportElement[] importElements = node.getImportElements(); - boolean hasProblem = false; - StringBuilder message = new StringBuilder(myCommonMessage); - for (int i = 0; i != myVersionsToProcess.size(); ++i) { - LanguageLevel languageLevel = myVersionsToProcess.get(i); - for (PyImportElement importElement : importElements) { - final PyQualifiedName qName = importElement.getImportedQName(); - if (qName != null) { - if (!languageLevel.isPy3K()) { - if (qName.matches("builtins")) { - if (hasProblem) - message.append(", "); - message.append(languageLevel.toString()); - message.append(" do not have module builtins."); - hasProblem = true; - } - } - else { - if (qName.matches("__builtin__")) { - if (hasProblem) - message.append(", "); - message.append(languageLevel.toString()); - message.append(" do not have module __builtin__."); - hasProblem = true; - } - } - } - } - } - if (hasProblem) - registerProblem(node, message.toString(), new ReplaceBuiltinsQuickFix()); - } - - @Override - public void visitPyStarExpression(PyStarExpression node) { - super.visitPyStarExpression(node); - boolean hasProblem = false; - StringBuilder message = new StringBuilder(myCommonMessage); - for (int i = 0; i != myVersionsToProcess.size(); ++i) { - LanguageLevel languageLevel = myVersionsToProcess.get(i); - if (!languageLevel.isPy3K()) { - if (hasProblem) - message.append(", "); - message.append(languageLevel.toString()); - hasProblem = true; - } - } - message.append(" do not support this syntax. Starred expressions are not allowed as assignment targets in Python 2"); - if (hasProblem) - registerProblem(node, message.toString()); - } - - @Override - public void visitPyBinaryExpression(PyBinaryExpression node) { - super.visitPyBinaryExpression(node); - boolean hasProblem = false; - if (node.isOperator("<>")) { - StringBuilder message = new StringBuilder(myCommonMessage); - for (int i = 0; i != myVersionsToProcess.size(); ++i) { - LanguageLevel level = myVersionsToProcess.get(i); - if (level.isPy3K()) { - if (hasProblem) - message.append(", "); - message.append(level.toString()); - hasProblem = true; - } - } - message.append(" do not support <>, use != instead."); - if (hasProblem) - registerProblem(node, message.toString(), new ReplaceNotEqOperatorQuickFix()); - } - } - - @Override - public void visitPyNumericLiteralExpression(final PyNumericLiteralExpression node) { - super.visitPyNumericLiteralExpression(node); - boolean hasProblem = false; - StringBuilder message = new StringBuilder(myCommonMessage); - 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")) { - message.append(languageLevel.toString()).append(" "); - hasProblem = true; - } - 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) { - if (hasProblem) - message.append(", "); - message.append(languageLevel.toString()); - hasProblem = true; - } - } - } - } - } - message.append(" do not support a trailing \'l\' or \'L\'."); - if (hasProblem) - registerProblem(node, message.toString(), new ReplaceOctalNumericLiteralQuickFix()); - } - - @Override - public void visitPyStringLiteralExpression(final PyStringLiteralExpression node) { - super.visitPyStringLiteralExpression(node); - boolean hasProblem = false; - StringBuilder message = new StringBuilder(myCommonMessage); - for (int i = 0; i != myVersionsToProcess.size(); ++i) { - LanguageLevel languageLevel = myVersionsToProcess.get(i); - - if (languageLevel.isPy3K()) { - final String text = node.getText(); - if (text.startsWith("u") || text.startsWith("U")) { - if (hasProblem) - message.append(", "); - message.append(languageLevel.toString()); - hasProblem = true; - } - } - } - message.append(" do not a leading \'u\' or \'U\'."); - if (hasProblem) - registerProblem(node, message.toString(), new RemoveLeadingUQuickFix()); - } - - public void visitPyListCompExpression(final PyListCompExpression node) { - super.visitPyListCompExpression(node); - boolean hasProblem = false; - 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) { - if (hasProblem) - message.append(", "); - message.append(languageLevel); - hasProblem = true; - } - } - message.append(" do not support this syntax in list comprehensions."); - for (ComprhForComponent forComponent : node.getForComponents()) { - final PyExpression iteratedList = forComponent.getIteratedList(); - if (hasProblem) - registerProblem(iteratedList, message.toString(), new ReplaceListComprehensionsQuickFix()); - } - } - - @Override - public void visitPyRaiseStatement(PyRaiseStatement node) { - super.visitPyRaiseStatement(node); - boolean hasProblem = false; - 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) { - message.append(languageLevel).append(" "); - hasProblem = true; - } - } - message.append(" do not support this syntax. Raise with no arguments can only be used in an except block"); - if (hasProblem) - registerProblem(node, message.toString()); - - hasProblem = false; - 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) { - if (hasProblem) - message.append(", "); - message.append(languageLevel); - hasProblem = true; - } - } - message.append(" do not support this syntax."); - if (hasProblem) - registerProblem(node, message.toString(), new ReplaceRaiseStatementQuickFix()); - } - - @Override - public void visitPyReprExpression(PyReprExpression node) { - super.visitPyReprExpression(node); - boolean hasProblem = false; - StringBuilder message = new StringBuilder(myCommonMessage); - for (int i = 0; i != myVersionsToProcess.size(); ++i) { - LanguageLevel languageLevel = myVersionsToProcess.get(i); - if (languageLevel.isPy3K()) { - if (hasProblem) - message.append(", "); - message.append(languageLevel); - hasProblem = true; - } - } - message.append(" do not support backquotes, use repr() instead"); - if (hasProblem) - registerProblem(node, message.toString(), new ReplaceBackquoteExpressionQuickFix()); - } - - @Override - public void visitPyWithStatement(PyWithStatement node) { - super.visitPyWithStatement(node); - Set problemItems = new HashSet(); - 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 visitPyCallExpression(PyCallExpression node) { - super.visitPyCallExpression(node); - boolean hasProblem = false; - StringBuilder message = new StringBuilder(myCommonMessage); - 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) { - if (hasProblem) - message.append(", "); - message.append(languageLevel.toString()); - hasProblem = true; - } - } - } - } - } - if (hasProblem) - registerProblem(node, message.toString()); - - hasProblem = false; - message = new StringBuilder(myCommonMessage); - for (int i = 0; i != myVersionsToProcess.size(); ++i) { - LanguageLevel languageLevel = myVersionsToProcess.get(i); - - PyExpression callee = node.getCallee(); - assert callee != null; - PsiReference reference = callee.getReference(); - if (reference != null) { - PsiElement resolved = reference.resolve(); - ProjectFileIndex ind = ProjectRootManager.getInstance(callee.getProject()).getFileIndex(); - if (resolved != null) { - PsiFile file = resolved.getContainingFile(); - if (file != null && ind.isInLibraryClasses(file.getVirtualFile())) { - final String name = callee.getText(); - if (!name.equals("print") && UnsupportedFeaturesUtil.BUILTINS.get(languageLevel).contains(name)) { - if (hasProblem) - message.append(", "); - hasProblem = true; - message.append(languageLevel.toString()); - } - } - } - } - } - if (hasProblem) { - message.append(" have no method ").append(node.getCallee().getText()); - registerProblem(node, message.toString()); - } - } - - @Override - public void visitPyClass(PyClass node) { //PY-2719 - 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) { - if (myVersionsToProcess.contains(LanguageLevel.PYTHON30) || myVersionsToProcess.contains(LanguageLevel.PYTHON31)) { - 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 versions >= 3.0 do not support this syntax. The print statement has been replaced with a print() function", - new CompatibilityPrintCallQuickFix()); - } - } - - @Override - public void visitPyImportElement(PyImportElement node) { - boolean hasProblem = false; - StringBuilder message = new StringBuilder(myCommonMessage); - for (int i = 0; i != myVersionsToProcess.size(); ++i) { - LanguageLevel languageLevel = myVersionsToProcess.get(i); - if (UnsupportedFeaturesUtil.MODULES.get(languageLevel).contains(node.getText())) { - if (hasProblem) - message.append(", "); - message.append(languageLevel.toString()); - hasProblem = true; - } - } - message.append(" do not have module ").append(node.getText()); - if (hasProblem) - registerProblem(node, message.toString()); - } - - @Override - public void visitPyFromImportStatement(PyFromImportStatement node) { - boolean hasProblem = false; - StringBuilder message = new StringBuilder(myCommonMessage); - 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."); - } - - String name = importSource.getText(); - for (int i = 0; i != myVersionsToProcess.size(); ++i) { - LanguageLevel languageLevel = myVersionsToProcess.get(i); - if (UnsupportedFeaturesUtil.MODULES.get(languageLevel).contains(name)) { - if (hasProblem) - message.append(", "); - message.append(languageLevel.toString()); - hasProblem = true; - } - } - message.append(" do not have module ").append(name); - if (hasProblem) - registerProblem(node, message.toString()); - } - else { - if (myVersionsToProcess.contains(LanguageLevel.PYTHON24)) - registerProblem(node, "Python version 2.4 doesn't support this syntax."); - } - } - - @Override - public void visitPyAssignmentStatement(PyAssignmentStatement node) { - if (myVersionsToProcess.contains(LanguageLevel.PYTHON24)) { - PyExpression assignedValue = node.getAssignedValue(); - if (assignedValue instanceof PyConditionalExpression) // PY-2792 - registerProblem(node, "Python version 2.4 doesn't support this syntax."); - - Stack st = new Stack(); // 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 visitPyTryExceptStatement(PyTryExceptStatement node) { // PY-2795 - 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."); + protected final void registerProblem(@Nullable final PsiElement element, + @NotNull final String message, + final LocalQuickFix quickFix, boolean asError){ + if (element == null || element.getTextLength() == 0){ + return; } + if (quickFix != null) + myHolder.registerProblem(element, message, quickFix); + else + myHolder.registerProblem(element, message); } } } diff --git a/python/src/com/jetbrains/python/validation/CompatibilityVisitor.java b/python/src/com/jetbrains/python/validation/CompatibilityVisitor.java new file mode 100644 index 000000000000..afe522fc9c6d --- /dev/null +++ b/python/src/com/jetbrains/python/validation/CompatibilityVisitor.java @@ -0,0 +1,514 @@ +package com.jetbrains.python.validation; + +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.openapi.roots.ProjectFileIndex; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiReference; +import com.intellij.psi.PsiWhiteSpace; +import com.jetbrains.python.PyNames; +import com.jetbrains.python.PyTokenTypes; +import com.jetbrains.python.actions.*; +import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.impl.PyQualifiedName; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.Stack; + +/** + * User : catherine + */ +public abstract class CompatibilityVisitor extends PyAnnotator { + protected List myVersionsToProcess; + private String myCommonMessage = "Python version "; + + public CompatibilityVisitor(List 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); + 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 PyQualifiedName 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); + int len = 0; + StringBuilder message = new StringBuilder(myCommonMessage); + for (int i = 0; i != myVersionsToProcess.size(); ++i) { + LanguageLevel languageLevel = myVersionsToProcess.get(i); + + if (languageLevel.isPy3K()) { + final String text = node.getText(); + if (text.startsWith("u") || text.startsWith("U")) { + len = appendLanguageLevel(message, len, languageLevel); + } + } + } + commonRegisterProblem(message, " not support a leading \'u\' or \'U\'.", len, node, new RemoveLeadingUQuickFix()); + } + + @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); + 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); + + 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()); + } + + @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 problemItems = new HashSet(); + 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 (myVersionsToProcess.contains(LanguageLevel.PYTHON30) || myVersionsToProcess.contains(LanguageLevel.PYTHON31)) { + 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); + int len = 0; + StringBuilder message = new StringBuilder(myCommonMessage); + 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."); + } + + String name = importSource.getText(); + for (int i = 0; i != myVersionsToProcess.size(); ++i) { + LanguageLevel languageLevel = myVersionsToProcess.get(i); + if (UnsupportedFeaturesUtil.MODULES.get(languageLevel).contains(name)) { + len = appendLanguageLevel(message, len, languageLevel); + } + } + commonRegisterProblem(message, " not have module " + name, + len, node, null, false); + } + 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(); + if (assignedValue instanceof PyConditionalExpression) // PY-2792 + registerProblem(node, "Python version 2.4 doesn't support this syntax."); + + Stack st = new Stack(); // 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 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); + 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); + len = 0; + message = new StringBuilder(myCommonMessage); + for (int i = 0; i != myVersionsToProcess.size(); ++i) { + LanguageLevel languageLevel = myVersionsToProcess.get(i); + PyExpression callee = node.getCallee(); + assert callee != null; + PsiReference reference = callee.getReference(); + if (reference != null) { + PsiElement resolved = reference.resolve(); + ProjectFileIndex ind = ProjectRootManager.getInstance(callee.getProject()).getFileIndex(); + final String name = callee.getText(); + if (resolved != null) { + PsiFile file = resolved.getContainingFile(); + if (file != null && ind.isInLibraryClasses(file.getVirtualFile())) { + if (!name.equals("print") && UnsupportedFeaturesUtil.BUILTINS.get(languageLevel).contains(name)) { + len = appendLanguageLevel(message, len, languageLevel); + } + } + } + else { + if (!name.equals("print") && UnsupportedFeaturesUtil.BUILTINS.get(languageLevel).contains(name)) { + len = appendLanguageLevel(message, len, languageLevel); + } + } + } + } + commonRegisterProblem(message, " not have method " + node.getCallee().getText(), + len, node, null, false); + } + + @Override + public void visitPyImportElement(PyImportElement node) { + super.visitPyImportElement(node); + int len = 0; + StringBuilder message = new StringBuilder(myCommonMessage); + for (int i = 0; i != myVersionsToProcess.size(); ++i) { + LanguageLevel languageLevel = myVersionsToProcess.get(i); + if (UnsupportedFeaturesUtil.MODULES.get(languageLevel).contains(node.getText()) && + !node.getText().equals("__builtin__") && !node.getText().equals("builtins")) { + len = appendLanguageLevel(message, len, languageLevel); + } + } + commonRegisterProblem(message, " not have module " + node.getText(), len, node, null, false); + } + + + protected abstract void registerProblem(PsiElement node, String s, LocalQuickFix localQuickFix, boolean asError); + + protected void registerProblem(PsiElement node, String s, LocalQuickFix localQuickFix) { + registerProblem(node, s, localQuickFix, true); + } + + protected void registerProblem(PsiElement node, String s) { + registerProblem(node, s, null); + } + + protected void setVersionsToProcess(List versionsToProcess) { + myVersionsToProcess = versionsToProcess; + } + + protected void commonRegisterProblem(StringBuilder initMessage, String suffix, + int len, PyElement node, LocalQuickFix localQuickFix) { + commonRegisterProblem(initMessage, suffix, len, node, localQuickFix, true); + } + + protected void commonRegisterProblem(StringBuilder initMessage, String suffix, + int len, PyElement node, LocalQuickFix localQuickFix, boolean asError) { + initMessage.append(" do"); + if (len == 1) + initMessage.append("es"); + initMessage.append(suffix); + if (len != 0) + registerProblem(node, initMessage.toString(), localQuickFix, asError); + } + + private static int appendLanguageLevel(StringBuilder message, int len, LanguageLevel languageLevel) { + if (len != 0) + message.append(", "); + message.append(languageLevel.toString()); + return ++len; + } +} diff --git a/python/src/com/jetbrains/python/validation/UnsupportedFeatures.java b/python/src/com/jetbrains/python/validation/UnsupportedFeatures.java index 38d6ad3a845b..00de71d63e9c 100644 --- a/python/src/com/jetbrains/python/validation/UnsupportedFeatures.java +++ b/python/src/com/jetbrains/python/validation/UnsupportedFeatures.java @@ -9,24 +9,44 @@ import com.intellij.codeInspection.ex.ProblemDescriptorImpl; import com.intellij.codeInspection.ex.QuickFixWrapper; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiReference; -import com.intellij.psi.PsiWhiteSpace; -import com.jetbrains.python.PyBundle; -import com.jetbrains.python.PyNames; -import com.jetbrains.python.PyTokenTypes; -import com.jetbrains.python.actions.*; -import com.jetbrains.python.codeInsight.intentions.RemoveTrailingLIntention; -import com.jetbrains.python.psi.*; -import com.jetbrains.python.psi.impl.PyQualifiedName; +import com.jetbrains.python.psi.LanguageLevel; +import com.jetbrains.python.psi.PyElement; import org.jetbrains.annotations.NotNull; -import java.util.List; -import java.util.Stack; +import java.util.ArrayList; +import java.util.Arrays; /** * @author Alexey.Ivanov */ -public class UnsupportedFeatures extends PyAnnotator { +public class UnsupportedFeatures extends CompatibilityVisitor { + + public UnsupportedFeatures() { + super(new ArrayList()); + } + + @Override + public void visitPyElement(PyElement node) { + setVersionsToProcess(Arrays.asList(getLanguageLevel(node))); + } + + @Override + protected void registerProblem(PsiElement node, String message, LocalQuickFix localQuickFix, boolean asError) { + if (node == null || node.getTextLength() == 0){ + return; + } + if (localQuickFix != null) + if (asError) + getHolder().createErrorAnnotation(node, message).registerFix(createIntention(node, message, localQuickFix)); + else + getHolder().createWarningAnnotation(node, message).registerFix(createIntention(node, message, localQuickFix)); + else + if (asError) + getHolder().createErrorAnnotation(node, message); + else + getHolder().createWarningAnnotation(node, message); + } + @NotNull private static LanguageLevel getLanguageLevel(PyElement node) { VirtualFile virtualFile = node.getContainingFile().getVirtualFile(); @@ -36,321 +56,11 @@ public class UnsupportedFeatures extends PyAnnotator { return LanguageLevel.getDefault(); } - private static boolean isPy2(PyElement node) { - return !getLanguageLevel(node).isPy3K(); - } - - private static boolean isPy3K(PyElement node) { - return getLanguageLevel(node).isPy3K(); - } - - private static IntentionAction createIntention(PyElement node, String message, LocalQuickFix fix) { + private static IntentionAction createIntention(PsiElement node, String message, LocalQuickFix fix) { LocalQuickFix[] quickFixes = {fix}; CommonProblemDescriptorImpl descr = new ProblemDescriptorImpl(node, node, message, quickFixes, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, true, node.getTextRange(), true); return QuickFixWrapper.wrap((ProblemDescriptor)descr, 0); } - - @Override - public void visitPyDictCompExpression(PyDictCompExpression node) { - super.visitPyDictCompExpression(node); - LanguageLevel languageLevel = getLanguageLevel(node); - if (!languageLevel.supportsSetLiterals()) { - String message = "Python version " + languageLevel + " does not support dictionary comprehensions"; - getHolder() - .createWarningAnnotation(node, message) - .registerFix(createIntention(node, message, new ConvertDictCompQuickFix())); - } - } - - @Override - public void visitPySetLiteralExpression(PySetLiteralExpression node) { - super.visitPySetLiteralExpression(node); - LanguageLevel languageLevel = getLanguageLevel(node); - if (!languageLevel.supportsSetLiterals()) { - String message = "Python version " + languageLevel + " does not support set literal expressions"; - getHolder() - .createWarningAnnotation(node, message) - .registerFix(createIntention(node, message, new ConvertSetLiteralQuickFix())); - } - } - - @Override - public void visitPySetCompExpression(PySetCompExpression node) { - super.visitPySetCompExpression(node); - final LanguageLevel languageLevel = getLanguageLevel(node); - if (!languageLevel.supportsSetLiterals()) { - getHolder().createWarningAnnotation(node, "Python version " + languageLevel + " does not support set comprehensions"); - } - } - - @Override - public void visitPyExceptBlock(PyExceptPart node) { - super.visitPyExceptBlock(node); - PyExpression exceptClass = node.getExceptClass(); - if (exceptClass != null) { - LanguageLevel languageLevel = getLanguageLevel(node); - if (languageLevel == LanguageLevel.PYTHON24 || languageLevel == LanguageLevel.PYTHON25) { - PsiElement element = exceptClass.getNextSibling(); - while (element instanceof PsiWhiteSpace) { - element = element.getNextSibling(); - } - if (element != null && "as".equals(element.getText())) { - getHolder().createWarningAnnotation(node, "This Python version does not support this syntax"); - } - } - else if (isPy3K(node)) { - PsiElement element = exceptClass.getNextSibling(); - while (element instanceof PsiWhiteSpace) { - element = element.getNextSibling(); - } - if (element != null && ",".equals(element.getText())) { - String message = "Python 3 does not support this syntax"; - getHolder().createWarningAnnotation(node, message).registerFix(createIntention(node, message, new ReplaceExceptPartQuickFix())); - } - } - } - } - - @Override - public void visitPyImportStatement(PyImportStatement node) { - super.visitPyImportStatement(node); - PyImportElement[] importElements = node.getImportElements(); - for (PyImportElement importElement : importElements) { - final PyQualifiedName qName = importElement.getImportedQName(); - if (qName != null) { - if (isPy2(node)) { - if (qName.matches("builtins")) { - String message = "There is no module builtins in Python 2"; - getHolder().createWarningAnnotation(node, message) - .registerFix(createIntention(node, message, new ReplaceBuiltinsQuickFix())); - } - } - else { - if (qName.matches("__builtin__")) { - String message = "Module __builtin__ renamed to builtins"; - getHolder().createWarningAnnotation(node, "Module __builtin__ renamed to builtins") - .registerFix(createIntention(node, message, new ReplaceBuiltinsQuickFix())); - } - } - } - } - } - - @Override - public void visitPyCallExpression(PyCallExpression node) { - super.visitPyCallExpression(node); - if (isPy2(node)) { - 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) { - getHolder().createWarningAnnotation(node, "super() should have arguments in Python 2"); - } - } - } - } - else { - PyExpression callee = node.getCallee(); - assert callee != null; - PsiReference reference = callee.getReference(); - if (reference != null) { - PsiElement resolved = reference.resolve(); - if (resolved == null) { - final String name = callee.getText(); - if (!name.equals("print") && UnsupportedFeaturesUtil.BUILTINS.get(getLanguageLevel(callee)).contains(name)) { - getHolder().createWarningAnnotation(callee, PyBundle.message("ANN.method.$0.removed", name)); - } - } - } - } - } - - @Override - public void visitPyStarExpression(PyStarExpression node) { - super.visitPyStarExpression(node); - if (isPy2(node)) { - getHolder().createWarningAnnotation(node, "Starred expressions are not allowed as assignment targets in Python 2"); - } - } - - @Override - public void visitPyBinaryExpression(PyBinaryExpression node) { - super.visitPyBinaryExpression(node); - if (isPy3K(node) && node.isOperator("<>")) { - final String message = isPy3K(node) ? "<> is not supported in Python 3, use != instead" : "<> is deprecated, use != instead"; - getHolder().createWarningAnnotation(node, message).registerFix(createIntention(node, message, new ReplaceNotEqOperatorQuickFix())); - } - } - - @Override - public void visitPyNumericLiteralExpression(final PyNumericLiteralExpression node) { - super.visitPyNumericLiteralExpression(node); - if (isPy3K(node)) { - if (!node.isIntegerLiteral()) { - return; - } - final String text = node.getText(); - if (text.endsWith("l") || text.endsWith("L")) { - getHolder().createWarningAnnotation(node, - "Integer literals do not support a trailing \'l\' or \'L\' in Python 3") - .registerFix(new RemoveTrailingLIntention()); - } - 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) { - final String message = "Python 3 requires '0o' prefix for octal literals"; - getHolder().createWarningAnnotation(node, message).registerFix(createIntention(node, message, new ReplaceOctalNumericLiteralQuickFix())); - } - } - } - } - } - - @Override - public void visitPyStringLiteralExpression(final PyStringLiteralExpression node) { - super.visitPyStringLiteralExpression(node); - if (isPy3K(node)) { - final String text = node.getText(); - if (text.startsWith("u") || text.startsWith("U")) { - final String message = "String literals do not support a leading \'u\' or \'U\' in Python 3"; - getHolder().createWarningAnnotation(node, message).registerFix(createIntention(node, message, new RemoveLeadingUQuickFix())); - } - } - } - - @Override - public void visitPyListCompExpression(final PyListCompExpression node) { - super.visitPyListCompExpression(node); - if (isPy3K(node)) { - final List forComponents = node.getForComponents(); - for (ComprhForComponent forComponent : forComponents) { - final PyExpression iteratedList = forComponent.getIteratedList(); - if (iteratedList instanceof PyTupleExpression) { - final String message = "List comprehensions do not support this syntax in Python 3"; - getHolder().createWarningAnnotation(iteratedList, message).registerFix(createIntention(iteratedList, message, new ReplaceListComprehensionsQuickFix())); - } - } - } - } - - @Override - public void visitPyRaiseStatement(PyRaiseStatement node) { - super.visitPyRaiseStatement(node); - boolean hasProblem = UnsupportedFeaturesUtil.raiseHasNoArgs(node, LanguageLevel.forElement(node)); - if (hasProblem) { - getHolder().createErrorAnnotation(node, "raise with no arguments can only be used in an except block"); - } - hasProblem = UnsupportedFeaturesUtil.raiseHasMoreThenOneArg(node, LanguageLevel.forElement(node)); - if (hasProblem) { - final String message = "Python 3 does not support this syntax"; - getHolder().createWarningAnnotation(node, message).registerFix(createIntention(node, message, new ReplaceRaiseStatementQuickFix())); - } - } - - @Override - public void visitPyReprExpression(PyReprExpression node) { - super.visitPyReprExpression(node); - if (isPy3K(node)) { - final String message = "Backquote is not supported in Python 3, use repr() instead"; - getHolder().createWarningAnnotation(node, message).registerFix(createIntention(node, message, new ReplaceBackquoteExpressionQuickFix())); - } - } - - @Override - public void visitPyWithStatement(PyWithStatement node) { - super.visitPyWithStatement(node); - final LanguageLevel languageLevel = getLanguageLevel(node); - if (languageLevel == LanguageLevel.PYTHON24) { - getHolder().createWarningAnnotation(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 i = 1; i < items.length; i++) { - getHolder().createWarningAnnotation(items [i], "Python version " + languageLevel + " does not support multiple context managers"); - } - } - } - } - - @Override - public void visitPyClass(PyClass node) { //PY-2719 - if (getLanguageLevel(node) == LanguageLevel.PYTHON24) { - PyArgumentList list = node.getSuperClassExpressionList(); - if (list != null && list.getArguments().length == 0) - getHolder().createWarningAnnotation(list, "Python version 2.4 does not support this syntax."); - } - } - - @Override - public void visitPyPrintStatement(PyPrintStatement node) { - PsiElement[] arguments = node.getChildren(); - if (getLanguageLevel(node).isPy3K()) { - for (PsiElement element : arguments) { - if (!((element instanceof PyParenthesizedExpression) || (element instanceof PyTupleExpression))) - getHolder().createWarningAnnotation(element, "Python versions >= 3.0 do not support this syntax. The print statement has been replaced with a print() function"); - } - } - } - - @Override - public void visitPyFromImportStatement(PyFromImportStatement node) { - PyReferenceExpression importSource = node.getImportSource(); - if (getLanguageLevel(node) == LanguageLevel.PYTHON24) { - if (importSource == null) - getHolder().createWarningAnnotation(node, "Python version 2.4 doesn't support this syntax."); - else { - PsiElement prev = importSource.getPrevSibling(); - if (prev != null && prev.getNode().getElementType() == PyTokenTypes.DOT) - getHolder().createWarningAnnotation(node, "Python version 2.4 doesn't support this syntax."); - } - } - } - - @Override - public void visitPyAssignmentStatement(PyAssignmentStatement node) { - if (getLanguageLevel(node) == LanguageLevel.PYTHON24) { - PyExpression assignedValue = node.getAssignedValue(); - if (assignedValue instanceof PyConditionalExpression) // PY-2792 - getHolder().createWarningAnnotation(node, "Python version 2.4 doesn't support this syntax."); - - Stack st = new Stack(); // PY-2796 - if (assignedValue != null) - st.push(assignedValue); - while (!st.isEmpty()) { - PsiElement el = st.pop(); - if (el instanceof PyYieldExpression) - getHolder().createWarningAnnotation(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 visitPyTryExceptStatement(PyTryExceptStatement node) { // PY-2795 - if (getLanguageLevel(node) == LanguageLevel.PYTHON24) { - PyExceptPart[] excepts = node.getExceptParts(); - PyFinallyPart finallyPart = node.getFinallyPart(); - if (excepts.length != 0 && finallyPart != null) - getHolder().createWarningAnnotation(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."); - } - } } diff --git a/python/testData/highlighting/unsupportedFeaturesInPython3.py b/python/testData/highlighting/unsupportedFeaturesInPython3.py index 648e6527fa91..2286f0d382a8 100644 --- a/python/testData/highlighting/unsupportedFeaturesInPython3.py +++ b/python/testData/highlighting/unsupportedFeaturesInPython3.py @@ -1,24 +1,24 @@ -print(a <> 3) -`foo()` -a = 123l -a = 043 +print(a <> 3) +`foo()` +a = 123l +a = 043 a = 0X43 a = 0b1 a = 0.0 -s = u"text" -raise a, b, c -raise a, b +s = u"text" +raise a, b, c +raise a, b try: pass -except a, name: - pass +except a, name: + pass -[x * 2 for x in vec1, vec2] +[x * 2 for x in vec1, vec2] -import __builtin__ +import __builtin__ -raise +raise try: pass diff --git a/python/testData/inspections/PyCompatibilityInspection/assignmentStatement.py b/python/testData/inspections/PyCompatibilityInspection/assignmentStatement.py new file mode 100644 index 000000000000..1910ccad01a9 --- /dev/null +++ b/python/testData/inspections/PyCompatibilityInspection/assignmentStatement.py @@ -0,0 +1,5 @@ +# PY-2792 +x = True if condition else False + +def foo(): # PY-2796 + a = (yield 1) diff --git a/python/testData/inspections/PyCompatibilityInspection/binaryExpression.py b/python/testData/inspections/PyCompatibilityInspection/binaryExpression.py new file mode 100644 index 000000000000..22b29c17dfe1 --- /dev/null +++ b/python/testData/inspections/PyCompatibilityInspection/binaryExpression.py @@ -0,0 +1,4 @@ +print(a <> b) + +if a <> 2: + var = a \ No newline at end of file diff --git a/python/testData/inspections/PyCompatibilityInspection/callExpression.py b/python/testData/inspections/PyCompatibilityInspection/callExpression.py new file mode 100644 index 000000000000..1016d41d1e07 --- /dev/null +++ b/python/testData/inspections/PyCompatibilityInspection/callExpression.py @@ -0,0 +1,11 @@ +class A(B): + def __init__(self): + super() + +cmp() +reduce() +all() + +bytearray() +next() +buffer() \ No newline at end of file diff --git a/python/testData/inspections/PyCompatibilityInspection/dictCompExpression.py b/python/testData/inspections/PyCompatibilityInspection/dictCompExpression.py new file mode 100644 index 000000000000..aa65718de495 --- /dev/null +++ b/python/testData/inspections/PyCompatibilityInspection/dictCompExpression.py @@ -0,0 +1 @@ +var = {i : chr(65+i) for i in range(4)} diff --git a/python/testData/inspections/PyCompatibilityInspection/exceptBlock.py b/python/testData/inspections/PyCompatibilityInspection/exceptBlock.py new file mode 100644 index 000000000000..831903c48467 --- /dev/null +++ b/python/testData/inspections/PyCompatibilityInspection/exceptBlock.py @@ -0,0 +1,9 @@ +try: + do_smth() +except ImportError as e: + do() + +try: + do_smth() +except ImportError, ImportWarning: + do() \ No newline at end of file diff --git a/python/testData/inspections/PyCompatibilityInspection/fromImportStatement.py b/python/testData/inspections/PyCompatibilityInspection/fromImportStatement.py new file mode 100644 index 000000000000..ffb157960f68 --- /dev/null +++ b/python/testData/inspections/PyCompatibilityInspection/fromImportStatement.py @@ -0,0 +1,4 @@ +from . import smth +from .module import name1, name2 + +from Bastion import BastionClass \ No newline at end of file diff --git a/python/testData/inspections/PyCompatibilityInspection/importElement.py b/python/testData/inspections/PyCompatibilityInspection/importElement.py new file mode 100644 index 000000000000..18c1a0c38885 --- /dev/null +++ b/python/testData/inspections/PyCompatibilityInspection/importElement.py @@ -0,0 +1 @@ +import Bastion \ No newline at end of file diff --git a/python/testData/inspections/PyCompatibilityInspection/importStatement.py b/python/testData/inspections/PyCompatibilityInspection/importStatement.py new file mode 100644 index 000000000000..0e20e9214b4c --- /dev/null +++ b/python/testData/inspections/PyCompatibilityInspection/importStatement.py @@ -0,0 +1,3 @@ +import builtins + +import __builtin__ \ No newline at end of file diff --git a/python/testData/inspections/PyCompatibilityInspection/listCompExpression.py b/python/testData/inspections/PyCompatibilityInspection/listCompExpression.py new file mode 100644 index 000000000000..a4071a458390 --- /dev/null +++ b/python/testData/inspections/PyCompatibilityInspection/listCompExpression.py @@ -0,0 +1 @@ +var = [x for x in 1, 2, 3] \ No newline at end of file diff --git a/python/testData/inspections/PyCompatibilityInspection/numericLiteralExpression.py b/python/testData/inspections/PyCompatibilityInspection/numericLiteralExpression.py new file mode 100644 index 000000000000..d10bf20270fe --- /dev/null +++ b/python/testData/inspections/PyCompatibilityInspection/numericLiteralExpression.py @@ -0,0 +1,2 @@ +a = 12l +v = 048 \ No newline at end of file diff --git a/python/testData/inspections/PyCompatibilityInspection/printStatement.py b/python/testData/inspections/PyCompatibilityInspection/printStatement.py new file mode 100644 index 000000000000..62681a44e44d --- /dev/null +++ b/python/testData/inspections/PyCompatibilityInspection/printStatement.py @@ -0,0 +1 @@ +print "One value" \ No newline at end of file diff --git a/python/testData/inspections/PyCompatibilityInspection/pyClass.py b/python/testData/inspections/PyCompatibilityInspection/pyClass.py new file mode 100644 index 000000000000..5f0c2301410d --- /dev/null +++ b/python/testData/inspections/PyCompatibilityInspection/pyClass.py @@ -0,0 +1,3 @@ +#PY-2719 +class BaseC(): + pass \ No newline at end of file diff --git a/python/testData/inspections/PyCompatibilityInspection/raiseStatement.py b/python/testData/inspections/PyCompatibilityInspection/raiseStatement.py new file mode 100644 index 000000000000..376b4c243d61 --- /dev/null +++ b/python/testData/inspections/PyCompatibilityInspection/raiseStatement.py @@ -0,0 +1,6 @@ +try: + a +except : + raise ImportError, ImportWarning + +raise diff --git a/python/testData/inspections/PyCompatibilityInspection/reprExpression.py b/python/testData/inspections/PyCompatibilityInspection/reprExpression.py new file mode 100644 index 000000000000..408795040c01 --- /dev/null +++ b/python/testData/inspections/PyCompatibilityInspection/reprExpression.py @@ -0,0 +1 @@ +a = `imp.acquire_lock()` diff --git a/python/testData/inspections/PyCompatibilityInspection/setCompExpression.py b/python/testData/inspections/PyCompatibilityInspection/setCompExpression.py new file mode 100644 index 000000000000..900a0a2f0586 --- /dev/null +++ b/python/testData/inspections/PyCompatibilityInspection/setCompExpression.py @@ -0,0 +1 @@ +var = {i for i in range(2)} diff --git a/python/testData/inspections/PyCompatibilityInspection/setLiteralExpression.py b/python/testData/inspections/PyCompatibilityInspection/setLiteralExpression.py new file mode 100644 index 000000000000..1a0699f74d26 --- /dev/null +++ b/python/testData/inspections/PyCompatibilityInspection/setLiteralExpression.py @@ -0,0 +1 @@ +var = {1, 2} diff --git a/python/testData/inspections/PyCompatibilityInspection/starExpression.py b/python/testData/inspections/PyCompatibilityInspection/starExpression.py new file mode 100644 index 000000000000..19333a1ab8cb --- /dev/null +++ b/python/testData/inspections/PyCompatibilityInspection/starExpression.py @@ -0,0 +1,2 @@ +t = (1, 2, 3) +a, *b, c = t \ No newline at end of file diff --git a/python/testData/inspections/PyCompatibilityInspection/stringLiteralExpression.py b/python/testData/inspections/PyCompatibilityInspection/stringLiteralExpression.py new file mode 100644 index 000000000000..d8b6420fcebb --- /dev/null +++ b/python/testData/inspections/PyCompatibilityInspection/stringLiteralExpression.py @@ -0,0 +1 @@ +a = u"String" diff --git a/python/testData/inspections/PyCompatibilityInspection/test.py b/python/testData/inspections/PyCompatibilityInspection/test.py index 26d7e519362b..342c5b7abd50 100644 --- a/python/testData/inspections/PyCompatibilityInspection/test.py +++ b/python/testData/inspections/PyCompatibilityInspection/test.py @@ -1,10 +1,10 @@ -print(a <> b) +print(a <> b) -if a <> 2: +if a <> 2: var = a ######################## -print "One value" +print "One value" ######################## @@ -14,14 +14,14 @@ class BaseC()< ######################## -with A() as a, B() as b: +with A() as a, B() as b: suite ######################## -a = `imp.acquire_lock()` +a = `imp.acquire_lock()` ######################## -var = [x for x in 1, 2, 3] +var = [x for x in 1, 2, 3] ######################## class A: @@ -33,35 +33,35 @@ a.cmp() ######################## -cmp() -reduce() -all() +cmp() +reduce() +all() bytearray() next() -buffer() +buffer() ######################## try: a except : - raise ImportError, ImportWarning + raise ImportError, ImportWarning try: a -except ImportError, ImportWarning: +except ImportError, ImportWarning: b ######################## -var = {i : chr(65+i) for i in range(4)} +var = {i : chr(65+i) for i in range(4)} ######################## -import Bastion +import Bastion var = Bastion.BastionClass() ######################## @@ -70,7 +70,7 @@ def foo(): # PY-2796 ######################## -raise +raise ######################## # PY-2792 diff --git a/python/testData/inspections/PyCompatibilityInspection/tryExceptStatement.py b/python/testData/inspections/PyCompatibilityInspection/tryExceptStatement.py new file mode 100644 index 000000000000..786872043f71 --- /dev/null +++ b/python/testData/inspections/PyCompatibilityInspection/tryExceptStatement.py @@ -0,0 +1,8 @@ +try: + do_smth() +except ImportError: + do() +except KeyError: + do_1() +finally: + quit() \ No newline at end of file diff --git a/python/testData/inspections/PyCompatibilityInspection/withStatement.py b/python/testData/inspections/PyCompatibilityInspection/withStatement.py new file mode 100644 index 000000000000..a5d7fb211bad --- /dev/null +++ b/python/testData/inspections/PyCompatibilityInspection/withStatement.py @@ -0,0 +1,2 @@ +with A() as a, B() as b: + suite diff --git a/python/testData/inspections/PyUnusedVariableTupleUnpacking/test.py b/python/testData/inspections/PyUnusedVariableTupleUnpacking/test.py index ffca07e98d05..de8dff5f1bba 100644 --- a/python/testData/inspections/PyUnusedVariableTupleUnpacking/test.py +++ b/python/testData/inspections/PyUnusedVariableTupleUnpacking/test.py @@ -3,8 +3,8 @@ def foo(): print x def test_vlu(): - *h, t = [1, 2, 3] # fail + *h, t = [1, 2, 3] # fail def test_vlu(): - *h, t = [1, 2, 3] # pass + *h, t = [1, 2, 3] # pass print(t) \ No newline at end of file diff --git a/python/testData/inspections/ReplaceNotEqOperator.py b/python/testData/inspections/ReplaceNotEqOperator.py index ed61c99e27d5..5794000d7dca 100644 --- a/python/testData/inspections/ReplaceNotEqOperator.py +++ b/python/testData/inspections/ReplaceNotEqOperator.py @@ -1 +1 @@ -print(a <> b) \ No newline at end of file +print(a <> b) \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java b/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java index e0e086f2e5ce..e8de9b38c2db 100644 --- a/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java +++ b/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java @@ -74,6 +74,7 @@ public class PythonHighlightingTest extends PyLightFixtureTestCase { } public void testAssignmentTargets() { + setLanguageLevel(LanguageLevel.PYTHON26); doTest(true, false); } diff --git a/python/testSrc/com/jetbrains/python/inspections/PyCompatibilityInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyCompatibilityInspectionTest.java new file mode 100644 index 000000000000..16dfd45df177 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/inspections/PyCompatibilityInspectionTest.java @@ -0,0 +1,102 @@ +package com.jetbrains.python.inspections; + +import com.jetbrains.python.fixtures.PyLightFixtureTestCase; +import com.jetbrains.python.psi.LanguageLevel; + +/** + * User : catherine + */ +public class PyCompatibilityInspectionTest extends PyLightFixtureTestCase { + + public void testDictCompExpression() { + setLanguageLevel(LanguageLevel.PYTHON27); + doTest(); + } + + public void testSetLiteralExpression() { + setLanguageLevel(LanguageLevel.PYTHON27); + doTest(); + } + + public void testSetCompExpression() { + setLanguageLevel(LanguageLevel.PYTHON27); + doTest(); + } + + public void testExceptBlock() { + doTest(); + } + + public void testImportStatement() { + doTest(); + } + + public void testStarExpression() { + setLanguageLevel(LanguageLevel.PYTHON30); + doTest(); + } + + public void testBinaryExpression() { + setLanguageLevel(LanguageLevel.PYTHON27); + doTest(); + } + + public void testNumericLiteralExpression() { + doTest(); + } + + public void testStringLiteralExpression() { + doTest(); + } + + public void testListCompExpression() { + doTest(); + } + + public void testRaiseStatement() { + doTest(); + } + + public void testReprExpression() { + doTest(); + } + + public void testWithStatement() { + doTest(); + } + + public void testPyClass() { + doTest(); + } + + public void testPrintStatement() { + doTest(); + } + + public void testFromImportStatement() { + doTest(); + } + + public void testAssignmentStatement() { + doTest(); + } + + public void testTryExceptStatement() { + doTest(); + } + + public void testImportElement() { + doTest(); + } + + public void testCallExpression() { + setLanguageLevel(LanguageLevel.PYTHON30); + doTest(); + } + + private void doTest() { + myFixture.configureByFile("inspections/PyCompatibilityInspection/" + getTestName(true) + ".py"); + myFixture.enableInspections(PyCompatibilityInspection.class); + myFixture.checkHighlighting(true, false, false); + } +}