From d30e10f60f7463d04ee70fe876245fba2e5124c2 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Fri, 27 Nov 2009 17:54:02 +0300 Subject: [PATCH] pluggable quickfixes for Python unresolved references --- python/src/META-INF/python-plugin-common.xml | 1 + ...PyUnresolvedReferenceQuickFixProvider.java | 15 ++ .../PyUnresolvedReferencesInspection.java | 202 ++++++++++-------- 3 files changed, 125 insertions(+), 93 deletions(-) create mode 100644 python/src/com/jetbrains/python/inspections/PyUnresolvedReferenceQuickFixProvider.java diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml index e0eabffa368b..9a2633908954 100644 --- a/python/src/META-INF/python-plugin-common.xml +++ b/python/src/META-INF/python-plugin-common.xml @@ -104,6 +104,7 @@ + diff --git a/python/src/com/jetbrains/python/inspections/PyUnresolvedReferenceQuickFixProvider.java b/python/src/com/jetbrains/python/inspections/PyUnresolvedReferenceQuickFixProvider.java new file mode 100644 index 000000000000..3089bfe1e52e --- /dev/null +++ b/python/src/com/jetbrains/python/inspections/PyUnresolvedReferenceQuickFixProvider.java @@ -0,0 +1,15 @@ +package com.jetbrains.python.inspections; + +import com.intellij.codeInspection.LocalQuickFix; +import com.intellij.openapi.extensions.ExtensionPointName; +import com.intellij.psi.PsiReference; +import com.intellij.util.Consumer; + +/** + * @author yole + */ +public interface PyUnresolvedReferenceQuickFixProvider { + ExtensionPointName EP_NAME = ExtensionPointName.create("Pythonid.unresolvedReferenceQuickFixProvider"); + + void registerQuickFixes(PsiReference reference, Consumer fixConsumer); +} diff --git a/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java b/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java index b38d6579d3cc..13a3c9d776ac 100644 --- a/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyUnresolvedReferencesInspection.java @@ -3,6 +3,7 @@ package com.jetbrains.python.inspections; import com.intellij.codeHighlighting.HighlightDisplayLevel; import com.intellij.codeInspection.*; import com.intellij.lang.annotation.HighlightSeverity; +import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; @@ -10,6 +11,7 @@ import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.stubs.StubIndex; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.Consumer; import com.jetbrains.python.PyBundle; import com.jetbrains.python.actions.AddFieldQuickFix; import com.jetbrains.python.actions.AddImportAction; @@ -84,8 +86,6 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool { else return name; } - static LocalQuickFix[] LQF_EMPTY = new LocalQuickFix[0]; - @NotNull static List proposeImportFixes(final PyElement node, String ref_text) { PsiFile exisitng_import_file = null; // if there's a matching existing import, this it the file it imports @@ -242,100 +242,116 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool { unresolved = (reference.resolve() == null); } if (unresolved) { - final StringBuilder description_buf = new StringBuilder(""); // TODO: clear description_buf logic. maybe a flag is needed instead. - final String text = reference.getElement().getText(); - final String ref_text = reference.getRangeInElement().substring(text); // text of the part we're working with - final PsiElement ref_element = reference.getElement(); - final boolean ref_in_import = SyntaxMatchers.IN_IMPORT.search(ref_element) != null; - List actions = new ArrayList(2); - HintAction hint_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(); - if (refex.getQualifier() != null) { - final PyClassType object_type = PyBuiltinCache.getInstance(node).getObjectType(); - if ((object_type != null) && object_type.getPossibleInstanceMembers().contains(refname)) continue; - } - // unqualified: - // may be module's - if (PyModuleType.getPossibleInstanceMembers().contains(refname)) continue; - // may be a "try: import ..."; not an error not to resolve - if (( - PsiTreeUtil.getParentOfType( - PsiTreeUtil.getParentOfType(node, PyImportElement.class), PyTryExceptStatement.class, PyIfStatement.class - ) != null - )) { - severity = HighlightSeverity.INFO; - String errmsg = PyBundle.message("INSP.module.$0.not.found", ref_text); - description_buf.append(errmsg); - // TODO: mark the node so that future references pointing to it won't result in a error, but in a warning - } - // look in other imported modules for this whole name - if (! ref_in_import) { - List import_fixes = proposeImportFixes(node, ref_text); - if (import_fixes.size() > 0) { - actions.addAll(import_fixes); - Object first_action = import_fixes.get(0); - if (first_action instanceof HintAction) { - hint_action = ((HintAction)first_action); - } - } - } - } - if (reference instanceof PsiReferenceEx) { - final String s = ((PsiReferenceEx)reference).getUnresolvedDescription(); - if (s != null) description_buf.append(s); - } - if (description_buf.length() == 0) { - boolean marked_qualified = false; - if (reference instanceof PyQualifiedExpression) { - final PyExpression qexpr = ((PyQualifiedExpression)reference).getQualifier(); - if (qexpr != null) { - PyType qtype = qexpr.getType(); - if (qtype != null) { - if (qtype instanceof PyNoneType) { - // this almost always means that we don't know the type, so don't show an error in this case - continue; - } - if (qtype instanceof PyClassType) { - PyClass cls = ((PyClassType)qtype).getPyClass(); - if (cls != null && ! PyBuiltinCache.hasInBuiltins(cls)) { - if (reference.getElement().getParent() instanceof PyCallExpression) { - actions.add(new AddMethodQuickFix(ref_text, (PyClassType)qtype)); - } - else actions.add(new AddFieldQuickFix(ref_text, cls)); - } - description_buf.append(PyBundle.message("INSP.unresolved.ref.$0.for.class.$1", ref_text, qtype.getName())); - marked_qualified = true; - } - else { - description_buf.append(PyBundle.message("INSP.cannot.find.$0.in.$1", ref_text, qtype.getName())); - marked_qualified = true; - } - } - } - } - if (! marked_qualified) { - description_buf.append(PyBundle.message("INSP.unresolved.ref.$0", ref_text)); - // add import hint unless we're an import ourselves; the rest of action will fend for itself. - if (ref_element != null && ! ref_in_import) hint_action = new AddImportAction(reference); - } - } - String description = description_buf.toString(); - ProblemHighlightType hl_type; - if (severity == HighlightSeverity.WARNING) { - hl_type = ProblemHighlightType.GENERIC_ERROR_OR_WARNING; - } - else { - hl_type = ProblemHighlightType.LIKE_UNKNOWN_SYMBOL; - } - PsiElement point = node.getLastChild(); // usually the identifier at the end of qual ref - if (point == null) point = node; - registerProblem(point, description, hl_type, hint_action, actions.toArray(LQF_EMPTY)); + registerUnresolvedReferenceProblem(node, reference, severity); } } } + private void registerUnresolvedReferenceProblem(PyElement node, PsiReference reference, HighlightSeverity severity) { + final StringBuilder description_buf = new StringBuilder(""); // TODO: clear description_buf logic. maybe a flag is needed instead. + final String text = reference.getElement().getText(); + final String ref_text = reference.getRangeInElement().substring(text); // text of the part we're working with + final PsiElement ref_element = reference.getElement(); + final boolean ref_in_import = SyntaxMatchers.IN_IMPORT.search(ref_element) != null; + final List actions = new ArrayList(2); + HintAction hint_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(); + if (refex.getQualifier() != null) { + final PyClassType object_type = PyBuiltinCache.getInstance(node).getObjectType(); + if ((object_type != null) && object_type.getPossibleInstanceMembers().contains(refname)) return; + } + // unqualified: + // may be module's + if (PyModuleType.getPossibleInstanceMembers().contains(refname)) return; + // may be a "try: import ..."; not an error not to resolve + if (( + PsiTreeUtil.getParentOfType( + PsiTreeUtil.getParentOfType(node, PyImportElement.class), PyTryExceptStatement.class, PyIfStatement.class + ) != null + )) { + severity = HighlightSeverity.INFO; + String errmsg = PyBundle.message("INSP.module.$0.not.found", ref_text); + description_buf.append(errmsg); + // TODO: mark the node so that future references pointing to it won't result in a error, but in a warning + } + // look in other imported modules for this whole name + if (! ref_in_import) { + List import_fixes = proposeImportFixes(node, ref_text); + if (import_fixes.size() > 0) { + actions.addAll(import_fixes); + Object first_action = import_fixes.get(0); + if (first_action instanceof HintAction) { + hint_action = ((HintAction)first_action); + } + } + } + } + if (reference instanceof PsiReferenceEx) { + final String s = ((PsiReferenceEx)reference).getUnresolvedDescription(); + if (s != null) description_buf.append(s); + } + if (description_buf.length() == 0) { + boolean marked_qualified = false; + if (reference instanceof PyQualifiedExpression) { + final PyExpression qexpr = ((PyQualifiedExpression)reference).getQualifier(); + if (qexpr != null) { + PyType qtype = qexpr.getType(); + if (qtype != null) { + if (qtype instanceof PyNoneType) { + // this almost always means that we don't know the type, so don't show an error in this case + return; + } + if (qtype instanceof PyClassType) { + PyClass cls = ((PyClassType)qtype).getPyClass(); + if (cls != null && ! PyBuiltinCache.hasInBuiltins(cls)) { + if (reference.getElement().getParent() instanceof PyCallExpression) { + actions.add(new AddMethodQuickFix(ref_text, (PyClassType)qtype)); + } + else actions.add(new AddFieldQuickFix(ref_text, cls)); + } + description_buf.append(PyBundle.message("INSP.unresolved.ref.$0.for.class.$1", ref_text, qtype.getName())); + marked_qualified = true; + } + else { + description_buf.append(PyBundle.message("INSP.cannot.find.$0.in.$1", ref_text, qtype.getName())); + marked_qualified = true; + } + } + } + } + if (! marked_qualified) { + description_buf.append(PyBundle.message("INSP.unresolved.ref.$0", ref_text)); + // add import hint unless we're an import ourselves; the rest of action will fend for itself. + if (ref_element != null && ! ref_in_import) hint_action = new AddImportAction(reference); + } + } + String description = description_buf.toString(); + ProblemHighlightType hl_type; + if (severity == HighlightSeverity.WARNING) { + hl_type = ProblemHighlightType.GENERIC_ERROR_OR_WARNING; + } + else { + hl_type = ProblemHighlightType.LIKE_UNKNOWN_SYMBOL; + } + + addPluginQuickFixes(reference, actions); + + PsiElement point = node.getLastChild(); // usually the identifier at the end of qual ref + if (point == null) point = node; + registerProblem(point, description, hl_type, hint_action, actions.toArray(new LocalQuickFix[actions.size()])); + } + + private static void addPluginQuickFixes(PsiReference reference, final List actions) { + for(PyUnresolvedReferenceQuickFixProvider provider: Extensions.getExtensions(PyUnresolvedReferenceQuickFixProvider.EP_NAME)) { + provider.registerQuickFixes(reference, new Consumer() { + public void consume(LocalQuickFix localQuickFix) { + actions.add(localQuickFix); + } + }); + } + } } }