diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index 87aa317dd111..cfc81339bb09 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -57,6 +57,7 @@ INTN.Family.toggle.import.alias=Toggle import alias INTN.Family.convert.except.part=Convert except part to supported form INTN.Family.convert.set.literal=Convert set literal two supported forms INTN.Family.convert.builtin=Convert builtin module import +INTN.Family.convert.dict.comp.expression=Convert dictionary comprehension expression INTN.convert.to.from.$0.import.$1=Convert to ''from {0} import {1}'' INTN.convert.to.import.$0=Convert to ''import {0}'' @@ -71,6 +72,8 @@ INTN.convert.set.literal.to=Convert set literal to 'set' method call INTN.convert.builtin.import=Convert builtin module import to supported form +INTN.convert.dict.comp.to=Convert dictionary comprehension to 'dict' method call + # Conflict checker CONFLICT.name.$0.obscured=Name ''{0}'' obscured by local definitions CONFLICT.name.$0.obscured.cannot.convert=Name ''{0}'' obscured. Cannot convert. diff --git a/python/src/com/jetbrains/python/PythonLanguage.java b/python/src/com/jetbrains/python/PythonLanguage.java index 4ccedab551dd..5d0c21cfc513 100644 --- a/python/src/com/jetbrains/python/PythonLanguage.java +++ b/python/src/com/jetbrains/python/PythonLanguage.java @@ -35,6 +35,7 @@ public class PythonLanguage extends Language { _annotators.add(ImportAnnotator.class); _annotators.add(StringConstantAnnotator.class); _annotators.add(PyBuiltinAnnotator.class); + _annotators.add(UnsupportedFeaturesIn2.class); } diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/ConvertDictCompIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/ConvertDictCompIntention.java new file mode 100644 index 000000000000..cb6acb3a0f90 --- /dev/null +++ b/python/src/com/jetbrains/python/codeInsight/intentions/ConvertDictCompIntention.java @@ -0,0 +1,81 @@ +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.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +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.*; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +/** + * Created by IntelliJ IDEA. + * User: Alexey.Ivanov + * Date: 20.02.2010 + * Time: 15:49:35 + */ +public class ConvertDictCompIntention implements IntentionAction { + @NotNull + @Override + public String getText() { + return PyBundle.message("INTN.convert.dict.comp.to"); + } + + @NotNull + @Override + public String getFamilyName() { + return PyBundle.message("INTN.Family.convert.dict.comp.expression"); + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + VirtualFile virtualFile = file.getVirtualFile(); + if (virtualFile != null && !LanguageLevel.forFile(virtualFile).isPy3K()) { + PsiElement element = file.findElementAt(editor.getCaretModel().getOffset()); + PyDictCompExpression expression = PsiTreeUtil.getParentOfType(element, PyDictCompExpression.class); + if (expression == null) { + return false; + } + return expression.getResultExpression() instanceof PyKeyValueExpression; + } + return false; + } + + // TODO: {k, v for k in range(4) for v in range(4)} + @Override + public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + VirtualFile virtualFile = file.getVirtualFile(); + if (virtualFile != null && !LanguageLevel.forFile(virtualFile).isPy3K()) { + PsiElement element = file.findElementAt(editor.getCaretModel().getOffset()); + PyDictCompExpression expression = PsiTreeUtil.getParentOfType(element, PyDictCompExpression.class); + assert expression != null; + replaceComprehension(project, expression); + } + } + + private static void replaceComprehension(Project project, PyDictCompExpression expression) { + List forComponents = expression.getForComponents(); + if (expression.getResultExpression() instanceof PyKeyValueExpression) { + PyKeyValueExpression keyValueExpression = (PyKeyValueExpression)expression.getResultExpression(); + PyElementGenerator elementGenerator = PythonLanguage.getInstance().getElementGenerator(); + assert keyValueExpression.getValue() != null; + expression.replace(elementGenerator.createFromText(project, PyExpressionStatement.class, + "dict([(" + keyValueExpression.getKey().getText() + ", " + + keyValueExpression.getValue().getText() + ") for " + + forComponents.get(0).getIteratorVariable().getText() + " in " + + forComponents.get(0).getIteratedList().getText() + "])")); + } + } + + @Override + public boolean startInWriteAction() { + return true; + } +} diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/ConvertSetLiteralIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/ConvertSetLiteralIntention.java new file mode 100644 index 000000000000..59410a28b6c4 --- /dev/null +++ b/python/src/com/jetbrains/python/codeInsight/intentions/ConvertSetLiteralIntention.java @@ -0,0 +1,67 @@ +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.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +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.*; +import org.jetbrains.annotations.NotNull; + +/** + * Created by IntelliJ IDEA. + * User: Alexey.Ivanov + * Date: 16.02.2010 + * Time: 21:33:28 + */ +public class ConvertSetLiteralIntention implements IntentionAction { + @NotNull + public String getText() { + return PyBundle.message("INTN.convert.set.literal.to"); + } + + @NotNull + public String getFamilyName() { + return PyBundle.message("INTN.Family.convert.set.literal"); + } + + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + VirtualFile virtualFile = file.getVirtualFile(); + if (virtualFile != null && !LanguageLevel.forFile(virtualFile).isPy3K()) { + PsiElement element = file.findElementAt(editor.getCaretModel().getOffset()); + PySetLiteralExpression setLiteral = PsiTreeUtil.getParentOfType(element, PySetLiteralExpression.class); + return (setLiteral != null); + } + return false; + } + + public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + VirtualFile virtualFile = file.getVirtualFile(); + if (virtualFile != null && !LanguageLevel.forFile(virtualFile).isPy3K()) { + PsiElement element = file.findElementAt(editor.getCaretModel().getOffset()); + PySetLiteralExpression setLiteral = PsiTreeUtil.getParentOfType(element, PySetLiteralExpression.class); + assert setLiteral != null; + PyExpression[] expressions = setLiteral.getElements(); + if (expressions != null) { + PyElementGenerator elementGenerator = PythonLanguage.getInstance().getElementGenerator(); + assert expressions.length != 0; + StringBuilder stringBuilder = new StringBuilder(expressions[0].getText()); + for (int i = 1; i < expressions.length; ++i) { + stringBuilder.append(", "); + stringBuilder.append(expressions[i].getText()); + } + PyStatement newElement = elementGenerator.createFromText(project, PyExpressionStatement.class, "set([" + stringBuilder.toString() + "])"); + setLiteral.replace(newElement); + } + } + } + + public boolean startInWriteAction() { + return true; + } +} diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/ReplaceBuiltinsIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/ReplaceBuiltinsIntention.java new file mode 100644 index 000000000000..0fabde84c85e --- /dev/null +++ b/python/src/com/jetbrains/python/codeInsight/intentions/ReplaceBuiltinsIntention.java @@ -0,0 +1,61 @@ +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.PsiElement; +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.*; +import org.jetbrains.annotations.NotNull; + +/** + * Created by IntelliJ IDEA. + * User: Alexey.Ivanov + * Date: 19.02.2010 + * Time: 18:50:24 + */ +public class ReplaceBuiltinsIntention implements IntentionAction { + @NotNull + public String getText() { + return PyBundle.message("INTN.convert.builtin.import"); + } + + @NotNull + public String getFamilyName() { + return PyBundle.message("INTN.Family.convert.builtin"); + } + + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + PsiElement element = file.findElementAt(editor.getCaretModel().getOffset()); + PyImportStatement importStatement = PsiTreeUtil.getParentOfType(element, PyImportStatement.class); + return (importStatement != null); + } + + public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + PyElementGenerator elementGenerator = PythonLanguage.getInstance().getElementGenerator(); + PsiElement element = file.findElementAt(editor.getCaretModel().getOffset()); + PyImportStatement importStatement = PsiTreeUtil.getParentOfType(element, PyImportStatement.class); + for (PyImportElement importElement : importStatement.getImportElements()) { + PyReferenceExpression importReference = importElement.getImportReference(); + if (importReference != null) { + if (LanguageLevel.forFile(file.getVirtualFile()).isPy3K()) { + if ("__builtin__".equals(importReference.getName())) { + importReference.replace(elementGenerator.createFromText(project, PyReferenceExpression.class, "builtins")); + } + } else { + if ("builtins".equals(importReference.getName())) { + importReference.replace(elementGenerator.createFromText(project, PyReferenceExpression.class, "__builtin__")); + } + } + } + } + } + + public boolean startInWriteAction() { + return true; + } +} diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/ReplaceExceptPartIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/ReplaceExceptPartIntention.java new file mode 100644 index 000000000000..556ff933ecc5 --- /dev/null +++ b/python/src/com/jetbrains/python/codeInsight/intentions/ReplaceExceptPartIntention.java @@ -0,0 +1,63 @@ +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.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiWhiteSpace; +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.LanguageLevel; +import com.jetbrains.python.psi.PyElementGenerator; +import com.jetbrains.python.psi.PyExceptPart; +import org.jetbrains.annotations.NotNull; + +/** + * Created by IntelliJ IDEA. + * User: Alexey.Ivanov + * Date: 16.02.2010 + * Time: 18:07:55 + */ +public class ReplaceExceptPartIntention implements IntentionAction { + @NotNull + public String getText() { + return PyBundle.message("INTN.convert.except.to"); + } + + @NotNull + public String getFamilyName() { + return PyBundle.message("INTN.Family.convert.except.part"); + } + + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + VirtualFile virtualFile = file.getVirtualFile(); + if (virtualFile != null && !LanguageLevel.forFile(virtualFile).isPy3K()) { + PsiElement element = file.findElementAt(editor.getCaretModel().getOffset()); + PyExceptPart exceptPart = PsiTreeUtil.getParentOfType(element, PyExceptPart.class); + return (exceptPart != null); + } + return false; + } + + public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + PyExceptPart exceptPart = PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyExceptPart.class); + PyElementGenerator elementGenerator = PythonLanguage.getInstance().getElementGenerator(); + assert exceptPart != null; + PsiElement element = exceptPart.getExceptClass().getNextSibling(); + while (element instanceof PsiWhiteSpace) { + element = element.getNextSibling(); + } + assert element != null; + element = element.replace(elementGenerator.createComma(project).getPsi()); + assert element.getPrevSibling() instanceof PsiWhiteSpace; + element.getPrevSibling().delete(); + } + + public boolean startInWriteAction() { + return true; + } +} diff --git a/python/src/com/jetbrains/python/inspections/PyUnsupportedFeaturesInspection.java b/python/src/com/jetbrains/python/inspections/PyUnsupportedFeaturesInspection.java index 2944c3fb2a1d..8c2dd7d3b5dd 100644 --- a/python/src/com/jetbrains/python/inspections/PyUnsupportedFeaturesInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyUnsupportedFeaturesInspection.java @@ -62,14 +62,18 @@ public class PyUnsupportedFeaturesInspection extends LocalInspectionTool { REMOVED_METHODS.add("reload"); } + private static boolean isPy3K(PyElement node) { + VirtualFile virtualFile = node.getContainingFile().getVirtualFile(); + return virtualFile != null && LanguageLevel.forFile(virtualFile).isPy3K(); + } + 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 (isPy3K(node)) { if (node.isOperator("<>")) { registerProblem(node, "<> is not supported in Python 3, use != instead", new ReplaceNotEqOperatorQuickFix()); } @@ -78,8 +82,7 @@ public class PyUnsupportedFeaturesInspection extends LocalInspectionTool { @Override public void visitPyNumericLiteralExpression(PyNumericLiteralExpression node) { - VirtualFile virtualFile = node.getContainingFile().getVirtualFile(); - if (virtualFile != null && LanguageLevel.forFile(virtualFile).isPy3K()) { + if (isPy3K(node)) { String text = node.getText(); if (text.endsWith("l") || text.endsWith("L")) { registerProblem(node, "Integer literals do not support a trailing \'l\' or \'L\' in Python 3", new RemoveTrailingLQuickFix()); @@ -92,8 +95,7 @@ public class PyUnsupportedFeaturesInspection extends LocalInspectionTool { @Override public void visitPyStringLiteralExpression(PyStringLiteralExpression node) { - VirtualFile virtualFile = node.getContainingFile().getVirtualFile(); - if (virtualFile != null && LanguageLevel.forFile(virtualFile).isPy3K()) { + if (isPy3K(node)) { String text = node.getText(); if (text.startsWith("u") || text.startsWith("U")) { registerProblem(node, "String literals do not support a leading \'u\' or \'U\' in Python 3", new ReamoveLeadingUQuickFix()); @@ -101,13 +103,14 @@ public class PyUnsupportedFeaturesInspection extends LocalInspectionTool { } } + @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 do not support this syntax in Python 3", new ReplaceListComprehensionsQuickFix()); + registerProblem(iteratedList, "List comprehensions do not support such syntax in Python 3", new ReplaceListComprehensionsQuickFix()); } } } @@ -116,16 +119,13 @@ public class PyUnsupportedFeaturesInspection extends LocalInspectionTool { 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, "Python 3 does not support this syntax", new ReplaceExceptPartQuickFix(true)); - } + if (isPy3K(node)) { + PsiElement element = exceptClass.getNextSibling(); + while (element instanceof PsiWhiteSpace) { + element = element.getNextSibling(); + } + if (element != null && ",".equals(element.getText())) { + registerProblem(node, "Python 3 does not support this syntax", new ReplaceExceptPartQuickFix(true)); } } } @@ -134,68 +134,60 @@ public class PyUnsupportedFeaturesInspection extends LocalInspectionTool { @Override public void visitPyRaiseStatement(PyRaiseStatement node) { PyExpression[] expressions = node.getExpressions(); - assert(expressions != null); + 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, "Python 3 does not support this syntax", new ReplaceRaiseStatementQuickFix()); - return; - } - PsiElement element = expressions[0].getNextSibling(); - while (element instanceof PsiWhiteSpace) { - element = element.getNextSibling(); - } - if (element != null && ",".equals(element.getText())) { - registerProblem(node, "Python 3 does not support this 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, "Python 2 does not support raise ... from syntax"); - } - } + if (isPy3K(node)) { + if (expressions.length == 3) { + registerProblem(node, "Python 3 does not support this syntax", new ReplaceRaiseStatementQuickFix()); + return; + } + PsiElement element = expressions[0].getNextSibling(); + while (element instanceof PsiWhiteSpace) { + element = element.getNextSibling(); + } + if (element != null && ",".equals(element.getText())) { + registerProblem(node, "Python 3 does not support this syntax", new ReplaceRaiseStatementQuickFix()); } } } @Override public void visitPyReprExpression(PyReprExpression node) { - VirtualFile virtualFile = node.getContainingFile().getVirtualFile(); - if (virtualFile != null && LanguageLevel.forFile(virtualFile).isPy3K()) { + if (isPy3K(node)) { registerProblem(node, "Backquote is not supported in Python 3, 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"); + public void visitPyImportStatement(PyImportStatement node) { + if (isPy3K(node)) { + PyImportElement[] importElements = node.getImportElements(); + for (PyImportElement importElement : importElements) { + PyReferenceExpression importReference = importElement.getImportReference(); + if (importReference != null) { + String name = importReference.getName(); + if ("__builtin__".equals(name)) { + registerProblem(node, "Module __builtin__ renamed to builtins"); } } } } } + + @Override + public void visitPyCallExpression(PyCallExpression node) { + if (isPy3K(node)) { + String name = node.getCallee().getName(); + 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)); + } + } + } } } diff --git a/python/src/com/jetbrains/python/psi/PyDictCompExpression.java b/python/src/com/jetbrains/python/psi/PyDictCompExpression.java index 588a9a52c966..405e16c47cbe 100644 --- a/python/src/com/jetbrains/python/psi/PyDictCompExpression.java +++ b/python/src/com/jetbrains/python/psi/PyDictCompExpression.java @@ -1,9 +1,14 @@ package com.jetbrains.python.psi; +import java.util.List; + /** * Dict comprehension: {x:x+1 for x in range(10)} * * @author yole */ public interface PyDictCompExpression extends PyExpression, NameDefiner { + PyExpression getResultExpression(); + List getForComponents(); + List getIfComponents(); } diff --git a/python/src/com/jetbrains/python/psi/PyElementVisitor.java b/python/src/com/jetbrains/python/psi/PyElementVisitor.java index c5c70e801161..0b0f6d9d6df4 100644 --- a/python/src/com/jetbrains/python/psi/PyElementVisitor.java +++ b/python/src/com/jetbrains/python/psi/PyElementVisitor.java @@ -217,4 +217,8 @@ public class PyElementVisitor extends PsiElementVisitor { public void visitPyReprExpression(PyReprExpression node) { visitPyExpression(node); } + + public void visitPyNonlocalStatement(PyNonlocalStatement node) { + visitPyStatement(node); + } } diff --git a/python/src/com/jetbrains/python/psi/PyNonlocalStatement.java b/python/src/com/jetbrains/python/psi/PyNonlocalStatement.java index 227ff051dcc8..06253ab36c72 100644 --- a/python/src/com/jetbrains/python/psi/PyNonlocalStatement.java +++ b/python/src/com/jetbrains/python/psi/PyNonlocalStatement.java @@ -4,4 +4,5 @@ package com.jetbrains.python.psi; * @author yole */ public interface PyNonlocalStatement extends PyStatement { + void accept(PyElementVisitor visitor); } diff --git a/python/src/com/jetbrains/python/psi/PySetLiteralExpression.java b/python/src/com/jetbrains/python/psi/PySetLiteralExpression.java index 0ae7114b252b..ac0a9c4cc8f8 100644 --- a/python/src/com/jetbrains/python/psi/PySetLiteralExpression.java +++ b/python/src/com/jetbrains/python/psi/PySetLiteralExpression.java @@ -1,9 +1,13 @@ package com.jetbrains.python.psi; +import org.jetbrains.annotations.Nullable; + /** * Represents a Python 3 set literal expression, for example, {1, 2, 3} * * @author yole */ public interface PySetLiteralExpression extends PyExpression { + @Nullable + PyExpression[] getElements(); } diff --git a/python/src/com/jetbrains/python/psi/impl/PyNonlocalStatementImpl.java b/python/src/com/jetbrains/python/psi/impl/PyNonlocalStatementImpl.java index 8cd88b8f7001..aec53977629e 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyNonlocalStatementImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyNonlocalStatementImpl.java @@ -1,6 +1,7 @@ package com.jetbrains.python.psi.impl; import com.intellij.lang.ASTNode; +import com.jetbrains.python.psi.PyElementVisitor; import com.jetbrains.python.psi.PyNonlocalStatement; /** @@ -10,4 +11,8 @@ public class PyNonlocalStatementImpl extends PyElementImpl implements PyNonlocal public PyNonlocalStatementImpl(ASTNode astNode) { super(astNode); } + + public void accept(PyElementVisitor visitor) { + visitor.visitPyNonlocalStatement(this); + } } diff --git a/python/src/com/jetbrains/python/psi/impl/PySetLiteralExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PySetLiteralExpressionImpl.java index f260bfb64fdc..fc784bb92c10 100644 --- a/python/src/com/jetbrains/python/psi/impl/PySetLiteralExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PySetLiteralExpressionImpl.java @@ -1,9 +1,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.PySetLiteralExpression; import com.jetbrains.python.psi.types.PyType; +import org.jetbrains.annotations.Nullable; /** * @author yole @@ -21,4 +24,9 @@ public class PySetLiteralExpressionImpl extends PyElementImpl implements PySetLi protected void acceptPyVisitor(PyElementVisitor pyVisitor) { pyVisitor.visitPySetLiteralExpression(this); } + + @Nullable + public PyExpression[] getElements() { + return PsiTreeUtil.getChildrenOfType(this, PyExpression.class); + } } diff --git a/python/src/com/jetbrains/python/validation/UnsupportedFeaturesIn2.java b/python/src/com/jetbrains/python/validation/UnsupportedFeaturesIn2.java new file mode 100644 index 000000000000..053e14769c66 --- /dev/null +++ b/python/src/com/jetbrains/python/validation/UnsupportedFeaturesIn2.java @@ -0,0 +1,91 @@ +package com.jetbrains.python.validation; + +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiWhiteSpace; +import com.jetbrains.python.codeInsight.intentions.ConvertDictCompIntention; +import com.jetbrains.python.codeInsight.intentions.ConvertSetLiteralIntention; +import com.jetbrains.python.codeInsight.intentions.ReplaceBuiltinsIntention; +import com.jetbrains.python.codeInsight.intentions.ReplaceExceptPartIntention; +import com.jetbrains.python.psi.*; + +/** + * Created by IntelliJ IDEA. + * User: Alexey.Ivanov + * Date: 16.02.2010 + * Time: 16:16:45 + */ +public class UnsupportedFeaturesIn2 extends PyAnnotator { + + private static boolean isPy2(PyElement node) { + VirtualFile virtualFile = node.getContainingFile().getVirtualFile(); + return virtualFile != null && !LanguageLevel.forFile(virtualFile).isPy3K(); + } + + private static boolean isPy3K(PyElement node) { + VirtualFile virtualFile = node.getContainingFile().getVirtualFile(); + return virtualFile != null && LanguageLevel.forFile(virtualFile).isPy3K(); + } + + @Override + public void visitPyDictCompExpression(PyDictCompExpression node) { + if (isPy2(node)) { + getHolder().createWarningAnnotation(node, "Dictionary comprehension not supported in Python2").registerFix(new ConvertDictCompIntention()); + } + } + + @Override + public void visitPySetLiteralExpression(PySetLiteralExpression node) { + if (isPy2(node)) { + getHolder().createWarningAnnotation(node, "Python2 not supported set literal expressions").registerFix(new ConvertSetLiteralIntention()); + } + } + + @Override + public void visitPyExceptBlock(PyExceptPart node) { + if (isPy2(node)) { + PyExpression exceptClass = node.getExceptClass(); + if (exceptClass != null) { + PsiElement element = exceptClass.getNextSibling(); + while (element instanceof PsiWhiteSpace) { + element = element.getNextSibling(); + } + if (element != null && "as".equals(element.getText())) { + getHolder().createWarningAnnotation(node, "Python2 not supported such syntax").registerFix(new ReplaceExceptPartIntention()); + } + } + } + } + + @Override + public void visitPyImportStatement(PyImportStatement node) { + if (isPy2(node)) { + PyImportElement[] importElements = node.getImportElements(); + for (PyImportElement importElement : importElements) { + PyReferenceExpression importReference = importElement.getImportReference(); + if (importReference != null) { + String name = importReference.getName(); + if ("builtins".equals(name)) { + getHolder().createWarningAnnotation(node, "There is no module builtins in Python2").registerFix(new ReplaceBuiltinsIntention()); + } + } + } + } + } + + @Override + public void visitPyCallExpression(PyCallExpression node) { + if (isPy2(node)) { + PsiElement firstChild = node.getFirstChild(); + if (firstChild != null) { + String name = firstChild.getText(); + if ("super".equals(name)) { + PyArgumentList argumentList = node.getArgumentList(); + if (argumentList != null && argumentList.getArguments().length == 0) { + getHolder().createWarningAnnotation(node, "super() should have arguments in Python2"); + } + } + } + } + } +} diff --git a/python/testData/highlighting/unsupportedFeaturesInPython2.py b/python/testData/highlighting/unsupportedFeaturesInPython2.py new file mode 100644 index 000000000000..bbe0a70fa83f --- /dev/null +++ b/python/testData/highlighting/unsupportedFeaturesInPython2.py @@ -0,0 +1,12 @@ +{k: v for k, v in stuff} + +try: + pass +except a as name: + pass + +class A(B): + def foo(self): + super() + +{1, 2} \ No newline at end of file diff --git a/python/testData/intentions/afterConvertBuiltins.py b/python/testData/intentions/afterConvertBuiltins.py new file mode 100644 index 000000000000..c9fad1f39346 --- /dev/null +++ b/python/testData/intentions/afterConvertBuiltins.py @@ -0,0 +1 @@ +import __builtin__ \ No newline at end of file diff --git a/python/testData/intentions/afterConvertDictComp.py b/python/testData/intentions/afterConvertDictComp.py new file mode 100644 index 000000000000..961bcb8ff90d --- /dev/null +++ b/python/testData/intentions/afterConvertDictComp.py @@ -0,0 +1 @@ +dict([(k, chr(k + 65)) for k in range(10)]) \ No newline at end of file diff --git a/python/testData/intentions/afterConvertSetLiteral.py b/python/testData/intentions/afterConvertSetLiteral.py new file mode 100644 index 000000000000..c5cef98d5e76 --- /dev/null +++ b/python/testData/intentions/afterConvertSetLiteral.py @@ -0,0 +1 @@ +set([1, 2, 3, 5, 34]) \ No newline at end of file diff --git a/python/testData/intentions/afterReplaceExceptPart.py b/python/testData/intentions/afterReplaceExceptPart.py new file mode 100644 index 000000000000..db7da33e5d91 --- /dev/null +++ b/python/testData/intentions/afterReplaceExceptPart.py @@ -0,0 +1,4 @@ +try: + pass +except e, name: + pass \ No newline at end of file diff --git a/python/testData/intentions/beforeConvertBuiltins.py b/python/testData/intentions/beforeConvertBuiltins.py new file mode 100644 index 000000000000..934553c2704a --- /dev/null +++ b/python/testData/intentions/beforeConvertBuiltins.py @@ -0,0 +1 @@ +import builtins \ No newline at end of file diff --git a/python/testData/intentions/beforeConvertDictComp.py b/python/testData/intentions/beforeConvertDictComp.py new file mode 100644 index 000000000000..fb382fce70fd --- /dev/null +++ b/python/testData/intentions/beforeConvertDictComp.py @@ -0,0 +1 @@ +{k: chr(k + 65) for k in range(10)} \ No newline at end of file diff --git a/python/testData/intentions/beforeConvertSetLiteral.py b/python/testData/intentions/beforeConvertSetLiteral.py new file mode 100644 index 000000000000..48915002b753 --- /dev/null +++ b/python/testData/intentions/beforeConvertSetLiteral.py @@ -0,0 +1 @@ +{1, 2, 3, 5, 34} \ No newline at end of file diff --git a/python/testData/intentions/beforeReplaceExceptPart.py b/python/testData/intentions/beforeReplaceExceptPart.py new file mode 100644 index 000000000000..d6adc9bbbf97 --- /dev/null +++ b/python/testData/intentions/beforeReplaceExceptPart.py @@ -0,0 +1,4 @@ +try: + pass +except e as name: + pass \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyIntentionTest.java b/python/testSrc/com/jetbrains/python/PyIntentionTest.java new file mode 100644 index 000000000000..ccaa0040b96b --- /dev/null +++ b/python/testSrc/com/jetbrains/python/PyIntentionTest.java @@ -0,0 +1,41 @@ +package com.jetbrains.python; + +import com.intellij.codeInsight.intention.IntentionAction; +import com.jetbrains.python.fixtures.PyLightFixtureTestCase; + +/** + * Created by IntelliJ IDEA. + * User: Alexey.Ivanov + * Date: 25.02.2010 + * Time: 12:45:53 + */ +public class PyIntentionTest extends PyLightFixtureTestCase { + + private void doTest(String hint) throws Exception { + myFixture.configureByFile("before" + getTestName(false) + ".py"); + final IntentionAction action = myFixture.findSingleIntention(hint); + myFixture.launchAction(action); + myFixture.checkResultByFile("after" + getTestName(false) + ".py"); + + } + + protected String getTestDataPath() { + return PythonTestUtil.getTestDataPath() + "/intentions/"; + } + + public void testConvertDictComp() throws Exception { + doTest(PyBundle.message("INTN.convert.dict.comp.to")); + } + + public void testConvertSetLiteral() throws Exception { + doTest(PyBundle.message("INTN.convert.set.literal.to")); + } + + public void testReplaceExceptPart() throws Exception { + doTest(PyBundle.message("INTN.convert.except.to")); + } + + public void testConvertBuiltins() throws Exception { + doTest(PyBundle.message("INTN.convert.builtin.import")); + } +} diff --git a/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java b/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java index 05c44ea9a82b..dcbd7748dc58 100644 --- a/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java +++ b/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java @@ -107,6 +107,10 @@ public class PythonHighlightingTest extends PyLightFixtureTestCase { doTest(); } + public void testUnsupportedFeaturesInPython2() throws Exception { + doTest(LanguageLevel.PYTHON26, true, false); + } + public void testYieldInNestedFunction() throws Exception { // highlight func declaration first, lest we get an "Extra fragment highlighted" error. EditorColorsManager manager = EditorColorsManager.getInstance();