diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index ab7db3c65134..9e0d50aa320b 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -32,6 +32,24 @@ QFIX.action.failed=Action failed QFIX.remove.trailing.semicolon=Remove trailing semicolon +QFIX.replace.noteq.operator=Replace not equal operator + +QFIX.replace.backquote.expression=Replace backquote expression + +QFIX.replace.method=Replace method which not supported in current Python version + +QFIX.remove.leading.u=Remove leading U + +QFIX.remove.trailing.l=Remove trailing L + +QFIX.replace.raise.statement=Convert raise statement to supported form + +QFIX.replace.except.part=Convert except clause to supported form + +QFIX.replace.list.comprehensions=Convert list comprehensions to supported form + +QFIX.replace.octal.numeric.literal=Convert octal numeric literal to supported form + # Intentions: INTN INTN.Family.convert.import.unqualify=Convert 'import module' to 'from module import' INTN.Family.convert.import.qualify=Convert 'from module import' to 'import module' @@ -114,6 +132,11 @@ INSP.NAME.unused=Unused local variable INSP.unused.locals.parameter.isnot.used=Parameter ''{0}'' value is not used INSP.unused.locals.local.variable.isnot.used=Local variable ''{0}'' value is not used +# PyUnsupportedFeaturesInspection +INSP.NAME.unsupported.features=Feature not supported by current Python version +INSP.method.$0.removed.use.$1=Method ''{0}'' removed, use ''{1}'' instead +INSP.method.$0.removed=Method ''{0}'' removed + # Refactoring # introduce refactoring.introduce.name.error=Incorrect name diff --git a/python/src/com/jetbrains/python/actions/ReamoveLeadingUQuickFix.java b/python/src/com/jetbrains/python/actions/ReamoveLeadingUQuickFix.java new file mode 100644 index 000000000000..c0d37bb3efaa --- /dev/null +++ b/python/src/com/jetbrains/python/actions/ReamoveLeadingUQuickFix.java @@ -0,0 +1,34 @@ +package com.jetbrains.python.actions; + +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.openapi.project.Project; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.PythonLanguage; +import com.jetbrains.python.psi.PyElementGenerator; +import com.jetbrains.python.psi.PyStringLiteralExpression; +import org.jetbrains.annotations.NotNull; + +/** + * Created by IntelliJ IDEA. + * User: Alexey.Ivanov + * Date: 10.02.2010 + * Time: 17:45:58 + */ +public class ReamoveLeadingUQuickFix implements LocalQuickFix { + @NotNull + public String getName() { + return PyBundle.message("QFIX.remove.leading.u"); + } + + @NotNull + public String getFamilyName() { + return PyBundle.message("INSP.GROUP.python"); + } + + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PyStringLiteralExpression stringLiteralExpression = (PyStringLiteralExpression) descriptor.getPsiElement(); + PyElementGenerator elementGenerator = PythonLanguage.getInstance().getElementGenerator(); + stringLiteralExpression.replace(elementGenerator.createExpressionFromText(project, stringLiteralExpression.getText().substring(1))); + } +} diff --git a/python/src/com/jetbrains/python/actions/ReamoveTrailingLQuickFix.java b/python/src/com/jetbrains/python/actions/ReamoveTrailingLQuickFix.java new file mode 100644 index 000000000000..31254dc58c78 --- /dev/null +++ b/python/src/com/jetbrains/python/actions/ReamoveTrailingLQuickFix.java @@ -0,0 +1,35 @@ +package com.jetbrains.python.actions; + +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.openapi.project.Project; +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. + * User: Alexey.Ivanov + * Date: 10.02.2010 + * Time: 17:45:58 + */ +public class ReamoveTrailingLQuickFix implements LocalQuickFix { + @NotNull + public String getName() { + return PyBundle.message("QFIX.remove.trailing.l"); + } + + @NotNull + public String getFamilyName() { + return PyBundle.message("INSP.GROUP.python"); + } + + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PyNumericLiteralExpression numericLiteralExpression = (PyNumericLiteralExpression) descriptor.getPsiElement(); + PyElementGenerator elementGenerator = PythonLanguage.getInstance().getElementGenerator(); + String text = numericLiteralExpression.getText(); + numericLiteralExpression.replace(elementGenerator.createExpressionFromText(project, text.substring(0, text.length() - 1))); + } +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/actions/ReplaceBackquoteExpressionQuickFix.java b/python/src/com/jetbrains/python/actions/ReplaceBackquoteExpressionQuickFix.java new file mode 100644 index 000000000000..2989988bfeba --- /dev/null +++ b/python/src/com/jetbrains/python/actions/ReplaceBackquoteExpressionQuickFix.java @@ -0,0 +1,36 @@ +package com.jetbrains.python.actions; + +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.openapi.project.Project; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.PythonLanguage; +import com.jetbrains.python.psi.PyElementGenerator; +import com.jetbrains.python.psi.PyReprExpression; +import org.jetbrains.annotations.NotNull; + +/** + * Created by IntelliJ IDEA. + * User: Alexey.Ivanov + * Date: 08.02.2010 + * Time: 20:23:53 + */ +public class ReplaceBackquoteExpressionQuickFix implements LocalQuickFix { + @NotNull + public String getName() { + return PyBundle.message("QFIX.replace.backquote.expression"); + } + + @NotNull + public String getFamilyName() { + return PyBundle.message("INSP.GROUP.python"); + } + + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PyReprExpression problemElement = (PyReprExpression) descriptor.getPsiElement(); + if (problemElement != null && problemElement.getExpression() != null) { + PyElementGenerator elementGenerator = PythonLanguage.getInstance().getElementGenerator(); + problemElement.replace(elementGenerator.createExpressionFromText(project, "repr(" + problemElement.getExpression().getText() + ")")); + } + } +} diff --git a/python/src/com/jetbrains/python/actions/ReplaceExceptPartQuickFix.java b/python/src/com/jetbrains/python/actions/ReplaceExceptPartQuickFix.java new file mode 100644 index 000000000000..426be288a152 --- /dev/null +++ b/python/src/com/jetbrains/python/actions/ReplaceExceptPartQuickFix.java @@ -0,0 +1,57 @@ +package com.jetbrains.python.actions; + +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.lang.ASTNode; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiWhiteSpace; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.PyTokenTypes; +import com.jetbrains.python.PythonLanguage; +import com.jetbrains.python.psi.PyElementGenerator; +import com.jetbrains.python.psi.PyExceptPart; +import com.jetbrains.python.psi.PyTryExceptStatement; +import org.jetbrains.annotations.NotNull; + +/** + * Created by IntelliJ IDEA. + * User: Alexey.Ivanov + * Date: 10.02.2010 + * Time: 19:24:17 + */ +public class ReplaceExceptPartQuickFix implements LocalQuickFix { + private final boolean myPy3KFlag; + + public ReplaceExceptPartQuickFix(boolean py3KFlag) { + myPy3KFlag = py3KFlag; + } + + @NotNull + public String getName() { + return PyBundle.message("QFIX.replace.except.part"); + } + + @NotNull + public String getFamilyName() { + return PyBundle.message("INSP.GROUP.python"); + } + + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PyExceptPart exceptPart = (PyExceptPart) descriptor.getPsiElement(); + PyElementGenerator elementGenerator = PythonLanguage.getInstance().getElementGenerator(); + PsiElement element = exceptPart.getExceptClass().getNextSibling(); + while (element instanceof PsiWhiteSpace) { + element = element.getNextSibling(); + } + assert element != null; + if (myPy3KFlag) { + PyTryExceptStatement newElement = elementGenerator.createFromText(project, PyTryExceptStatement.class, "try: pass except a as b: pass"); + ASTNode node = newElement.getExceptParts()[0].getNode().findChildByType(PyTokenTypes.AS_KEYWORD); + assert node != null; + element.replace(node.getPsi()); + } else { + element.replace(elementGenerator.createComma(project).getPsi()); + } + } +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/actions/ReplaceListComprehensionsQuickFix.java b/python/src/com/jetbrains/python/actions/ReplaceListComprehensionsQuickFix.java new file mode 100644 index 000000000000..f66125cd6637 --- /dev/null +++ b/python/src/com/jetbrains/python/actions/ReplaceListComprehensionsQuickFix.java @@ -0,0 +1,35 @@ +package com.jetbrains.python.actions; + +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.openapi.project.Project; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.PythonLanguage; +import com.jetbrains.python.psi.PyElementGenerator; +import com.jetbrains.python.psi.PyExpression; +import com.jetbrains.python.psi.PyParenthesizedExpression; +import org.jetbrains.annotations.NotNull; + +/** + * Created by IntelliJ IDEA. + * User: Alexey.Ivanov + * Date: 12.02.2010 + * Time: 18:15:24 + */ +public class ReplaceListComprehensionsQuickFix implements LocalQuickFix { + @NotNull + public String getName() { + return PyBundle.message("QFIX.replace.list.comprehensions"); + } + + @NotNull + public String getFamilyName() { + return PyBundle.message("INSP.GROUP.python"); + } + + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PyExpression expression = (PyExpression) descriptor.getPsiElement(); + PyElementGenerator elementGenerator = PythonLanguage.getInstance().getElementGenerator(); + expression.replace(elementGenerator.createFromText(project, PyParenthesizedExpression.class, "(" + expression.getText() + ")")); + } +} diff --git a/python/src/com/jetbrains/python/actions/ReplaceMethodQuickFix.java b/python/src/com/jetbrains/python/actions/ReplaceMethodQuickFix.java new file mode 100644 index 000000000000..0294a0090851 --- /dev/null +++ b/python/src/com/jetbrains/python/actions/ReplaceMethodQuickFix.java @@ -0,0 +1,42 @@ +package com.jetbrains.python.actions; + +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.openapi.project.Project; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.PythonLanguage; +import com.jetbrains.python.psi.PyElementGenerator; +import com.jetbrains.python.psi.PyExpression; +import org.jetbrains.annotations.NotNull; + +/** + * Created by IntelliJ IDEA. + * User: Alexey.Ivanov + * Date: 10.02.2010 + * Time: 15:46:03 + */ +public class ReplaceMethodQuickFix implements LocalQuickFix { + private final String myNewName; + + public ReplaceMethodQuickFix(String newName) { + myNewName = newName; + } + + @NotNull + public String getName() { + return PyBundle.message("QFIX.replace.method"); + } + + @NotNull + public String getFamilyName() { + return PyBundle.message("INSP.GROUP.python"); + } + + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PyExpression problemElement = (PyExpression) descriptor.getPsiElement(); + if (problemElement != null) { + PyElementGenerator elementGenerator = PythonLanguage.getInstance().getElementGenerator(); + problemElement.replace(elementGenerator.createCallExpression(project, myNewName).getCallee()); + } + } +} diff --git a/python/src/com/jetbrains/python/actions/ReplaceNotEqOperatorQuickFix.java b/python/src/com/jetbrains/python/actions/ReplaceNotEqOperatorQuickFix.java new file mode 100644 index 000000000000..0db6e2ba474c --- /dev/null +++ b/python/src/com/jetbrains/python/actions/ReplaceNotEqOperatorQuickFix.java @@ -0,0 +1,38 @@ +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.intellij.psi.impl.source.tree.LeafPsiElement; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.PythonLanguage; +import com.jetbrains.python.psi.PyBinaryExpression; +import com.jetbrains.python.psi.PyElementGenerator; +import org.jetbrains.annotations.NotNull; + +/** + * Created by IntelliJ IDEA. + * User: Alexey.Ivanov + * Date: 08.02.2010 + * Time: 19:28:35 + */ +public class ReplaceNotEqOperatorQuickFix implements LocalQuickFix { + @NotNull + public String getName() { + return PyBundle.message("QFIX.replace.noteq.operator"); + } + + @NotNull + public String getFamilyName() { + return PyBundle.message("INSP.GROUP.python"); + } + + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PsiElement operator = ((PyBinaryExpression) descriptor.getPsiElement()).getPsiOperator(); + if (operator != null) { + PyElementGenerator elementGenerator = PythonLanguage.getInstance().getElementGenerator(); + operator.replace(elementGenerator.createFromText(project, LeafPsiElement.class, "!=")); + } + } +} diff --git a/python/src/com/jetbrains/python/actions/ReplaceOctalNumericLiteralQuickFix.java b/python/src/com/jetbrains/python/actions/ReplaceOctalNumericLiteralQuickFix.java new file mode 100644 index 000000000000..e5bec5905397 --- /dev/null +++ b/python/src/com/jetbrains/python/actions/ReplaceOctalNumericLiteralQuickFix.java @@ -0,0 +1,35 @@ +package com.jetbrains.python.actions; + +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.openapi.project.Project; +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. + * User: Alexey.Ivanov + * Date: 12.02.2010 + * Time: 18:41:44 + */ +public class ReplaceOctalNumericLiteralQuickFix implements LocalQuickFix { + @NotNull + public String getName() { + return PyBundle.message("QFIX.replace.octal.numeric.literal"); + } + + @NotNull + public String getFamilyName() { + return PyBundle.message("INSP.GROUP.python"); + } + + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PyNumericLiteralExpression numericLiteralExpression = (PyNumericLiteralExpression) descriptor.getPsiElement(); + PyElementGenerator elementGenerator = PythonLanguage.getInstance().getElementGenerator(); + String text = numericLiteralExpression.getText(); + numericLiteralExpression.replace(elementGenerator.createExpressionFromText(project, "0o" + text.substring(1))); + } +} diff --git a/python/src/com/jetbrains/python/actions/ReplaceRaiseStatementQuickFix.java b/python/src/com/jetbrains/python/actions/ReplaceRaiseStatementQuickFix.java new file mode 100644 index 000000000000..6bcb7ec75b7d --- /dev/null +++ b/python/src/com/jetbrains/python/actions/ReplaceRaiseStatementQuickFix.java @@ -0,0 +1,43 @@ +package com.jetbrains.python.actions; + +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.openapi.project.Project; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.PythonLanguage; +import com.jetbrains.python.psi.PyElementGenerator; +import com.jetbrains.python.psi.PyExpression; +import com.jetbrains.python.psi.PyRaiseStatement; +import org.jetbrains.annotations.NotNull; + +/** + * Created by IntelliJ IDEA. + * User: Alexey.Ivanov + * Date: 10.02.2010 + * Time: 19:24:17 + */ +public class ReplaceRaiseStatementQuickFix implements LocalQuickFix { + @NotNull + public String getName() { + return PyBundle.message("QFIX.replace.raise.statement"); + } + + @NotNull + public String getFamilyName() { + return PyBundle.message("INSP.GROUP.python"); + } + + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PyRaiseStatement raiseStatement = (PyRaiseStatement) descriptor.getPsiElement(); + PyExpression[] expressions = raiseStatement.getExpressions(); + assert expressions != null; + PyElementGenerator elementGenerator = PythonLanguage.getInstance().getElementGenerator(); + String newExpressionText = expressions[0].getText() + "(" + expressions[1].getText() + ")"; + if (expressions.length == 2) { + raiseStatement.replace(elementGenerator.createFromText(project, PyRaiseStatement.class, "raise " + newExpressionText)); + } else if (expressions.length == 3) { + raiseStatement.replace(elementGenerator.createFromText(project, PyRaiseStatement.class, + "raise " + newExpressionText + ".with_traceback(" + expressions[2].getText() + ")")); + } + } +} diff --git a/python/src/com/jetbrains/python/inspections/PyUnsupportedFeaturesInspection.java b/python/src/com/jetbrains/python/inspections/PyUnsupportedFeaturesInspection.java new file mode 100644 index 000000000000..dbeff1761e08 --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/PyUnsupportedFeaturesInspection.java @@ -0,0 +1,209 @@ +package com.jetbrains.python.inspections; + +import com.intellij.codeInspection.LocalInspectionTool; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiElementVisitor; +import com.intellij.psi.PsiWhiteSpace; +import com.intellij.util.containers.HashSet; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.actions.*; +import com.jetbrains.python.psi.*; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Set; + +/** + * Created by IntelliJ IDEA. + * User: Alexey.Ivanov + * Date: 08.02.2010 + * Time: 18:05:36 + */ +public class PyUnsupportedFeaturesInspection extends LocalInspectionTool { + @Nls + @NotNull + @Override + public String getGroupDisplayName() { + return PyBundle.message("INSP.GROUP.python"); + } + + @Nls + @NotNull + @Override + public String getDisplayName() { + return PyBundle.message("INSP.NAME.unsupported.features"); + } + + @NotNull + @Override + public String getShortName() { + return "PyUnsupportedFeaturesInspection"; + } + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + return new Visitor(holder); + } + + static class Visitor extends PyInspectionVisitor { + private static Set REMOVED_METHODS = new HashSet(); + + static { + REMOVED_METHODS.add("cmp"); + REMOVED_METHODS.add("apply"); + REMOVED_METHODS.add("callable"); + REMOVED_METHODS.add("coerce"); + REMOVED_METHODS.add("execfile"); + REMOVED_METHODS.add("reduce"); + REMOVED_METHODS.add("reload"); + } + + public Visitor(final ProblemsHolder holder) { + super(holder); + } + + @Override + public void visitPyBinaryExpression(PyBinaryExpression node) { + VirtualFile virtualFile = node.getContainingFile().getVirtualFile(); + if (virtualFile != null && LanguageLevel.forFile(virtualFile).isPy3K()) { + if (node.isOperator("<>")) { + registerProblem(node, "<> not supported in Python3, use != instead", new ReplaceNotEqOperatorQuickFix()); + } + } + } + + @Override + public void visitPyNumericLiteralExpression(PyNumericLiteralExpression node) { + VirtualFile virtualFile = node.getContainingFile().getVirtualFile(); + if (virtualFile != null && LanguageLevel.forFile(virtualFile).isPy3K()) { + String text = node.getText(); + if (text.endsWith("l") || text.endsWith("L")) { + registerProblem(node, "Integer literals no support trailing \'l\' or \'L\' in Python3", new ReamoveTrailingLQuickFix()); + } + if (text.charAt(0) == '0' && (text.charAt(1) != 'o' || text.charAt(1) != 'b')) { + registerProblem(node, "Python3 not supported such syntax", new ReplaceOctalNumericLiteralQuickFix()); + } + } + } + + @Override + public void visitPyStringLiteralExpression(PyStringLiteralExpression node) { + VirtualFile virtualFile = node.getContainingFile().getVirtualFile(); + if (virtualFile != null && LanguageLevel.forFile(virtualFile).isPy3K()) { + String text = node.getText(); + if (text.startsWith("u") || text.startsWith("U")) { + registerProblem(node, "String literals no support a leading \'u\' or \'U\' in Python3", new ReamoveLeadingUQuickFix()); + } + } + } + + @Override + public void visitPyListCompExpression(PyListCompExpression node) { + List forComponents = node.getForComponents(); + for (ComprhForComponent forComponent: forComponents) { + PyExpression iteratedList = forComponent.getIteratedList(); + if (iteratedList instanceof PyTupleExpression) { + registerProblem(iteratedList, "List comprehensions no support such syntax in Python3", new ReplaceListComprehensionsQuickFix()); + } + } + } + + @Override + public void visitPyExceptBlock(PyExceptPart node) { + PyExpression exceptClass = node.getExceptClass(); + if (exceptClass != null && node.getTarget() != null) { + VirtualFile virtualFile = node.getContainingFile().getVirtualFile(); + if (virtualFile != null) { + if (LanguageLevel.forFile(virtualFile).isPy3K()) { + PsiElement element = exceptClass.getNextSibling(); + while (element instanceof PsiWhiteSpace) { + element = element.getNextSibling(); + } + if (element != null && ",".equals(element.getText())) { + registerProblem(node, "Python3 not supported such syntax", new ReplaceExceptPartQuickFix(true)); + } + } else { + PsiElement element = exceptClass.getNextSibling(); + while (element instanceof PsiWhiteSpace) { + element = element.getNextSibling(); + } + if (element != null && "as".equals(element.getText())) { + registerProblem(node, "Python2 not supported such syntax", new ReplaceExceptPartQuickFix(false)); + } + } + } + } + } + + @Override + public void visitPyRaiseStatement(PyRaiseStatement node) { + PyExpression[] expressions = node.getExpressions(); + assert(expressions != null); + if (expressions.length < 2) { + return; + } + + VirtualFile virtualFile = node.getContainingFile().getVirtualFile(); + if (virtualFile != null) { + if (LanguageLevel.forFile(virtualFile).isPy3K()) { + if (expressions.length == 3) { + registerProblem(node, "Python3 not supported such syntax", new ReplaceRaiseStatementQuickFix()); + return; + } + PsiElement element = expressions[0].getNextSibling(); + while (element instanceof PsiWhiteSpace) { + element = element.getNextSibling(); + } + if (element != null && ",".equals(element.getText())) { + registerProblem(node, "Python3 not supported such syntax", new ReplaceRaiseStatementQuickFix()); + } + } else { + if (expressions.length == 2) { + PsiElement element = expressions[0].getNextSibling(); + while (element instanceof PsiWhiteSpace) { + element = element.getNextSibling(); + } + if (element != null && "from".equals(element.getText())) { + registerProblem(node, "Python2 not supported such syntax"); + } + } + } + } + } + + @Override + public void visitPyReprExpression(PyReprExpression node) { + VirtualFile virtualFile = node.getContainingFile().getVirtualFile(); + if (virtualFile != null && LanguageLevel.forFile(virtualFile).isPy3K()) { + registerProblem(node, "Backquote not supported in Python3, use repr() instead", new ReplaceBackquoteExpressionQuickFix()); + } + } + + @Override + public void visitPyCallExpression(PyCallExpression node) { + VirtualFile virtualFile = node.getContainingFile().getVirtualFile(); + if (virtualFile != null) { + String name = node.getCallee().getName(); + if (LanguageLevel.forFile(virtualFile).isPy3K()) { + if ("raw_input".equals(name)) { + registerProblem(node.getCallee(), PyBundle.message("INSP.method.$0.removed.use.$1", name, "input"), + new ReplaceMethodQuickFix("input")); + } else if (REMOVED_METHODS.contains(name)) { + registerProblem(node.getCallee(), PyBundle.message("INSP.method.$0.removed", name)); + } + } else { + if ("super".equals(name)) { + PyArgumentList argumentList = node.getArgumentList(); + if (argumentList != null && argumentList.getArguments().length == 0) { + registerProblem(node, "super() should have arguments in current language version"); + } + } + } + } + } + } +} diff --git a/python/src/com/jetbrains/python/inspections/PythonInspectionToolProvider.java b/python/src/com/jetbrains/python/inspections/PythonInspectionToolProvider.java index 1f507999ea1a..359a75bced96 100644 --- a/python/src/com/jetbrains/python/inspections/PythonInspectionToolProvider.java +++ b/python/src/com/jetbrains/python/inspections/PythonInspectionToolProvider.java @@ -20,7 +20,8 @@ public class PythonInspectionToolProvider implements InspectionToolProvider { PyMethodOverridingInspection.class, PyTrailingSemicolonInspection.class, PyReturnFromInitInspection.class, - PyUnusedLocalVariableInspection.class + PyUnusedLocalVariableInspection.class, + PyUnsupportedFeaturesInspection.class }; } } diff --git a/python/src/com/jetbrains/python/psi/PyElementVisitor.java b/python/src/com/jetbrains/python/psi/PyElementVisitor.java index ddc77af34b76..c5c70e801161 100644 --- a/python/src/com/jetbrains/python/psi/PyElementVisitor.java +++ b/python/src/com/jetbrains/python/psi/PyElementVisitor.java @@ -213,4 +213,8 @@ public class PyElementVisitor extends PsiElementVisitor { public void visitPyImportStatement(PyImportStatement node) { visitPyStatement(node); } + + public void visitPyReprExpression(PyReprExpression node) { + visitPyExpression(node); + } } diff --git a/python/src/com/jetbrains/python/psi/PyRaiseStatement.java b/python/src/com/jetbrains/python/psi/PyRaiseStatement.java index e25c0ac99ddf..9b97bcf78a94 100644 --- a/python/src/com/jetbrains/python/psi/PyRaiseStatement.java +++ b/python/src/com/jetbrains/python/psi/PyRaiseStatement.java @@ -16,6 +16,8 @@ package com.jetbrains.python.psi; +import org.jetbrains.annotations.Nullable; + /** * Created by IntelliJ IDEA. * User: yole @@ -24,4 +26,6 @@ package com.jetbrains.python.psi; * To change this template use File | Settings | File Templates. */ public interface PyRaiseStatement extends PyStatement { + @Nullable + PyExpression[] getExpressions(); } diff --git a/python/src/com/jetbrains/python/psi/PyReprExpression.java b/python/src/com/jetbrains/python/psi/PyReprExpression.java index 17a3b22f6e9a..0f571bbe12b2 100644 --- a/python/src/com/jetbrains/python/psi/PyReprExpression.java +++ b/python/src/com/jetbrains/python/psi/PyReprExpression.java @@ -16,6 +16,8 @@ package com.jetbrains.python.psi; +import org.jetbrains.annotations.Nullable; + /** * Created by IntelliJ IDEA. * User: yole @@ -24,4 +26,6 @@ package com.jetbrains.python.psi; * To change this template use File | Settings | File Templates. */ public interface PyReprExpression extends PyExpression { + @Nullable + PyExpression getExpression(); } diff --git a/python/src/com/jetbrains/python/psi/impl/PyRaiseStatementImpl.java b/python/src/com/jetbrains/python/psi/impl/PyRaiseStatementImpl.java index d587f0b440c7..61987f8afd5f 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyRaiseStatementImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyRaiseStatementImpl.java @@ -17,8 +17,11 @@ package com.jetbrains.python.psi.impl; import com.intellij.lang.ASTNode; -import com.jetbrains.python.psi.PyRaiseStatement; +import com.intellij.psi.util.PsiTreeUtil; import com.jetbrains.python.psi.PyElementVisitor; +import com.jetbrains.python.psi.PyExpression; +import com.jetbrains.python.psi.PyRaiseStatement; +import org.jetbrains.annotations.Nullable; /** * Describes 'raise' statement. @@ -32,4 +35,9 @@ public class PyRaiseStatementImpl extends PyElementImpl implements PyRaiseStatem protected void acceptPyVisitor(PyElementVisitor pyVisitor) { pyVisitor.visitPyRaiseStatement(this); } + + @Nullable + public PyExpression[] getExpressions() { + return PsiTreeUtil.getChildrenOfType(this, PyExpression.class); + } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyReprExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyReprExpressionImpl.java index 694f35df300e..602533ad4337 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyReprExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyReprExpressionImpl.java @@ -17,8 +17,12 @@ package com.jetbrains.python.psi.impl; import com.intellij.lang.ASTNode; +import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.python.psi.PyElementVisitor; +import com.jetbrains.python.psi.PyExpression; import com.jetbrains.python.psi.PyReprExpression; import com.jetbrains.python.psi.types.PyType; +import org.jetbrains.annotations.Nullable; /** * Created by IntelliJ IDEA. @@ -28,9 +32,19 @@ import com.jetbrains.python.psi.types.PyType; * To change this template use File | Settings | File Templates. */ public class PyReprExpressionImpl extends PyElementImpl implements PyReprExpression { - public PyReprExpressionImpl(ASTNode astNode) { - super(astNode); - } + public PyReprExpressionImpl(ASTNode astNode) { + super(astNode); + } + + @Nullable + public PyExpression getExpression() { + return PsiTreeUtil.getChildOfType(this, PyExpression.class); + } + + @Override + protected void acceptPyVisitor(PyElementVisitor pyVisitor) { + pyVisitor.visitPyReprExpression(this); + } public PyType getType() { return null; diff --git a/python/testData/inspections/RemoveLeadingU.py b/python/testData/inspections/RemoveLeadingU.py new file mode 100644 index 000000000000..a80b59fefa0c --- /dev/null +++ b/python/testData/inspections/RemoveLeadingU.py @@ -0,0 +1 @@ +a = u"text" \ No newline at end of file diff --git a/python/testData/inspections/RemoveLeadingU_after.py b/python/testData/inspections/RemoveLeadingU_after.py new file mode 100644 index 000000000000..a281a93f0f10 --- /dev/null +++ b/python/testData/inspections/RemoveLeadingU_after.py @@ -0,0 +1 @@ +a = "text" \ No newline at end of file diff --git a/python/testData/inspections/RemoveTrailingL.py b/python/testData/inspections/RemoveTrailingL.py new file mode 100644 index 000000000000..220eca675da1 --- /dev/null +++ b/python/testData/inspections/RemoveTrailingL.py @@ -0,0 +1 @@ +a = 1223l \ No newline at end of file diff --git a/python/testData/inspections/RemoveTrailingL_after.py b/python/testData/inspections/RemoveTrailingL_after.py new file mode 100644 index 000000000000..46e8a8133fb2 --- /dev/null +++ b/python/testData/inspections/RemoveTrailingL_after.py @@ -0,0 +1 @@ +a = 1223 \ No newline at end of file diff --git a/python/testData/inspections/ReplaceBackQuoteExpression.py b/python/testData/inspections/ReplaceBackQuoteExpression.py new file mode 100644 index 000000000000..44c695da792b --- /dev/null +++ b/python/testData/inspections/ReplaceBackQuoteExpression.py @@ -0,0 +1 @@ +`a + b, 34 + a` \ No newline at end of file diff --git a/python/testData/inspections/ReplaceBackQuoteExpression_after.py b/python/testData/inspections/ReplaceBackQuoteExpression_after.py new file mode 100644 index 000000000000..6ca256acd3e9 --- /dev/null +++ b/python/testData/inspections/ReplaceBackQuoteExpression_after.py @@ -0,0 +1 @@ +repr(a + b, 34 + a) \ No newline at end of file diff --git a/python/testData/inspections/ReplaceExceptPartTo2.py b/python/testData/inspections/ReplaceExceptPartTo2.py new file mode 100644 index 000000000000..6c432497dd7c --- /dev/null +++ b/python/testData/inspections/ReplaceExceptPartTo2.py @@ -0,0 +1,4 @@ +try: + pass +except a as name: + pass \ No newline at end of file diff --git a/python/testData/inspections/ReplaceExceptPartTo2_after.py b/python/testData/inspections/ReplaceExceptPartTo2_after.py new file mode 100644 index 000000000000..4db57c0f757e --- /dev/null +++ b/python/testData/inspections/ReplaceExceptPartTo2_after.py @@ -0,0 +1,4 @@ +try: + pass +except a , name: + pass \ No newline at end of file diff --git a/python/testData/inspections/ReplaceExceptPartTo3.py b/python/testData/inspections/ReplaceExceptPartTo3.py new file mode 100644 index 000000000000..b431a0ebf2c2 --- /dev/null +++ b/python/testData/inspections/ReplaceExceptPartTo3.py @@ -0,0 +1,4 @@ +try: + pass +except a, name: + pass \ No newline at end of file diff --git a/python/testData/inspections/ReplaceExceptPartTo3_after.py b/python/testData/inspections/ReplaceExceptPartTo3_after.py new file mode 100644 index 000000000000..89559a0fdb31 --- /dev/null +++ b/python/testData/inspections/ReplaceExceptPartTo3_after.py @@ -0,0 +1,4 @@ +try: + pass +except a as name: + pass \ No newline at end of file diff --git a/python/testData/inspections/ReplaceListComprehensions.py b/python/testData/inspections/ReplaceListComprehensions.py new file mode 100644 index 000000000000..c329642ac580 --- /dev/null +++ b/python/testData/inspections/ReplaceListComprehensions.py @@ -0,0 +1 @@ +[x * 2 for x in [1, 2], [2, 3]] \ No newline at end of file diff --git a/python/testData/inspections/ReplaceListComprehensions_after.py b/python/testData/inspections/ReplaceListComprehensions_after.py new file mode 100644 index 000000000000..d1ede3a1997f --- /dev/null +++ b/python/testData/inspections/ReplaceListComprehensions_after.py @@ -0,0 +1 @@ +[x * 2 for x in ([1, 2], [2, 3])] \ No newline at end of file diff --git a/python/testData/inspections/ReplaceMethod.py b/python/testData/inspections/ReplaceMethod.py new file mode 100644 index 000000000000..e2ccffc0b400 --- /dev/null +++ b/python/testData/inspections/ReplaceMethod.py @@ -0,0 +1 @@ +a = raw_input() \ No newline at end of file diff --git a/python/testData/inspections/ReplaceMethod_after.py b/python/testData/inspections/ReplaceMethod_after.py new file mode 100644 index 000000000000..5787c06da455 --- /dev/null +++ b/python/testData/inspections/ReplaceMethod_after.py @@ -0,0 +1 @@ +a = input() \ No newline at end of file diff --git a/python/testData/inspections/ReplaceNotEqOperator.py b/python/testData/inspections/ReplaceNotEqOperator.py new file mode 100644 index 000000000000..b611e77936d2 --- /dev/null +++ b/python/testData/inspections/ReplaceNotEqOperator.py @@ -0,0 +1 @@ +print(a <> b) \ No newline at end of file diff --git a/python/testData/inspections/ReplaceNotEqOperator_after.py b/python/testData/inspections/ReplaceNotEqOperator_after.py new file mode 100644 index 000000000000..172ad0a4dc9c --- /dev/null +++ b/python/testData/inspections/ReplaceNotEqOperator_after.py @@ -0,0 +1 @@ +print(a != b) \ No newline at end of file diff --git a/python/testData/inspections/ReplaceOctalNumericLiteral.py b/python/testData/inspections/ReplaceOctalNumericLiteral.py new file mode 100644 index 000000000000..4492d956c159 --- /dev/null +++ b/python/testData/inspections/ReplaceOctalNumericLiteral.py @@ -0,0 +1 @@ +a = 01223 \ No newline at end of file diff --git a/python/testData/inspections/ReplaceOctalNumericLiteral_after.py b/python/testData/inspections/ReplaceOctalNumericLiteral_after.py new file mode 100644 index 000000000000..05978836c162 --- /dev/null +++ b/python/testData/inspections/ReplaceOctalNumericLiteral_after.py @@ -0,0 +1 @@ +a = 0o1223 \ No newline at end of file diff --git a/python/testData/inspections/ReplaceRaiseStatement.py b/python/testData/inspections/ReplaceRaiseStatement.py new file mode 100644 index 000000000000..eb01804a0972 --- /dev/null +++ b/python/testData/inspections/ReplaceRaiseStatement.py @@ -0,0 +1 @@ +raise a, b, c \ No newline at end of file diff --git a/python/testData/inspections/ReplaceRaiseStatement_after.py b/python/testData/inspections/ReplaceRaiseStatement_after.py new file mode 100644 index 000000000000..928fd2a7b058 --- /dev/null +++ b/python/testData/inspections/ReplaceRaiseStatement_after.py @@ -0,0 +1 @@ +raise a(b).with_traceback(c) \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java index 47b19e212ac2..2d5529bf7aa1 100644 --- a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java +++ b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java @@ -12,6 +12,9 @@ import com.jetbrains.python.fixtures.PyLightFixtureTestCase; import com.jetbrains.python.inspections.PyMethodParametersInspection; import com.jetbrains.python.inspections.PyTrailingSemicolonInspection; import com.jetbrains.python.inspections.PyUnresolvedReferencesInspection; +import com.jetbrains.python.inspections.PyUnsupportedFeaturesInspection; +import com.jetbrains.python.psi.LanguageLevel; +import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher; import com.jetbrains.python.sdk.PythonSdkType; import org.jetbrains.annotations.NonNls; @@ -88,6 +91,69 @@ public class PyQuickFixTest extends PyLightFixtureTestCase { true, true); } + public void testReplaceExceptPartTo2() throws Exception { + doInspectionTest("ReplaceExceptPartTo2.py", PyUnsupportedFeaturesInspection.class, PyBundle.message("QFIX.replace.except.part"), true, + true); + } + + public void testReplaceNotEqOperator() throws Exception { + doInspectionTestWithPy3k("ReplaceNotEqOperator.py", PyUnsupportedFeaturesInspection.class, + PyBundle.message("QFIX.replace.noteq.operator"), true, true); + } + + public void testReplaceBackquoteExpression() throws Exception { + doInspectionTestWithPy3k("ReplaceBackquoteExpression.py", PyUnsupportedFeaturesInspection.class, + PyBundle.message("QFIX.replace.backquote.expression"), true, true); + } + + public void testReplaceMethod() throws Exception { + doInspectionTestWithPy3k("ReplaceMethod.py", PyUnsupportedFeaturesInspection.class, PyBundle.message("QFIX.replace.method"), + true, true); + } + + public void testRemoveLeadingU() throws Exception { + doInspectionTestWithPy3k("RemoveLeadingU.py", PyUnsupportedFeaturesInspection.class, PyBundle.message("QFIX.remove.leading.u"), true, true); + } + + public void testTrailingL() throws Exception { + doInspectionTestWithPy3k("RemoveTrailingL.py", PyUnsupportedFeaturesInspection.class, PyBundle.message("QFIX.remove.trailing.l"), true, true); + } + + public void testReplaceOctalNumericLiteral() throws Exception { + doInspectionTestWithPy3k("ReplaceOctalNumericLiteral.py", PyUnsupportedFeaturesInspection.class, + PyBundle.message("QFIX.replace.octal.numeric.literal"), true, true); + } + + public void testReplaceRaiseStatement() throws Exception { + doInspectionTestWithPy3k("ReplaceRaiseStatement.py", PyUnsupportedFeaturesInspection.class, + PyBundle.message("QFIX.replace.raise.statement"), true, true); + } + + public void testReplaceExceptPartTo3() throws Exception { + doInspectionTestWithPy3k("ReplaceExceptPartTo3.py", PyUnsupportedFeaturesInspection.class, PyBundle.message("QFIX.replace.except.part"), + true, true); + } + + public void testReplaceListComprehensions() throws Exception { + doInspectionTestWithPy3k("ReplaceListComprehensions.py", PyUnsupportedFeaturesInspection.class, + PyBundle.message("QFIX.replace.list.comprehensions"), true, true); + } + + protected void doInspectionTestWithPy3k(@NonNls String testFileName, + final Class inspectionClass, + @NonNls String quickFixName, + boolean applyFix, + boolean available) throws Exception { + PythonLanguageLevelPusher.FORCE_LANGUAGE_LEVEL = LanguageLevel.PYTHON30; + PythonLanguageLevelPusher.pushLanguageLevel(myFixture.getProject()); + try { + doInspectionTest(testFileName, inspectionClass, quickFixName, applyFix, available); + } + finally { + PythonLanguageLevelPusher.FORCE_LANGUAGE_LEVEL = null; + } + } + protected @NonNls String getTestDataPath() { diff --git a/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java b/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java index 30b9da6a16ed..2bda35178d80 100644 --- a/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java +++ b/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java @@ -19,6 +19,17 @@ public class PythonInspectionsTest extends PyLightFixtureTestCase { private void doTest(String testName, LocalInspectionTool localInspectionTool) throws Throwable { myFixture.testInspection("inspections/" + testName, new LocalInspectionToolWrapper(localInspectionTool)); } + + private void doTestWithPy3k(String testName, LocalInspectionTool localInspectionTool) throws Throwable { + PythonLanguageLevelPusher.FORCE_LANGUAGE_LEVEL = LanguageLevel.PYTHON30; + PythonLanguageLevelPusher.pushLanguageLevel(myFixture.getProject()); + try { + doTest(testName, localInspectionTool); + } + finally { + PythonLanguageLevelPusher.FORCE_LANGUAGE_LEVEL = null; + } + } public void testPyMethodFirstArgAssignmentInspection() throws Throwable { LocalInspectionTool inspection = new PyMethodFirstArgAssignmentInspection(); @@ -41,15 +52,8 @@ public class PythonInspectionsTest extends PyLightFixtureTestCase { } public void testPyArgumentListInspection3K() throws Throwable { - PythonLanguageLevelPusher.FORCE_LANGUAGE_LEVEL = LanguageLevel.PYTHON30; - PythonLanguageLevelPusher.pushLanguageLevel(myFixture.getProject()); - try { - LocalInspectionTool inspection = new PyArgumentListInspection(); - doTest(getTestName(false), inspection); - } - finally { - PythonLanguageLevelPusher.FORCE_LANGUAGE_LEVEL = null; - } + LocalInspectionTool inspection = new PyArgumentListInspection(); + doTestWithPy3k(getTestName(false), inspection); } public void testPyRedeclarationInspection() throws Throwable { @@ -76,4 +80,9 @@ public class PythonInspectionsTest extends PyLightFixtureTestCase { LocalInspectionTool inspection = new PyUnusedLocalVariableInspection(); doTest(getTestName(false), inspection); } + + public void testPyUnsupportedFeaturesInspection() throws Throwable { + LocalInspectionTool inspection = new PyUnsupportedFeaturesInspection(); + doTestWithPy3k(getTestName(false), inspection); + } }