From 445622e530cf1cca17bc3a6536a0150c50c49d00 Mon Sep 17 00:00:00 2001 From: Ekaterina Tuzova Date: Thu, 29 Aug 2013 15:47:04 +0400 Subject: [PATCH] fixed PY-9724 Notice about abstract methods and properties --- .../src/com/jetbrains/python/PyNames.java | 1 + python/src/META-INF/python-plugin-common.xml | 1 + .../com/jetbrains/python/PyBundle.properties | 7 ++ .../override/PyOverrideImplementUtil.java | 25 +++++-- .../PyAbstractClassInspection.java | 66 +++++++++++++++++++ .../quickfix/PyImplementMethodsQuickFix.java | 65 ++++++++++++++++++ 6 files changed, 160 insertions(+), 5 deletions(-) create mode 100644 python/src/com/jetbrains/python/inspections/PyAbstractClassInspection.java create mode 100644 python/src/com/jetbrains/python/inspections/quickfix/PyImplementMethodsQuickFix.java diff --git a/python/psi-api/src/com/jetbrains/python/PyNames.java b/python/psi-api/src/com/jetbrains/python/PyNames.java index e1b99d0135a8..340a57dbda77 100644 --- a/python/psi-api/src/com/jetbrains/python/PyNames.java +++ b/python/psi-api/src/com/jetbrains/python/PyNames.java @@ -120,6 +120,7 @@ public class PyNames { public static final String COLLECTIONS_NAMEDTUPLE = COLLECTIONS + "." + NAMEDTUPLE; public static final String ABSTRACTMETHOD = "abstractmethod"; + public static final String ABSTRACTPROPERTY = "abstractproperty"; public static final String TUPLE = "tuple"; public static final String SET = "set"; diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml index 19cef71a95a3..2aca6649cb00 100644 --- a/python/src/META-INF/python-plugin-common.xml +++ b/python/src/META-INF/python-plugin-common.xml @@ -337,6 +337,7 @@ + diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties index 84ce4c1800d0..454067904cdc 100644 --- a/python/src/com/jetbrains/python/PyBundle.properties +++ b/python/src/com/jetbrains/python/PyBundle.properties @@ -111,6 +111,9 @@ QFIX.NAME.make.static=Make method static #PyMakeFunctionFromMethodQuickFix QFIX.NAME.make.function=Make function from method +#PyImplementMethodsQuickFix +QFIX.NAME.implement.methods=Implement abstract methods + # Intentions: INTN INTN.Family.convert.import.unqualify=Convert 'import module' to 'from module import' INTN.Family.convert.import.qualify=Convert 'from module import' to 'import module' @@ -460,6 +463,10 @@ INSP.protected.member.$0.access=Access to a protected member {0} of a class INSP.NAME.argument.equal.default=Argument passed to function is equal to default parameter value INSP.argument.equals.to.default=Argument equals to default parameter value +# PyAbstractClassInspection +INSP.NAME.abstract.class=Class must implement all abstract methods +INSP.NAME.abstract.class.$0.must.implement=Class {0} must implement all abstract methods + # PyOldStyleClassesInspection INSP.NAME.oldstyle.class=Old-style class contains new-style class features diff --git a/python/src/com/jetbrains/python/codeInsight/override/PyOverrideImplementUtil.java b/python/src/com/jetbrains/python/codeInsight/override/PyOverrideImplementUtil.java index 79eb0470a7f4..3df9fefa3f7a 100644 --- a/python/src/com/jetbrains/python/codeInsight/override/PyOverrideImplementUtil.java +++ b/python/src/com/jetbrains/python/codeInsight/override/PyOverrideImplementUtil.java @@ -1,5 +1,6 @@ package com.jetbrains.python.codeInsight.override; +import com.google.common.collect.Sets; import com.intellij.codeInsight.CodeInsightUtilCore; import com.intellij.featureStatistics.FeatureUsageTracker; import com.intellij.featureStatistics.ProductivityFeatureNames; @@ -67,13 +68,22 @@ public class PyOverrideImplementUtil { chooseAndOverrideOrImplementMethods(project, editor, pyClass); } - private static void chooseAndOverrideOrImplementMethods(final Project project, + + public static void chooseAndOverrideOrImplementMethods(final Project project, + @NotNull final Editor editor, + @NotNull final PyClass pyClass) { + chooseAndOverrideOrImplementMethods(project, editor, pyClass, Sets.newHashSet()); + } + + public static void chooseAndOverrideOrImplementMethods(final Project project, @NotNull final Editor editor, - @NotNull final PyClass pyClass) { + @NotNull final PyClass pyClass, + @NotNull final Set toSelect) { LOG.assertTrue(pyClass.isValid()); ApplicationManager.getApplication().assertReadAccessAllowed(); final Collection superFunctions = getAllSuperFunctions(pyClass); + final List membersToSelect = new ArrayList(); List elements = new ArrayList(); for (PyFunction function : superFunctions) { final String name = function.getName(); @@ -81,7 +91,11 @@ public class PyOverrideImplementUtil { continue; } if (pyClass.findMethodByName(name, false) == null) { - elements.add(new PyMethodMember(function)); + final PyMethodMember member = new PyMethodMember(function); + elements.add(member); + if (toSelect.contains(function)) { + membersToSelect.add(member); + } } } if (elements.size() == 0) { @@ -103,6 +117,7 @@ public class PyOverrideImplementUtil { }; chooser.setTitle("Select Methods to Override"); chooser.setCopyJavadocVisible(false); + chooser.selectElements(membersToSelect.toArray(new PyMethodMember[membersToSelect.size()])); chooser.show(); if (chooser.getExitCode() != DialogWrapper.OK_EXIT_CODE) { return; @@ -192,7 +207,7 @@ public class PyOverrideImplementUtil { } } - if (PyNames.FAKE_OLD_BASE.equals(baseFunction.getContainingClass().getName())) { + if (PyNames.FAKE_OLD_BASE.equals(baseClass.getName())) { statementBody.append(PyNames.PASS); } else { @@ -238,7 +253,7 @@ public class PyOverrideImplementUtil { } @NotNull - private static Collection getAllSuperFunctions(@NotNull final PyClass pyClass) { + public static Collection getAllSuperFunctions(@NotNull final PyClass pyClass) { final Map superFunctions = new HashMap(); for (PyClass aClass : pyClass.getAncestorClasses()) { for (PyFunction function : aClass.getMethods()) { diff --git a/python/src/com/jetbrains/python/inspections/PyAbstractClassInspection.java b/python/src/com/jetbrains/python/inspections/PyAbstractClassInspection.java new file mode 100644 index 000000000000..22f241d9de25 --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/PyAbstractClassInspection.java @@ -0,0 +1,66 @@ +package com.jetbrains.python.inspections; + +import com.intellij.codeInspection.LocalInspectionToolSession; +import com.intellij.codeInspection.ProblemHighlightType; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.lang.ASTNode; +import com.intellij.psi.PsiElementVisitor; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.PyNames; +import com.jetbrains.python.codeInsight.override.PyOverrideImplementUtil; +import com.jetbrains.python.inspections.quickfix.PyImplementMethodsQuickFix; +import com.jetbrains.python.psi.PyClass; +import com.jetbrains.python.psi.PyDecoratorList; +import com.jetbrains.python.psi.PyFunction; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * User: ktisha + */ +public class PyAbstractClassInspection extends PyInspection { + @Nls + @NotNull + @Override + public String getDisplayName() { + return PyBundle.message("INSP.NAME.abstract.class"); + } + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, + boolean isOnTheFly, + @NotNull LocalInspectionToolSession session) { + return new Visitor(holder, session); + } + + private static class Visitor extends PyInspectionVisitor { + public Visitor(@Nullable ProblemsHolder holder, @NotNull LocalInspectionToolSession session) { + super(holder, session); + } + + @Override + public void visitPyClass(PyClass node) { + Set toBeImplemented = new HashSet(); + final Collection functions = PyOverrideImplementUtil.getAllSuperFunctions(node); + for (PyFunction method : functions) { + final PyDecoratorList list = method.getDecoratorList(); + if (list != null) { + if (list.findDecorator(PyNames.ABSTRACTMETHOD) != null || list.findDecorator(PyNames.ABSTRACTPROPERTY) != null) { + toBeImplemented.add(method); + } + } + } + final ASTNode nameNode = node.getNameNode(); + if (!toBeImplemented.isEmpty() && nameNode != null) { + registerProblem(nameNode.getPsi(), PyBundle.message("INSP.NAME.abstract.class.$0.must.implement", node), + ProblemHighlightType.INFO, null, new PyImplementMethodsQuickFix(node, toBeImplemented)); + } + } + } +} diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyImplementMethodsQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/PyImplementMethodsQuickFix.java new file mode 100644 index 000000000000..c9b2f59ce47e --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/quickfix/PyImplementMethodsQuickFix.java @@ -0,0 +1,65 @@ +package com.jetbrains.python.inspections.quickfix; + +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.codeInspection.ProblemDescriptor; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.codeInsight.override.PyOverrideImplementUtil; +import com.jetbrains.python.psi.PyClass; +import com.jetbrains.python.psi.PyFunction; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Set; + +/** + * User: ktisha + */ +public class PyImplementMethodsQuickFix implements LocalQuickFix { + + private final PyClass myClass; + private final Set myToImplement; + + public PyImplementMethodsQuickFix(PyClass aClass, Set toBeImplemented) { + myClass = aClass; + myToImplement = toBeImplemented; + } + + @NotNull + public String getName() { + return PyBundle.message("QFIX.NAME.implement.methods"); + } + + @NonNls + @NotNull + public String getFamilyName() { + return getName(); + } + + public void applyFix(@NotNull final Project project, @NotNull final ProblemDescriptor descriptor) { + final Editor editor = getEditor(project, descriptor.getPsiElement().getContainingFile()); + if (editor != null) + PyOverrideImplementUtil.chooseAndOverrideOrImplementMethods(project, editor, myClass, myToImplement); + } + + @Nullable + private static Editor getEditor(Project project, PsiFile file) { + Document document = PsiDocumentManager.getInstance(project).getDocument(file); + if (document != null) { + final EditorFactory instance = EditorFactory.getInstance(); + if (instance == null) return null; + Editor[] editors = instance.getEditors(document); + if (editors.length > 0) { + return editors[0]; + } + } + return null; + } + +}