diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml
index ff3dc3ae8d56..bed99bd64d91 100644
--- a/python/src/META-INF/python-plugin-common.xml
+++ b/python/src/META-INF/python-plugin-common.xml
@@ -43,6 +43,7 @@
+
diff --git a/python/src/com/jetbrains/python/codeInsight/PyMethodNameTypedHandler.java b/python/src/com/jetbrains/python/codeInsight/PyMethodNameTypedHandler.java
new file mode 100644
index 000000000000..a16636998e1d
--- /dev/null
+++ b/python/src/com/jetbrains/python/codeInsight/PyMethodNameTypedHandler.java
@@ -0,0 +1,69 @@
+package com.jetbrains.python.codeInsight;
+
+import com.intellij.codeInsight.editorActions.TypedHandlerDelegate;
+import com.intellij.lang.ASTNode;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.EditorModificationUtil;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.jetbrains.python.PyNames;
+import com.jetbrains.python.PyTokenTypes;
+import com.jetbrains.python.PythonFileType;
+import com.jetbrains.python.psi.PyFunction;
+import com.jetbrains.python.psi.PyStringLiteralExpression;
+import com.jetbrains.python.psi.PyUtil;
+
+/**
+ * Adds appropriate first parameter to a freshly-typed method declaration.
+ *
+ * User: dcheryasov
+ * Date: 11/29/10 12:44 AM
+ */
+public class PyMethodNameTypedHandler extends TypedHandlerDelegate {
+ @Override
+ public Result beforeCharTyped(char character, Project project, Editor editor, PsiFile file, FileType fileType) {
+ if (!(fileType instanceof PythonFileType)) return Result.CONTINUE; // else we'd mess up with other file types!
+ if (character == '(') {
+ final Document document = editor.getDocument();
+ final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
+ final int offset = editor.getCaretModel().getOffset();
+
+ PsiElement token = file.findElementAt(offset - 1);
+ if (token == null) return Result.CONTINUE; // sanity check: beyond EOL
+
+ final ASTNode token_node = token.getNode();
+ if (token_node != null && token_node.getElementType() == PyTokenTypes.IDENTIFIER) {
+ PsiElement maybe_def = PyUtil.getFirstNonCommentBefore(token.getPrevSibling());
+ if (maybe_def != null) {
+ ASTNode def_node = maybe_def.getNode();
+ if (def_node != null && def_node.getElementType() == PyTokenTypes.DEF_KEYWORD) {
+ PsiElement maybe_func = token.getParent();
+ if (maybe_func instanceof PyFunction) {
+ PyFunction func = (PyFunction)maybe_func;
+ PyUtil.MethodFlags flags = PyUtil.MethodFlags.of(func);
+ if (flags != null) {
+ // we're in a method
+ // TODO: all string constants go to Settings
+ String pname = flags.isClassMethod() || flags.isMetaclassMethod() ? "cls" : "self";
+ final boolean is_new = PyNames.NEW.equals(func.getName());
+ if (flags.isMetaclassMethod() && is_new) pname = "typ";
+ else if (flags.isClassMethod() || is_new) pname = "cls";
+ else if (flags.isStaticMethod()) pname="";
+ documentManager.commitDocument(document);
+ // TODO: only print the ")" if Settings require it
+ EditorModificationUtil.typeInStringAtCaretHonorBlockSelection(editor, "("+pname+"):", true);
+ editor.getCaretModel().moveToOffset(offset + 1 + pname.length()); // right after param name
+ return Result.STOP;
+ }
+ }
+ }
+ }
+ }
+ }
+ return Result.CONTINUE; // the default
+ }
+}
diff --git a/python/src/com/jetbrains/python/inspections/PyMethodParametersInspection.java b/python/src/com/jetbrains/python/inspections/PyMethodParametersInspection.java
index 74ff3aecb5a9..6b154a475039 100644
--- a/python/src/com/jetbrains/python/inspections/PyMethodParametersInspection.java
+++ b/python/src/com/jetbrains/python/inspections/PyMethodParametersInspection.java
@@ -11,16 +11,10 @@ import com.jetbrains.python.PyNames;
import com.jetbrains.python.actions.AddSelfQuickFix;
import com.jetbrains.python.actions.RenameParameterQuickFix;
import com.jetbrains.python.psi.*;
-import com.jetbrains.python.psi.impl.PyBuiltinCache;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
-import java.util.Set;
-
-import static com.jetbrains.python.psi.PyFunction.Flag.CLASSMETHOD;
-import static com.jetbrains.python.psi.PyFunction.Flag.STATICMETHOD;
-
/**
* Looks for the 'self' or its equivalents.
* @author dcheryasov
@@ -50,34 +44,16 @@ public class PyMethodParametersInspection extends PyInspection {
}
- private static boolean among(@NotNull String what, String... variants) {
- for (String s : variants) {
- if (what.equals(s)) return true;
- }
- return false;
- }
-
@Override
public void visitPyFunction(final PyFunction node) {
- PsiElement cap = PyUtil.getConcealingParent(node);
- if (cap instanceof PyClass) {
+ PyUtil.MethodFlags flags = PyUtil.MethodFlags.of(node);
+ if (flags != null) {
PyParameterList plist = node.getParameterList();
PyParameter[] params = plist.getParameters();
- Set flags = PyUtil.detectDecorationsAndWrappersOf(node);
- boolean isMetaclassMethod = false;
- PyClass type_cls = PyBuiltinCache.getInstance(node).getClass("type");
- for (PyClass ancestor_cls : ((PyClass)cap).iterateAncestors()) {
- if (ancestor_cls == type_cls) {
- isMetaclassMethod = true;
- break;
- }
- }
final String method_name = node.getName();
- boolean isSpecialMetaclassMethod = isMetaclassMethod && among(method_name, PyNames.INIT, "__call__");
- final boolean is_staticmethod = flags.contains(STATICMETHOD);
if (params.length == 0) {
// check for "staticmetod"
- if (is_staticmethod) return; // no params may be fine
+ if (flags.isStaticMethod()) return; // no params may be fine
// check actual param list
ASTNode name_node = node.getNameNode();
if (name_node != null) {
@@ -87,7 +63,7 @@ public class PyMethodParametersInspection extends PyInspection {
open_paren != null && close_paren != null &&
"(".equals(open_paren.getText()) && ")".equals(close_paren.getText())
) {
- String paramName = flags.contains(CLASSMETHOD) || isMetaclassMethod ? "cls" : "self";
+ String paramName = flags.isClassMethod() || flags.isMetaclassMethod() ? "cls" : "self";
registerProblem(
plist, PyBundle.message("INSP.must.have.first.parameter", paramName),
ProblemHighlightType.GENERIC_ERROR, null, new AddSelfQuickFix(paramName)
@@ -101,20 +77,18 @@ public class PyMethodParametersInspection extends PyInspection {
String pname = first_param.getText();
// 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(
- PyUtil.sure(params[0].getNode()).getPsi(),
- PyBundle.message("INSP.probably.mistyped.self"),
- new RenameParameterQuickFix(PyNames.CANONICAL_SELF)
- );
- return;
- }
+ if (PyUtil.among(pname, mangled)) {
+ registerProblem(
+ PyUtil.sure(params[0].getNode()).getPsi(),
+ PyBundle.message("INSP.probably.mistyped.self"),
+ new RenameParameterQuickFix(PyNames.CANONICAL_SELF)
+ );
+ return;
}
String CLS = "cls"; // TODO: move to style settings
- if (isMetaclassMethod && PyNames.NEW.equals(method_name)) {
+ if (flags.isMetaclassMethod() && PyNames.NEW.equals(method_name)) {
final String[] POSSIBLE_PARAM_NAMES = {"typ", "meta"}; // TODO: move to style settings
- if (!among(pname, POSSIBLE_PARAM_NAMES)) {
+ if (!PyUtil.among(pname, POSSIBLE_PARAM_NAMES)) {
registerProblem(
PyUtil.sure(params[0].getNode()).getPsi(),
PyBundle.message("INSP.usually.named.$0", POSSIBLE_PARAM_NAMES[0]),
@@ -122,7 +96,7 @@ public class PyMethodParametersInspection extends PyInspection {
);
}
}
- else if (flags.contains(CLASSMETHOD) || isSpecialMetaclassMethod || PyNames.NEW.equals(method_name)) {
+ else if (flags.isClassMethod() || flags.isSpecialMetaclassMethod() || PyNames.NEW.equals(method_name)) {
if (!CLS.equals(pname)) {
registerProblem(
PyUtil.sure(params[0].getNode()).getPsi(),
@@ -131,8 +105,8 @@ public class PyMethodParametersInspection extends PyInspection {
);
}
}
- else if (!is_staticmethod && !first_param.isPositionalContainer() && !PyNames.CANONICAL_SELF.equals(pname)) {
- if (isMetaclassMethod && CLS.equals(pname)) {
+ else if (!flags.isStaticMethod() && !first_param.isPositionalContainer() && !PyNames.CANONICAL_SELF.equals(pname)) {
+ if (flags.isMetaclassMethod() && CLS.equals(pname)) {
return; // accept either 'self' or 'cls' for all methods in metaclass
}
registerProblem(
@@ -143,7 +117,7 @@ public class PyMethodParametersInspection extends PyInspection {
}
}
else { // the unusual case of a method with first tuple param
- if (!is_staticmethod) {
+ if (!flags.isStaticMethod()) {
registerProblem(plist, PyBundle.message("INSP.first.param.must.not.be.tuple"));
}
}
diff --git a/python/src/com/jetbrains/python/psi/PyUtil.java b/python/src/com/jetbrains/python/psi/PyUtil.java
index 9ea31462b213..e83d49997d9f 100644
--- a/python/src/com/jetbrains/python/psi/PyUtil.java
+++ b/python/src/com/jetbrains/python/psi/PyUtil.java
@@ -678,6 +678,18 @@ public class PyUtil {
return null;
}
+ /**
+ * @param what thing to search for
+ * @param variants things to search among
+ * @return true iff what.equals() one of the variants.
+ */
+ public static boolean among(@NotNull T what, T... variants) {
+ for (T s : variants) {
+ if (what.equals(s)) return true;
+ }
+ return false;
+ }
+
public static class UnderscoreFilter implements Condition {
private int myAllowed; // how many starting underscores is allowed: 0 is none, 1 is only one, 2 is two and more.
@@ -716,5 +728,66 @@ public class PyUtil {
}
return false;
}
+
+ public static class MethodFlags {
+
+ private boolean myIsStaticMethod;
+ private boolean myIsMetaclassMethod;
+ private boolean myIsSpecialMetaclassMethod;
+ private boolean myIsClassMethod;
+
+ /**
+ * @return true iff the method belongs to a metaclass (an ancestor of 'type').
+ */
+ public boolean isMetaclassMethod() {
+ return myIsMetaclassMethod;
+ }
+
+ /**
+ * @return iff isMetaclassMethod and the method is either __init__ or __call__.
+ */
+ public boolean isSpecialMetaclassMethod() {
+ return myIsSpecialMetaclassMethod;
+ }
+
+ public boolean isStaticMethod() {
+ return myIsStaticMethod;
+ }
+
+ public boolean isClassMethod() {
+ return myIsClassMethod;
+ }
+
+ private MethodFlags(boolean isClassMethod, boolean isStaticMethod, boolean isMetaclassMethod, boolean isSpecialMetaclassMethod) {
+ myIsClassMethod = isClassMethod;
+ myIsStaticMethod = isStaticMethod;
+ myIsMetaclassMethod = isMetaclassMethod;
+ myIsSpecialMetaclassMethod = isSpecialMetaclassMethod;
+ }
+
+ /**
+ * @param node a function
+ * @return a new flags object, or null if the function is not a method
+ */
+ @Nullable
+ public static MethodFlags of(@NotNull PyFunction node) {
+ PyClass cls = node.getContainingClass();
+ if (cls != null) {
+ Set flags = detectDecorationsAndWrappersOf(node);
+ boolean isMetaclassMethod = false;
+ PyClass type_cls = PyBuiltinCache.getInstance(node).getClass("type");
+ for (PyClass ancestor_cls : cls.iterateAncestors()) {
+ if (ancestor_cls == type_cls) {
+ isMetaclassMethod = true;
+ break;
+ }
+ }
+ final String method_name = node.getName();
+ boolean isSpecialMetaclassMethod = isMetaclassMethod && method_name != null && among(method_name, PyNames.INIT, "__call__");
+ return new MethodFlags(flags.contains(CLASSMETHOD), flags.contains(STATICMETHOD), isMetaclassMethod, isSpecialMetaclassMethod);
+ }
+ return null;
+ }
+ }
}
diff --git a/python/testData/editing/firstParamClassmethod.after.py b/python/testData/editing/firstParamClassmethod.after.py
new file mode 100644
index 000000000000..d2f03b1fc3b9
--- /dev/null
+++ b/python/testData/editing/firstParamClassmethod.after.py
@@ -0,0 +1,3 @@
+class A(object):
+ @classmethod
+ def foo(cls):
diff --git a/python/testData/editing/firstParamClassmethod.py b/python/testData/editing/firstParamClassmethod.py
new file mode 100644
index 000000000000..a63856443848
--- /dev/null
+++ b/python/testData/editing/firstParamClassmethod.py
@@ -0,0 +1,3 @@
+class A(object):
+ @classmethod
+ def foo
diff --git a/python/testData/editing/firstParamMetaClass.after.py b/python/testData/editing/firstParamMetaClass.after.py
new file mode 100644
index 000000000000..6c702ea1d221
--- /dev/null
+++ b/python/testData/editing/firstParamMetaClass.after.py
@@ -0,0 +1,3 @@
+class A(type):
+ @classmethod
+ def f(cls):
diff --git a/python/testData/editing/firstParamMetaClass.py b/python/testData/editing/firstParamMetaClass.py
new file mode 100644
index 000000000000..e84217f2caf5
--- /dev/null
+++ b/python/testData/editing/firstParamMetaClass.py
@@ -0,0 +1,3 @@
+class A(type):
+ @classmethod
+ def f
diff --git a/python/testData/editing/firstParamMetaNew.after.py b/python/testData/editing/firstParamMetaNew.after.py
new file mode 100644
index 000000000000..e1b9d89fd3dc
--- /dev/null
+++ b/python/testData/editing/firstParamMetaNew.after.py
@@ -0,0 +1,2 @@
+class A(type):
+ def __new__(typ):
diff --git a/python/testData/editing/firstParamMetaNew.py b/python/testData/editing/firstParamMetaNew.py
new file mode 100644
index 000000000000..e9af18205f9c
--- /dev/null
+++ b/python/testData/editing/firstParamMetaNew.py
@@ -0,0 +1,2 @@
+class A(type):
+ def __new__
diff --git a/python/testData/editing/firstParamMetaSimple.after.py b/python/testData/editing/firstParamMetaSimple.after.py
new file mode 100644
index 000000000000..0c4d3dde42f6
--- /dev/null
+++ b/python/testData/editing/firstParamMetaSimple.after.py
@@ -0,0 +1,2 @@
+class A(type):
+ def f(cls):
diff --git a/python/testData/editing/firstParamMetaSimple.py b/python/testData/editing/firstParamMetaSimple.py
new file mode 100644
index 000000000000..8a94f6ede2c5
--- /dev/null
+++ b/python/testData/editing/firstParamMetaSimple.py
@@ -0,0 +1,2 @@
+class A(type):
+ def f
diff --git a/python/testData/editing/firstParamSimple.after.py b/python/testData/editing/firstParamSimple.after.py
new file mode 100644
index 000000000000..cc336854a5d5
--- /dev/null
+++ b/python/testData/editing/firstParamSimple.after.py
@@ -0,0 +1,2 @@
+class A(object):
+ def foo(self):
diff --git a/python/testData/editing/firstParamSimple.py b/python/testData/editing/firstParamSimple.py
new file mode 100644
index 000000000000..46d5efacf32d
--- /dev/null
+++ b/python/testData/editing/firstParamSimple.py
@@ -0,0 +1,2 @@
+class A(object):
+ def foo
diff --git a/python/testData/editing/firstParamSimpleInit.after.py b/python/testData/editing/firstParamSimpleInit.after.py
new file mode 100644
index 000000000000..f917824242d0
--- /dev/null
+++ b/python/testData/editing/firstParamSimpleInit.after.py
@@ -0,0 +1,2 @@
+class A(object):
+ def __init__(self):
diff --git a/python/testData/editing/firstParamSimpleInit.py b/python/testData/editing/firstParamSimpleInit.py
new file mode 100644
index 000000000000..e266c73c1708
--- /dev/null
+++ b/python/testData/editing/firstParamSimpleInit.py
@@ -0,0 +1,2 @@
+class A(object):
+ def __init__
diff --git a/python/testData/editing/firstParamSimpleNew.after.py b/python/testData/editing/firstParamSimpleNew.after.py
new file mode 100644
index 000000000000..bd75378dd3cf
--- /dev/null
+++ b/python/testData/editing/firstParamSimpleNew.after.py
@@ -0,0 +1,2 @@
+class A(object):
+ def __new__(cls):
diff --git a/python/testData/editing/firstParamSimpleNew.py b/python/testData/editing/firstParamSimpleNew.py
new file mode 100644
index 000000000000..fc2e9d2ac1bb
--- /dev/null
+++ b/python/testData/editing/firstParamSimpleNew.py
@@ -0,0 +1,2 @@
+class A(object):
+ def __new__
diff --git a/python/testData/editing/firstParamStaticmethod.after.py b/python/testData/editing/firstParamStaticmethod.after.py
new file mode 100644
index 000000000000..0d53971e8ce8
--- /dev/null
+++ b/python/testData/editing/firstParamStaticmethod.after.py
@@ -0,0 +1,3 @@
+class A(object):
+ @staticmethod
+ def foo():
diff --git a/python/testData/editing/firstParamStaticmethod.py b/python/testData/editing/firstParamStaticmethod.py
new file mode 100644
index 000000000000..48bb875821d7
--- /dev/null
+++ b/python/testData/editing/firstParamStaticmethod.py
@@ -0,0 +1,3 @@
+class A(object):
+ @staticmethod
+ def foo
diff --git a/python/testSrc/com/jetbrains/python/PyEditingTest.java b/python/testSrc/com/jetbrains/python/PyEditingTest.java
index 04ebebe1d97f..8bef93514603 100644
--- a/python/testSrc/com/jetbrains/python/PyEditingTest.java
+++ b/python/testSrc/com/jetbrains/python/PyEditingTest.java
@@ -176,4 +176,55 @@ public class PyEditingTest extends PyLightFixtureTestCase {
});
return myFixture.getDocument(file).getText();
}
+
+ private void doTypingTest(final char character) {
+ final String testName = "editing/" + getTestName(true);
+ myFixture.configureByFile(testName + ".py");
+ doTyping(character);
+ myFixture.checkResultByFile(testName + ".after.py");
+ }
+
+ private void doTyping(final char character) {
+ final int offset = myFixture.getEditor().getCaretModel().getOffset();
+ final PsiFile file = ApplicationManager.getApplication().runWriteAction(new Computable() {
+ @Override
+ public PsiFile compute() {
+ myFixture.getEditor().getCaretModel().moveToOffset(offset);
+ myFixture.type(character);
+ return myFixture.getFile();
+ }
+ });
+ }
+
+ public void testFirstParamClassmethod() {
+ doTypingTest('(');
+ }
+
+ public void testFirstParamMetaClass() {
+ doTypingTest('(');
+ }
+
+ public void testFirstParamMetaNew() {
+ doTypingTest('(');
+ }
+
+ public void testFirstParamMetaSimple() {
+ doTypingTest('(');
+ }
+
+ public void testFirstParamSimpleInit() {
+ doTypingTest('(');
+ }
+
+ public void testFirstParamSimpleNew() {
+ doTypingTest('(');
+ }
+
+ public void testFirstParamSimple() {
+ doTypingTest('(');
+ }
+
+ public void testFirstParamStaticmethod() {
+ doTypingTest('(');
+ }
}