diff --git a/python/resources/idea/PythonApplicationInfo.xml b/python/resources/idea/PythonApplicationInfo.xml index 46258b020048..0e64fe8e1a02 100644 --- a/python/resources/idea/PythonApplicationInfo.xml +++ b/python/resources/idea/PythonApplicationInfo.xml @@ -2,7 +2,7 @@ - + diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml index ef8b5d162ef8..b7f929e1e6c8 100644 --- a/python/src/META-INF/python-plugin-common.xml +++ b/python/src/META-INF/python-plugin-common.xml @@ -3,7 +3,7 @@ - com.jetbrains.python.PyInspectionToolProvider + com.jetbrains.python.inspections.PythonPyInspectionToolProvider @@ -74,7 +74,6 @@ - diff --git a/python/src/com/jetbrains/python/PyBundle.java b/python/src/com/jetbrains/python/PyBundle.java new file mode 100644 index 000000000000..1af25917df61 --- /dev/null +++ b/python/src/com/jetbrains/python/PyBundle.java @@ -0,0 +1,39 @@ +package com.jetbrains.python; + +import com.intellij.CommonBundle; +import com.intellij.reference.SoftReference; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.PropertyKey; + +import java.lang.ref.Reference; +import java.util.ResourceBundle; + +// A copy of Ruby's. +/** + * Resource bundle access. + * Date: Nov 25, 2008 2:36:10 AM + */ +public class PyBundle { + private static Reference ourBundle; + + @NonNls + private static final String BUNDLE = "com.jetbrains.python.PyBundle"; + + private PyBundle() { + } + + public static String message(@PropertyKey(resourceBundle = BUNDLE)String key, Object... params) { + return CommonBundle.message(getBundle(), key, params); + } + + // Cached loading + private static ResourceBundle getBundle() { + ResourceBundle bundle = null; + if (ourBundle != null) bundle = ourBundle.get(); + if (bundle == null) { + bundle = ResourceBundle.getBundle(BUNDLE); + ourBundle = new SoftReference(bundle); + } + return bundle; + } +} diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties new file mode 100644 index 000000000000..25ceac78cf43 --- /dev/null +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -0,0 +1,91 @@ +### Generic words ### +GNAME.function=function +GNAME.class=class +GNAME.var=variable +GNAME.item=item + +### Actions: ACT ### +ACT.FAMILY.import=import + +# AddImport +ACT.NAME.add.import=Add import + + +### Quick fixes ### +QFIX.add.parameter.self=Add parameter 'self' + + +### Inspections: INSP ### +INSP.GROUP.python=Python + +# PyArgumentListInspection +INSP.NAME.incorrect.call.arguments=Incorrect call arguments +INSP.duplicate.argument=Duplicate argument +INSP.duplicate.star.arg=Duplicate *arg +INSP.duplicate.doublestar.arg=Duplicate **arg +INSP.cannot.appear.past.keyword.arg=Cannot appear past keyword arguments +INSP.unexpected.arg=Unexpected argument +INSP.parameter.$0.unfilled=Parameter ''{0}'' unfilled + +# PyMethodParametersInspection +INSP.NAME.problematic.first.parameter=Methods having troubles with first parameter +INSP.must.have.first.parameter=Method must have a first parameter, usually called 'self' +INSP.probably.mistyped.self=Did not you mean 'self'? +INSP.usually.named.self=Usually first parameter of a method is named 'self' + +# PyRedeclarationInspection +INSP.NAME.redeclaration=Names redeclared without usage +INSP.shadows.same.named.$0.above=Shadows same-named {0} above + +# PyUnresolvedReferencesInspection +INSP.NAME.unresolved.refs=Unresolved references +INSP.module.$0.not.found=Module ''{0}'' not found +INSP.unresolved.ref.$0=Unresolved reference ''{0}'' +INSP.unresolved.ref.$0.for.class.$1=Unresolved attribute reference ''{0}'' for class ''{1}'' + +### Annotators ### +ANN.deleting.none=deleting None +ANN.assign.to.none=assignment to None +ANN.cant.assign.to.call=can't assign to function call +ANN.cant.delete.call=can't delete function call +ANN.cant.aug.assign.to.generator=augmented assign to generator expression not possible +ANN.cant.aug.assign.to.tuple.or.generator=augmented assign to tuple literal or generator expression not possible +ANN.cant.assign.to.generator=assign to generator expression not possible +ANN.cant.assign.to.operator=can't assign to operator +ANN.cant.assign.to.parens=can't assign to () +ANN.cant.assign.to.brackets=can't assign to [] +ANN.cant.aug.assign.to.list.or.comprh=augmented assign to list literal or comprehension not possible +ANN.cant.assign.to.comprh=assign to list comprehension not possible +ANN.cant.aug.assign.to.comprh=augmented assign to list comprehension not possible +ANN.cant.assign.to.literal=can't assign to literal +ANN.cant.assign.to.lambda=can't assign to lambda + +ANN.break.outside.loop='break' outside loop +ANN.continue.outside.loop='continue' outside loop +ANN.cant.continue.in.finally='continue' not supported inside 'finally' clause + +### parsing +PARSE.expected.expression=expression expected +PARSE.expected.rbracket=']' expected +PARSE.expected.expr.or.comma.or.bracket=expected expression, ',' or ']' +PARSE.expected.in='in' expected +PARSE.expected.for.or.bracket=']' or 'for' expected +PARSE.expected.comma=',' expected +PARSE.expected.colon=':' expected +PARSE.expected.rpar=')' expected +PARSE.expected.lpar='(' expected +PARSE.expected.tick='`' (backtick) expected +PARSE.expected.name=name expected +PARSE.expected.colon.or.rbracket=':' or ']' expected +PARSE.expected.comma.or.rpar=',' or ')' expected +PARSE.expected.else='else' expected + +PARSE.expected.func.name=function name expected +PARSE.expected.comma.lpar.rpar=',' or '(' or ')' expected +PARSE.expected.statement.break=Statement break expected +PARSE.expected.@.or.def='@' or 'def' expected +PARSE.expected.formal.param.name=formal parameter name expected + + + + diff --git a/python/src/com/jetbrains/python/PyNames.java b/python/src/com/jetbrains/python/PyNames.java index 1d9d1882cfd3..84887878ed54 100644 --- a/python/src/com/jetbrains/python/PyNames.java +++ b/python/src/com/jetbrains/python/PyNames.java @@ -10,14 +10,16 @@ import java.util.Collections; * @author yole */ public class PyNames { - @NonNls public static final String NONE = "None"; - private PyNames() { } + @NonNls public static final String NONE = "None"; + @NonNls public static final String INIT = "__init__"; @NonNls public static final String OBJECT = "object"; + @NonNls public static final String DOT_PY = ".py"; + @NonNls private static Set _UnderscoredNames = new HashSet(); static { _UnderscoredNames.add("__abs__"); diff --git a/python/src/com/jetbrains/python/actions/AddSelfQuickfix.java b/python/src/com/jetbrains/python/actions/AddSelfQuickfix.java new file mode 100644 index 000000000000..7ccf931668e7 --- /dev/null +++ b/python/src/com/jetbrains/python/actions/AddSelfQuickfix.java @@ -0,0 +1,49 @@ +package com.jetbrains.python.actions; + +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.lang.Language; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.PythonLanguage; +import com.jetbrains.python.psi.PyElementGenerator; +import com.jetbrains.python.psi.PyParameter; +import com.jetbrains.python.psi.PyParameterList; +import com.jetbrains.python.psi.PyUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +/** + * Insert 'self' in a method that lacks any arguments + * User: dcheryasov + * Date: Nov 19, 2008 + */ +public class AddSelfQuickfix implements LocalQuickFix { + @NotNull + public String getName() { + return PyBundle.message("QFIX.add.parameter.self"); + } + + @NonNls + @NotNull + public String getFamilyName() { + return PyBundle.message("INSP.GROUP.python"); + } + + public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) { + PsiElement problem_elt = descriptor.getPsiElement(); + if (problem_elt instanceof PyParameterList) { + final PyParameterList param_list = (PyParameterList)problem_elt; + PyUtil.ensureWritable(problem_elt); + Language language = problem_elt.getLanguage(); + if (language instanceof PythonLanguage) { + final PythonLanguage pythonLanguage = (PythonLanguage)language; + PyElementGenerator generator = pythonLanguage.getElementGenerator(); + // TODO: generalize, move to generator + PyParameter new_param = generator.createFromText(project, PyParameter.class, "def f(self): pass", new int[]{0, 3, 1}); + param_list.addParameter(new_param); + } + } + } +} diff --git a/python/src/com/jetbrains/python/inspections/PyMethodParametersInspection.java b/python/src/com/jetbrains/python/inspections/PyMethodParametersInspection.java index 086dfc197761..383ccb15314e 100644 --- a/python/src/com/jetbrains/python/inspections/PyMethodParametersInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyMethodParametersInspection.java @@ -1,14 +1,17 @@ package com.jetbrains.python.inspections; -import com.intellij.codeInspection.LocalInspectionTool; -import com.intellij.codeInspection.ProblemsHolder; -import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.codeHighlighting.HighlightDisplayLevel; -import com.intellij.psi.PsiElementVisitor; -import com.intellij.psi.PsiElement; +import com.intellij.codeInspection.LocalInspectionTool; +import com.intellij.codeInspection.ProblemHighlightType; +import com.intellij.codeInspection.ProblemsHolder; import com.intellij.lang.ASTNode; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiElementVisitor; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.actions.AddSelfQuickfix; import com.jetbrains.python.psi.*; import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; /** @@ -20,13 +23,13 @@ public class PyMethodParametersInspection extends LocalInspectionTool { @Nls @NotNull public String getGroupDisplayName() { - return "Python"; // TODO: propertize + return PyBundle.message("INSP.GROUP.python"); } @Nls @NotNull public String getDisplayName() { - return "Method lacking first parameter"; // TODO: propertize + return PyBundle.message("INSP.NAME.problematic.first.parameter"); } @NotNull @@ -66,22 +69,32 @@ public class PyMethodParametersInspection extends LocalInspectionTool { // TODO: check for "staticmetod" ASTNode name_node = node.getNameNode(); if (name_node != null) { - registerProblem(name_node.getPsi(), "Method must have a first parameter, usually called 'self'", ProblemHighlightType.ERROR, null); + PsiElement open_paren = plist.getFirstChild(); + PsiElement close_paren = plist.getLastChild(); + if ( + open_paren != null && close_paren != null && + "(".equals(open_paren.getText()) && ")".equals(close_paren.getText()) + ) { + registerProblem( + plist, PyBundle.message("INSP.must.have.first.parameter"), + ProblemHighlightType.GENERIC_ERROR, null, new AddSelfQuickfix() + ); + } } } else { String pname = params[0].getText(); - // TODO: generate a better list, or use Levenstein's distance, etc. - for (String typo : new String[] {"elf", "sef", "sel", "slf", "sself", "seelf", "sellf"}) { + // every dup, swap, drop, or dup+drop of "self" + @NonNls String[] mangled = {"eslf", "sself", "elf", "felf", "slef", "seelf", "slf", "sslf", "sefl", "sellf", "sef", "seef"}; + for (String typo : mangled) { if (typo.equals(pname)) { - registerProblem(params[0].getNode().getPsi(), "Did not you mean 'self'?"); - // NOTE: test framework rejects weak warnings, which would be more appropriate + registerProblem(params[0].getNode().getPsi(), PyBundle.message("INSP.probably.mistyped.self")); return; } } // TODO: check for "classmethod" or "staticmetod" and style settings if (!"self".equals(pname)) { - registerProblem(plist, "Usually first parameter of a method is named 'self'"); + registerProblem(plist, PyBundle.message("INSP.usually.named.self")); } } } diff --git a/python/src/com/jetbrains/python/inspections/PyRedeclarationInspection.java b/python/src/com/jetbrains/python/inspections/PyRedeclarationInspection.java index d8ed4533a12a..6668fd17c838 100644 --- a/python/src/com/jetbrains/python/inspections/PyRedeclarationInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyRedeclarationInspection.java @@ -6,6 +6,7 @@ import com.intellij.codeInspection.ProblemsHolder; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementVisitor; import com.jetbrains.python.PyTokenTypes; +import com.jetbrains.python.PyBundle; import com.jetbrains.python.psi.*; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; @@ -20,13 +21,13 @@ public class PyRedeclarationInspection extends LocalInspectionTool { @Nls @NotNull public String getGroupDisplayName() { - return "Python"; // TODO: propertize + return PyBundle.message("INSP.GROUP.python"); } @Nls @NotNull public String getDisplayName() { - return "Duplicate definition"; // TODO: propertize + return PyBundle.message("INSP.NAME.redeclaration"); } @NotNull @@ -58,21 +59,24 @@ public class PyRedeclarationInspection extends LocalInspectionTool { // TODO: This function is a shame; replace with a proper interface. private static String _getKind(PsiElement elt) { - if (elt instanceof PyFunction) return "function"; - if (elt instanceof PyClass) return "class"; - if (elt instanceof PyTargetExpression) return "variable"; - return "item"; + if (elt instanceof PyFunction) return PyBundle.message("GNAME.function"); + if (elt instanceof PyClass) return PyBundle.message("GNAME.class"); + if (elt instanceof PyTargetExpression) return PyBundle.message("GNAME.var"); + return PyBundle.message("GNAME.item"); } - private void _checkAbove(PyElement node, String kind) { + private void _checkAbove(PyElement node, String kind) { String name = node.getName(); if (name != null) { PyResolveUtil.ResolveProcessor proc = new PyResolveUtil.ResolveProcessor(node.getName()); PyResolveUtil.treeCrawlUp(proc, node); PsiElement found = proc.getResult(); // TODO: check if the redefined name is used somehow - if (found != null) { - registerProblem(node.getNode().findChildByType(PyTokenTypes.IDENTIFIER).getPsi(), "Shadows same-named " + _getKind(found) + " above"); + if (found != null && ! (found instanceof PyTargetExpression)) { + registerProblem( + node.getNode().findChildByType(PyTokenTypes.IDENTIFIER).getPsi(), + PyBundle.message("INSP.shadows.same.named.$0.above", _getKind(found)) + ); //registerProblem(prev.getNode().findChildByType(PyTokenTypes.IDENTIFIER).getPsi(), "Overridden by same-named " + kind + " below"); } } @@ -82,17 +86,17 @@ public class PyRedeclarationInspection extends LocalInspectionTool { @Override public void visitPyFunction(final PyFunction node) { - _checkAbove(node, "function"); + _checkAbove(node, PyBundle.message("GNAME.function")); } @Override public void visitPyTargetExpression(final PyTargetExpression node) { - _checkAbove(node, "variable"); + _checkAbove(node, PyBundle.message("GNAME.var")); } @Override public void visitPyClass(final PyClass node) { - _checkAbove(node, "class"); + _checkAbove(node, PyBundle.message("GNAME.class")); } } } diff --git a/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java b/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java index ec206d64aa93..a5bb022b7073 100644 --- a/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java @@ -1,20 +1,22 @@ package com.jetbrains.python.inspections; import com.intellij.codeHighlighting.HighlightDisplayLevel; +import com.intellij.codeInspection.HintAction; import com.intellij.codeInspection.LocalInspectionTool; import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.codeInspection.ProblemsHolder; import com.intellij.lang.annotation.HighlightSeverity; +import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementVisitor; import com.intellij.psi.PsiReference; import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.python.PyBundle; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.types.PyClassType; import com.jetbrains.python.psi.types.PyModuleType; import com.jetbrains.python.psi.types.PyNoneType; import com.jetbrains.python.psi.types.PyType; -import com.jetbrains.python.validation.AddImportAction; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; @@ -27,13 +29,13 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool { @Nls @NotNull public String getGroupDisplayName() { - return "Python"; // TODO: propertize + return PyBundle.message("INSP.GROUP.python"); } @Nls @NotNull public String getDisplayName() { - return "Unresolved Python reference"; // TODO: propertize + return PyBundle.message("INSP.NAME.unresolved.refs"); } @NotNull @@ -76,6 +78,9 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool { if (reference.resolve() == null) { StringBuffer description_buf = new StringBuffer(""); String text = reference.getElement().getText(); + String ref_text = reference.getRangeInElement().substring(text); // text of the part we're working with + HintAction action = null; + if (ref_text.length() <= 0) return; // empty text, nothing to highlight if (reference instanceof PyReferenceExpression) { PyReferenceExpression refex = (PyReferenceExpression)reference; String refname = refex.getReferencedName(); @@ -90,8 +95,9 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool { if ((PsiTreeUtil.getParentOfType( PsiTreeUtil.getParentOfType(node, PyImportElement.class), PyTryExceptStatement.class, PyIfStatement.class) != null) ) { - severity = HighlightSeverity.WARNING; - description_buf.append("Module '").append(reference.getRangeInElement().substring(text)).append("' not found"); + severity = HighlightSeverity.INFO; + String errmsg = PyBundle.message("INSP.module.$0.not.found", ref_text); + description_buf.append(errmsg); } } if (reference instanceof PsiReferenceEx) { @@ -99,7 +105,7 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool { if (s != null) description_buf.append(s); } if (description_buf.length() == 0) { - description_buf.append("Unresolved reference '").append(reference.getRangeInElement().substring(text)).append("'"); + boolean marked_for_class = false; if (reference instanceof PyQualifiedExpression) { final PyExpression qexpr = ((PyQualifiedExpression)reference).getQualifier(); if (qexpr != null) { @@ -109,24 +115,27 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool { // this almost always means that we don't know the type, so don't show an error in this case continue; } - description_buf.append(" for class ").append(qtype.getName()); + description_buf.append(PyBundle.message("INSP.unresolved.ref.$0.for.class.$1", ref_text, qtype.getName())); + marked_for_class = true; } } } + if (! marked_for_class) { + description_buf.append(PyBundle.message("INSP.unresolved.ref.$0", ref_text)); + /*action = new AddImportAction(reference);*/ + } } String description = description_buf.toString(); ProblemHighlightType hl_type; - //final TextRange highlightRange = reference.getRangeInElement().shiftRight(reference.getElement().getTextRange().getStartOffset()); if (severity == HighlightSeverity.WARNING) { - //annotation = getHolder().createWarningAnnotation(highlightRange, description_buf.toString()); hl_type = ProblemHighlightType.GENERIC_ERROR_OR_WARNING; } else { - //annotation = getHolder().createErrorAnnotation(highlightRange, description_buf.toString()); - //annotation.setHighlightType(ProblemHighlightType.LIKE_UNKNOWN_SYMBOL); hl_type = ProblemHighlightType.LIKE_UNKNOWN_SYMBOL; } - registerProblem(reference.getElement(), description, hl_type, new AddImportAction(reference)); + PsiElement point = node.getLastChild(); // usually the identifier at the end of qual ref + if (point == null) point = node; + registerProblem(/*reference.getElement()*/ point, description, hl_type, action); } } } diff --git a/python/src/com/jetbrains/python/inspections/PythonPyInspectionToolProvider.java b/python/src/com/jetbrains/python/inspections/PythonPyInspectionToolProvider.java new file mode 100644 index 000000000000..66d6546243ec --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/PythonPyInspectionToolProvider.java @@ -0,0 +1,68 @@ +package com.jetbrains.python.inspections; + +import com.intellij.codeInspection.LocalInspectionTool; +import com.intellij.codeInspection.ex.InspectionTool; +import com.intellij.codeInspection.ex.InspectionToolRegistrar; +import com.intellij.codeInspection.ex.LocalInspectionToolWrapper; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.components.ApplicationComponent; +import com.intellij.openapi.util.Factory; +import com.jetbrains.python.JythonManager; +import org.jetbrains.annotations.NotNull; +import org.python.core.PyList; +import org.python.core.PyObject; + +/** + * @author yole + */ +public class PythonPyInspectionToolProvider implements ApplicationComponent { + public static PythonPyInspectionToolProvider getInstance() { + return ApplicationManager.getApplication().getComponent(PythonPyInspectionToolProvider.class); + } + + private InspectionToolRegistrar myRegistrar; + + public PythonPyInspectionToolProvider(final InspectionToolRegistrar registrar) { + myRegistrar = registrar; + } + + @NotNull + public String getComponentName() { + return "PythonPyInspectionToolProvider"; + } + + public void initComponent() { + JythonManager manager = JythonManager.getInstance(); + manager.execScriptFromResource("inspections/inspections.py"); + + final PyList pyList = (PyList) manager.eval("getAllInspections()"); + int len = pyList.__len__(); + for(int i=0; i { + private String myInspectionToolName; + + public PyInspectionToolFactory(final String inspectionToolName) { + myInspectionToolName = inspectionToolName; + } + + public InspectionTool create() { + final LocalInspectionTool tool = createLocalInspectionTool(myInspectionToolName); + return new LocalInspectionToolWrapper(tool); + } + } +} diff --git a/python/src/com/jetbrains/python/parsing/ExpressionParsing.java b/python/src/com/jetbrains/python/parsing/ExpressionParsing.java index 7275e08f92b3..0c60f8be21f6 100644 --- a/python/src/com/jetbrains/python/parsing/ExpressionParsing.java +++ b/python/src/com/jetbrains/python/parsing/ExpressionParsing.java @@ -19,6 +19,7 @@ package com.jetbrains.python.parsing; import com.intellij.lang.PsiBuilder; import com.intellij.openapi.diagnostic.Logger; import com.intellij.psi.tree.IElementType; +import static com.jetbrains.python.PyBundle.message; import com.jetbrains.python.PyElementTypes; import com.jetbrains.python.PyTokenTypes; @@ -96,7 +97,7 @@ public class ExpressionParsing extends Parsing { return; } if (!parseSingleExpression(isTargetExpression)) { - builder.error("expression expected"); + builder.error(message("PARSE.expected.expression")); } if (builder.getTokenType() == PyTokenTypes.FOR_KEYWORD) { parseListCompExpression(expr, PyTokenTypes.RBRACKET, PyElementTypes.LIST_COMP_EXPRESSION); @@ -107,11 +108,11 @@ public class ExpressionParsing extends Parsing { builder.advanceLexer(); } else if (!parseSingleExpression(isTargetExpression)) { - builder.error("expression or , or ] expected"); + builder.error(message("PARSE.expected.expr.or.comma.or.bracket")); break; } } - checkMatches(PyTokenTypes.RBRACKET, "] expected"); + checkMatches(PyTokenTypes.RBRACKET, message("PARSE.expected.rbracket")); expr.done(PyElementTypes.LIST_LITERAL_EXPRESSION); } } @@ -123,9 +124,9 @@ public class ExpressionParsing extends Parsing { while (true) { myBuilder.advanceLexer(); parseExpression(true, true); - checkMatches(PyTokenTypes.IN_KEYWORD, "'in' expected"); + checkMatches(PyTokenTypes.IN_KEYWORD, message("PARSE.expected.in")); if (!parseTupleExpression(false, false, true)) { - myBuilder.error("expression expected"); + myBuilder.error(message("PARSE.expected.expression")); } while (myBuilder.getTokenType() == PyTokenTypes.IF_KEYWORD) { myBuilder.advanceLexer(); @@ -140,7 +141,7 @@ public class ExpressionParsing extends Parsing { expr = expr.precede(); continue; } - myBuilder.error("closing bracket or 'for' expected"); + myBuilder.error(message("PARSE.expected.for.or.bracket")); break; } expr.done(exprType); @@ -155,7 +156,7 @@ public class ExpressionParsing extends Parsing { break; } if (builder.getTokenType() != PyTokenTypes.RBRACE) { - checkMatches(PyTokenTypes.COMMA, "comma expected"); + checkMatches(PyTokenTypes.COMMA, message("PARSE.expected.comma")); } } builder.advanceLexer(); @@ -168,7 +169,7 @@ public class ExpressionParsing extends Parsing { marker.drop(); return false; } - checkMatches(PyTokenTypes.COLON, ": expected"); + checkMatches(PyTokenTypes.COLON, message("PARSE.expected.colon")); if (!parseSingleExpression(false)) { marker.drop(); return false; @@ -191,7 +192,7 @@ public class ExpressionParsing extends Parsing { parseListCompExpression(expr, PyTokenTypes.RPAR, PyElementTypes.GENERATOR_EXPRESSION); } else { - checkMatches(PyTokenTypes.RPAR, ") expected"); + checkMatches(PyTokenTypes.RPAR, message("PARSE.expected.rpar")); expr.done(PyElementTypes.PARENTHESIZED_EXPRESSION); } } @@ -202,7 +203,7 @@ public class ExpressionParsing extends Parsing { final PsiBuilder.Marker expr = builder.mark(); builder.advanceLexer(); parseExpression(); - checkMatches(PyTokenTypes.TICK, "` expected"); + checkMatches(PyTokenTypes.TICK, message("PARSE.expected.tick")); expr.done(PyElementTypes.REPR_EXPRESSION); } @@ -227,7 +228,7 @@ public class ExpressionParsing extends Parsing { } else recast_first_identifier = false; builder.advanceLexer(); - checkMatches(PyTokenTypes.IDENTIFIER, "name expected"); + checkMatches(PyTokenTypes.IDENTIFIER, message("PARSE.expected.name")); if (isTargetExpression && builder.getTokenType() != PyTokenTypes.DOT) { expr.done(PyElementTypes.TARGET_EXPRESSION); } @@ -254,7 +255,7 @@ public class ExpressionParsing extends Parsing { parseSliceEnd(builder, expr); } else { - checkMatches(PyTokenTypes.RBRACKET, "] expected"); + checkMatches(PyTokenTypes.RBRACKET, message("PARSE.expected.rbracket")); expr.done(PyElementTypes.SUBSCRIPTION_EXPRESSION); } } @@ -287,13 +288,13 @@ public class ExpressionParsing extends Parsing { parseExpression(); } if (builder.getTokenType() != PyTokenTypes.RBRACKET && builder.getTokenType() != PyTokenTypes.COLON) { - builder.error(": or ] expected"); + builder.error(message("PARSE.expected.colon.or.rbracket")); } if (builder.getTokenType() == PyTokenTypes.COLON) { builder.advanceLexer(); parseExpressionOptional(); } - checkMatches(PyTokenTypes.RBRACKET, "] expected"); + checkMatches(PyTokenTypes.RBRACKET, message("PARSE.expected.rbracket")); } expr.done(PyElementTypes.SLICE_EXPRESSION); } @@ -320,7 +321,7 @@ public class ExpressionParsing extends Parsing { } } else { - builder.error(", or ) expected"); + builder.error(message("PARSE.expected.comma.or.rpar")); break; } } @@ -328,7 +329,7 @@ public class ExpressionParsing extends Parsing { final PsiBuilder.Marker starArgMarker = builder.mark(); builder.advanceLexer(); if (!parseSingleExpression(false)) { - builder.error("expression expected"); + builder.error(message("PARSE.expected.expression")); } starArgMarker.done(PyElementTypes.STAR_ARGUMENT_EXPRESSION); } @@ -339,7 +340,7 @@ public class ExpressionParsing extends Parsing { if (builder.getTokenType() == PyTokenTypes.EQ) { builder.advanceLexer(); if (!parseSingleExpression(false)) { - builder.error("expression expected"); + builder.error(message("PARSE.expected.expression")); } keywordArgMarker.done(PyElementTypes.KEYWORD_ARGUMENT_EXPRESSION); continue; @@ -347,14 +348,14 @@ public class ExpressionParsing extends Parsing { keywordArgMarker.rollbackTo(); } if (!parseSingleExpression(false)) { - builder.error("expression expected"); + builder.error(message("PARSE.expected.expression")); } } } if (needBracket) { genexpr.drop(); - checkMatches(PyTokenTypes.RPAR, ") expected"); + checkMatches(PyTokenTypes.RPAR, message("PARSE.expected.rpar")); } arglist.done(PyElementTypes.ARGUMENT_LIST); } @@ -369,13 +370,13 @@ public class ExpressionParsing extends Parsing { public void parseExpression() { if (!parseExpressionOptional()) { - myBuilder.error("expression expected"); + myBuilder.error(message("PARSE.expected.expression")); } } public void parseExpression(boolean stopOnIn, boolean isTargetExpression) { if (!parseTupleExpression(stopOnIn, isTargetExpression, false)) { - myBuilder.error("expression expected"); + myBuilder.error(message("PARSE.expected.expression")); } } @@ -434,16 +435,16 @@ public class ExpressionParsing extends Parsing { if (myBuilder.getTokenType() == PyTokenTypes.IF_KEYWORD) { myBuilder.advanceLexer(); if (!parseORTestExpression(myBuilder, stopOnIn, isTargetExpression)) { - myBuilder.error("expression expected"); + myBuilder.error(message("PARSE.expected.expression")); } else { if (myBuilder.getTokenType() != PyTokenTypes.ELSE_KEYWORD) { - myBuilder.error("'else' expected"); + myBuilder.error(message("PARSE.expected.else")); } else { myBuilder.advanceLexer(); if (!parseTestExpression(stopOnIn, isTargetExpression)) { - myBuilder.error("expression expected"); + myBuilder.error(message("PARSE.expected.expression")); } } } @@ -468,7 +469,7 @@ public class ExpressionParsing extends Parsing { getFunctionParser().parseParameterListContents(PyTokenTypes.COLON, false); boolean parseExpressionResult = oldTest ? parseOldTestExpression() : parseSingleExpression(false); if (!parseExpressionResult) { - myBuilder.error("expression expected"); + myBuilder.error(message("PARSE.expected.expression")); } expr.done(PyElementTypes.LAMBDA_EXPRESSION); return true; @@ -483,7 +484,7 @@ public class ExpressionParsing extends Parsing { while (builder.getTokenType() == PyTokenTypes.OR_KEYWORD) { builder.advanceLexer(); if (!parseANDTestExpression(builder, stopOnIn, isTargetExpression)) { - builder.error("expression expected"); + builder.error(message("PARSE.expected.expression")); } expr.done(PyElementTypes.BINARY_EXPRESSION); expr = expr.precede(); @@ -502,7 +503,7 @@ public class ExpressionParsing extends Parsing { while (builder.getTokenType() == PyTokenTypes.AND_KEYWORD) { builder.advanceLexer(); if (!parseNOTTestExpression(builder, stopOnIn, isTargetExpression)) { - builder.error("expression expected"); + builder.error(message("PARSE.expected.expression")); } expr.done(PyElementTypes.BINARY_EXPRESSION); expr = expr.precede(); @@ -517,7 +518,7 @@ public class ExpressionParsing extends Parsing { final PsiBuilder.Marker expr = builder.mark(); builder.advanceLexer(); if (!parseNOTTestExpression(builder, stopOnIn, isTargetExpression)) { - builder.error("Expression expected"); + builder.error(message("PARSE.expected.expression")); } expr.done(PyElementTypes.PREFIX_EXPRESSION); return true; @@ -559,7 +560,7 @@ public class ExpressionParsing extends Parsing { } if (!parseBitwiseORExpression(builder, isTargetExpression)) { - builder.error("expression expected"); + builder.error(message("PARSE.expected.expression")); } expr.done(PyElementTypes.BINARY_EXPRESSION); expr = expr.precede(); @@ -578,7 +579,7 @@ public class ExpressionParsing extends Parsing { while (builder.getTokenType() == PyTokenTypes.OR) { builder.advanceLexer(); if (!parseBitwiseXORExpression(builder, isTargetExpression)) { - builder.error("expression expected"); + builder.error(message("PARSE.expected.expression")); } expr.done(PyElementTypes.BINARY_EXPRESSION); expr = expr.precede(); @@ -597,7 +598,7 @@ public class ExpressionParsing extends Parsing { while (builder.getTokenType() == PyTokenTypes.XOR) { builder.advanceLexer(); if (!parseBitwiseANDExpression(builder, isTargetExpression)) { - builder.error("expression expected"); + builder.error(message("PARSE.expected.expression")); } expr.done(PyElementTypes.BINARY_EXPRESSION); expr = expr.precede(); @@ -616,7 +617,7 @@ public class ExpressionParsing extends Parsing { while (builder.getTokenType() == PyTokenTypes.AND) { builder.advanceLexer(); if (!parseShiftExpression(builder, isTargetExpression)) { - builder.error("expression expected"); + builder.error(message("PARSE.expected.expression")); } expr.done(PyElementTypes.BINARY_EXPRESSION); expr = expr.precede(); @@ -635,7 +636,7 @@ public class ExpressionParsing extends Parsing { while (PyTokenTypes.SHIFT_OPERATIONS.contains(builder.getTokenType())) { builder.advanceLexer(); if (!parseAdditiveExpression(builder, isTargetExpression)) { - builder.error("expression expected"); + builder.error(message("PARSE.expected.expression")); } expr.done(PyElementTypes.BINARY_EXPRESSION); expr = expr.precede(); @@ -654,7 +655,7 @@ public class ExpressionParsing extends Parsing { while (PyTokenTypes.ADDITIVE_OPERATIONS.contains(builder.getTokenType())) { builder.advanceLexer(); if (!parseMultiplicativeExpression(builder, isTargetExpression)) { - builder.error("expression expected"); + builder.error(message("PARSE.expected.expression")); } expr.done(PyElementTypes.BINARY_EXPRESSION); expr = expr.precede(); @@ -674,7 +675,7 @@ public class ExpressionParsing extends Parsing { while (PyTokenTypes.MULTIPLICATIVE_OPERATIONS.contains(builder.getTokenType())) { builder.advanceLexer(); if (!parseUnaryExpression(builder, isTargetExpression)) { - builder.error("expression expected"); + builder.error(message("PARSE.expected.expression")); } expr.done(PyElementTypes.BINARY_EXPRESSION); expr = expr.precede(); @@ -690,7 +691,7 @@ public class ExpressionParsing extends Parsing { final PsiBuilder.Marker expr = builder.mark(); builder.advanceLexer(); if (!parseUnaryExpression(builder, isTargetExpression)) { - builder.error("Expression expected"); + builder.error(message("PARSE.expected.expression")); } expr.done(PyElementTypes.PREFIX_EXPRESSION); return true; @@ -710,7 +711,7 @@ public class ExpressionParsing extends Parsing { if (builder.getTokenType() == PyTokenTypes.EXP) { builder.advanceLexer(); if (!parseUnaryExpression(builder, isTargetExpression)) { - builder.error("expression expected"); + builder.error(message("PARSE.expected.expression")); } expr.done(PyElementTypes.BINARY_EXPRESSION); } diff --git a/python/src/com/jetbrains/python/psi/PyElementGenerator.java b/python/src/com/jetbrains/python/psi/PyElementGenerator.java index eaafef72951c..6c84e1229011 100644 --- a/python/src/com/jetbrains/python/psi/PyElementGenerator.java +++ b/python/src/com/jetbrains/python/psi/PyElementGenerator.java @@ -62,4 +62,6 @@ public interface PyElementGenerator { void setStringValue(PyStringLiteralExpression string, String value); PyImportStatement createImportStatementFromText(Project project, String text); + + T createFromText(final Project project, Class aClass, final String text, final int[] path); } diff --git a/python/src/com/jetbrains/python/psi/PyExpression.java b/python/src/com/jetbrains/python/psi/PyExpression.java index d3751f97989f..0bbaca0a7c1e 100644 --- a/python/src/com/jetbrains/python/psi/PyExpression.java +++ b/python/src/com/jetbrains/python/psi/PyExpression.java @@ -20,6 +20,7 @@ import com.jetbrains.python.psi.types.PyType; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** @@ -29,7 +30,7 @@ import java.util.List; */ public interface PyExpression extends PyElement { PyExpression[] EMPTY_ARRAY = new PyExpression[0]; - List EMPTY_LIST = new ArrayList(0); + List EMPTY_LIST = Collections.unmodifiableList(new ArrayList(0)); @Nullable PyType getType(); diff --git a/python/src/com/jetbrains/python/psi/PyParameterList.java b/python/src/com/jetbrains/python/psi/PyParameterList.java index 134908a4d902..5d432ca566b3 100644 --- a/python/src/com/jetbrains/python/psi/PyParameterList.java +++ b/python/src/com/jetbrains/python/psi/PyParameterList.java @@ -28,4 +28,10 @@ import com.jetbrains.python.psi.stubs.PyParameterListStub; */ public interface PyParameterList extends PyElement, StubBasedPsiElement, NameDefiner { PyParameter[] getParameters(); + + /** + * Adds a paramter to list, after all other parameters. + * @param param + */ + void addParameter(PyParameter param); } diff --git a/python/src/com/jetbrains/python/psi/impl/EvaluatedTextRange.java b/python/src/com/jetbrains/python/psi/impl/EvaluatedTextRange.java index 143ea691b84a..202a435555d8 100644 --- a/python/src/com/jetbrains/python/psi/impl/EvaluatedTextRange.java +++ b/python/src/com/jetbrains/python/psi/impl/EvaluatedTextRange.java @@ -17,6 +17,7 @@ package com.jetbrains.python.psi.impl; import com.intellij.openapi.util.TextRange; +import org.jetbrains.annotations.NonNls; public class EvaluatedTextRange { private TextRange range; @@ -39,7 +40,7 @@ public class EvaluatedTextRange { return value; } - + @NonNls public String toString() { return "EvaluatedTextRange[" + range + "]: " + value; } diff --git a/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java b/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java index 57ffe3ee241b..196acb2df4e1 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java @@ -239,4 +239,14 @@ public class PyElementGeneratorImpl implements PyElementGenerator { final PsiFile dummyFile = createDummyFile(project, text); return (PyImportStatement)dummyFile.getFirstChild(); } + + public T createFromText(final Project project, Class aClass, final String text, final int[] path) { + final PsiFile dummyFile = createDummyFile(project, text); + PsiElement ret = dummyFile; + for (int skip : path) { + ret = ret.getFirstChild(); + for (int i = 0; i < skip; i += 1) ret = ret.getNextSibling(); + } + return (T)ret; + } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyParameterListImpl.java b/python/src/com/jetbrains/python/psi/impl/PyParameterListImpl.java index 439c5e6b5871..8fd3e0f8d925 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyParameterListImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyParameterListImpl.java @@ -17,6 +17,7 @@ package com.jetbrains.python.psi.impl; import com.intellij.lang.ASTNode; +import com.intellij.psi.PsiElement; import com.jetbrains.python.PyElementTypes; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.stubs.PyParameterListStub; @@ -47,6 +48,30 @@ public class PyParameterListImpl extends PyBaseElementImpl return getStubOrPsiChildren(PyElementTypes.FORMAL_PARAMETER, new PyParameter[0]); } + public void addParameter(final PyParameter param) { + PsiElement paren = getLastChild(); + if (paren != null && ")".equals(paren.getText())) { + PyUtil.ensureWritable(this); + ASTNode beforeWhat = paren.getNode(); // the closing bracket will be this + PyParameter[] params = getParameters(); + addItemNode(param, beforeWhat, true, params.length == 0); + } + } + + // TODO: open for general usage by all list-like structurtes + private void addItemNode(PyParameter item, ASTNode beforeThis, boolean isFirst, boolean isLast) { + PyUtil.ensureWritable(this); + ASTNode node = getNode(); + ASTNode itemNode = item.getNode(); + if (! isFirst) { + node.addChild(getLanguage().getElementGenerator().createComma(getProject()), beforeThis); + } + node.addChild(itemNode, beforeThis); + if (! isLast) { + node.addChild(getLanguage().getElementGenerator().createComma(getProject()), beforeThis); + } + } + @NotNull public Iterable iterateNames() { return new ArrayIterable(getParameters()); diff --git a/python/src/com/jetbrains/python/validation/AddImportAction.java b/python/src/com/jetbrains/python/validation/AddImportAction.java index 5ba44e2ba7d8..1a5e79d72e9a 100644 --- a/python/src/com/jetbrains/python/validation/AddImportAction.java +++ b/python/src/com/jetbrains/python/validation/AddImportAction.java @@ -8,6 +8,8 @@ import com.intellij.codeInsight.daemon.impl.ShowAutoImportPass; import com.intellij.codeInsight.hint.HintManager; import com.intellij.codeInsight.hint.QuestionAction; import com.intellij.codeInspection.HintAction; +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; @@ -17,13 +19,15 @@ import com.intellij.psi.search.FilenameIndex; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.IncorrectOperationException; +import com.jetbrains.python.PyBundle; +import static com.jetbrains.python.PyNames.DOT_PY; import com.jetbrains.python.PythonLanguage; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.ResolveImportUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -public class AddImportAction implements HintAction, QuestionAction { +public class AddImportAction implements HintAction, QuestionAction, LocalQuickFix { private final PsiReference myReference; private Project myProject; private static final Logger LOG = Logger.getInstance("#" + AddImportAction.class.getName()); @@ -35,7 +39,7 @@ public class AddImportAction implements HintAction, QuestionAction { @NotNull public String getText() { - return "Add import"; + return PyBundle.message("ACT.NAME.add.import"); } @NotNull @@ -45,7 +49,11 @@ public class AddImportAction implements HintAction, QuestionAction { @NotNull public String getFamilyName() { - return "import"; + return PyBundle.message("ACT.FAMILY.import"); + } + + public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) { + execute(descriptor.getPsiElement().getContainingFile()); } @Nullable @@ -54,7 +62,7 @@ public class AddImportAction implements HintAction, QuestionAction { } protected PsiFile[] getRefFiles(final String referenceName) { - PsiFile[] files = FilenameIndex.getFilesByName(myProject, referenceName + ".py", GlobalSearchScope.allScope(myProject)); + PsiFile[] files = FilenameIndex.getFilesByName(myProject, referenceName + DOT_PY, GlobalSearchScope.allScope(myProject)); if (files == null) files = PsiFile.EMPTY_ARRAY; return files; } diff --git a/python/src/com/jetbrains/python/validation/BreakContinueAnnotator.java b/python/src/com/jetbrains/python/validation/BreakContinueAnnotator.java index ab82eb067fe9..222188ce7da7 100644 --- a/python/src/com/jetbrains/python/validation/BreakContinueAnnotator.java +++ b/python/src/com/jetbrains/python/validation/BreakContinueAnnotator.java @@ -17,10 +17,11 @@ package com.jetbrains.python.validation; import com.intellij.psi.PsiElement; +import static com.jetbrains.python.PyBundle.message; +import com.jetbrains.python.PyElementTypes; import com.jetbrains.python.psi.PyBreakStatement; import com.jetbrains.python.psi.PyContinueStatement; import com.jetbrains.python.psi.PyTryExceptStatement; -import com.jetbrains.python.PyElementTypes; /** * Created by IntelliJ IDEA. @@ -32,13 +33,13 @@ import com.jetbrains.python.PyElementTypes; public class BreakContinueAnnotator extends PyAnnotator { @Override public void visitPyBreakStatement(final PyBreakStatement node) { if (node.getContainingElement(PyElementTypes.LOOPS) == null) { - getHolder().createErrorAnnotation(node, "'break' outside of loop"); + getHolder().createErrorAnnotation(node, message("ANN.break.outside.loop")); } } @Override public void visitPyContinueStatement(final PyContinueStatement node) { if (node.getContainingElement(PyElementTypes.LOOPS) == null) { - getHolder().createErrorAnnotation(node, "'continue' outside of loop"); + getHolder().createErrorAnnotation(node, message("ANN.continue.outside.loop")); return; } PyTryExceptStatement tryStatement = node.getContainingElement(PyTryExceptStatement.class); @@ -46,7 +47,7 @@ public class BreakContinueAnnotator extends PyAnnotator { PsiElement parent = node.getParent(); while (parent != null) { if (parent == tryStatement.getFinallyStatementList()) { - getHolder().createErrorAnnotation(node, "'continue' not supported inside 'finally' clause"); + getHolder().createErrorAnnotation(node, message("ANN.cant.continue.in.finally")); break; } parent = parent.getParent(); diff --git a/python/src/com/jetbrains/python/validation/PythonReferenceImporter.java b/python/src/com/jetbrains/python/validation/PythonReferenceImporter.java index 968ba6478de9..48a9a0169209 100644 --- a/python/src/com/jetbrains/python/validation/PythonReferenceImporter.java +++ b/python/src/com/jetbrains/python/validation/PythonReferenceImporter.java @@ -17,6 +17,10 @@ import org.jetbrains.annotations.NotNull; import java.util.List; public class PythonReferenceImporter implements ReferenceImporter { + public PythonReferenceImporter() { + // nothing + } + public boolean autoImportReferenceAtCursor(@NotNull final Editor editor, @NotNull final PsiFile file) { int caretOffset = editor.getCaretModel().getOffset(); Document document = editor.getDocument();