mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Adds a kind of auto-import for names defined in already imported files. Hopefully fixes PY-119.
Also refactors resolve processors and adds a bunch of small code improvements.
This commit is contained in:
@@ -2,7 +2,7 @@ package com.jetbrains.python.psi.impl;
|
||||
|
||||
import com.intellij.psi.*;
|
||||
import com.jetbrains.python.psi.PyReferenceExpression;
|
||||
import com.jetbrains.python.psi.PyResolveUtil;
|
||||
import com.jetbrains.python.psi.resolve.VariantsProcessor;
|
||||
import com.jetbrains.python.psi.types.PyType;
|
||||
|
||||
/**
|
||||
@@ -26,7 +26,7 @@ public class PyJavaClassType implements PyType {
|
||||
}
|
||||
|
||||
public Object[] getCompletionVariants(final PyReferenceExpression referenceExpression) {
|
||||
final PyResolveUtil.VariantsProcessor processor = new PyResolveUtil.VariantsProcessor();
|
||||
final VariantsProcessor processor = new VariantsProcessor();
|
||||
myClass.processDeclarations(processor, ResolveState.initial(), null, referenceExpression);
|
||||
return processor.getResult();
|
||||
}
|
||||
|
||||
@@ -7,8 +7,11 @@ GNAME.item=item
|
||||
### Actions: ACT ###
|
||||
ACT.FAMILY.import=import
|
||||
|
||||
# AddImport
|
||||
# Actions and associated commands
|
||||
ACT.NAME.add.import=Add import
|
||||
ACT.NAME.use.import=Find in imported modules
|
||||
|
||||
ACT.CMD.use.import=Use an imported module
|
||||
|
||||
### Quick fixes ###
|
||||
QFIX.add.parameter.self=Add parameter 'self'
|
||||
@@ -133,4 +136,5 @@ runcfg.labels.interpreter=&Interpreter:
|
||||
runcfg.labels.interpreter_options=Interpreter &options:
|
||||
runcfg.labels.working_directory=&Working directory:
|
||||
runcfg.captions.script_parameters_dialog=Enter script parameters
|
||||
runcfg.captions.interpreter_options_dialog=Enter interpreter options
|
||||
runcfg.captions.interpreter_options_dialog=Enter interpreter options
|
||||
ACT.qualify.with.module=Qualify with module
|
||||
@@ -15,13 +15,14 @@ import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.scope.PsiScopeProcessor;
|
||||
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 com.jetbrains.python.psi.impl.ResolveImportUtil;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil;
|
||||
import com.jetbrains.python.psi.resolve.PyClassScopeProcessor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -67,7 +68,7 @@ public class AddImportAction implements HintAction, QuestionAction, LocalQuickFi
|
||||
/**
|
||||
* Finds first import statement that imports given name.
|
||||
*/
|
||||
private static class ImportLookupProcessor implements PsiScopeProcessor {
|
||||
private static class ImportLookupProcessor implements PyClassScopeProcessor {
|
||||
|
||||
String name;
|
||||
PsiElement found;
|
||||
@@ -101,6 +102,11 @@ public class AddImportAction implements HintAction, QuestionAction, LocalQuickFi
|
||||
public PsiElement getFound() {
|
||||
return found;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Class[] getPossibleTargets() {
|
||||
return NAME_DEFINER_ONLY;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
package com.jetbrains.python.actions;
|
||||
|
||||
import com.intellij.codeInsight.hint.QuestionAction;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.command.CommandProcessor;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.colors.EditorColorsManager;
|
||||
import com.intellij.openapi.editor.colors.EditorColorsScheme;
|
||||
import com.intellij.openapi.ui.popup.PopupChooserBuilder;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.ui.SimpleColoredComponent;
|
||||
import com.intellij.ui.SimpleTextAttributes;
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.PythonLanguage;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Turns an unqualified unresolved identifier into qualifed and resolvable.
|
||||
* User: dcheryasov
|
||||
* Date: Apr 15, 2009 6:24:48 PM
|
||||
*/
|
||||
public class ImportFromExistingAction implements QuestionAction {
|
||||
|
||||
PyElement myTarget;
|
||||
List<Pair<PyImportElement, PsiElement>> mySources; // list of <import, imported_item>
|
||||
Editor myEditor;
|
||||
String myName;
|
||||
|
||||
/**
|
||||
* @param target element to become qualified as imported.
|
||||
* @param sources clauses of import to be used.
|
||||
*/
|
||||
public ImportFromExistingAction(@NotNull PyElement target, @NotNull List<Pair<PyImportElement, PsiElement>> sources, String name, Editor editor) {
|
||||
mySources = sources;
|
||||
myTarget = target;
|
||||
myEditor = editor;
|
||||
myName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alters either target (by qualifying a name) or source (by explicitly importing the name).
|
||||
* @return true if action succeeded
|
||||
*/
|
||||
public boolean execute() {
|
||||
// check if the tree is sane
|
||||
PsiDocumentManager.getInstance(myTarget.getProject()).commitAllDocuments();
|
||||
if (!myTarget.isValid()) return false;
|
||||
if ((myTarget instanceof PyQualifiedExpression) && ((((PyQualifiedExpression)myTarget).getQualifier() != null))) return false; // we cannot be qualified
|
||||
for (Pair<PyImportElement, PsiElement> src : mySources) {
|
||||
if (!src.getFirst().isValid()) return false;
|
||||
if (!src.getSecond().isValid()) return false;
|
||||
}
|
||||
// act
|
||||
if (mySources.size() > 1) {
|
||||
selectSourceAndDo();
|
||||
}
|
||||
else doWriteAction(mySources.get(0).getFirst());
|
||||
return true;
|
||||
}
|
||||
|
||||
private void selectSourceAndDo() {
|
||||
// GUI part
|
||||
QualifiedHolder[] items = new QualifiedHolder[mySources.size()];
|
||||
int i = 0;
|
||||
for (Pair<PyImportElement, PsiElement> pair : mySources) {
|
||||
items[i] = new QualifiedHolder(pair.getFirst(), pair.getSecond(), myName);
|
||||
i += 1;
|
||||
}
|
||||
final JList list = new JList(items);
|
||||
list.setCellRenderer(new CellRenderer());
|
||||
|
||||
Runnable runnable = new Runnable() {
|
||||
public void run() {
|
||||
int index = list.getSelectedIndex();
|
||||
if (index < 0) return;
|
||||
PsiDocumentManager.getInstance(myTarget.getProject()).commitAllDocuments();
|
||||
doWriteAction(mySources.get(index).getFirst());
|
||||
}
|
||||
};
|
||||
|
||||
new PopupChooserBuilder(list).
|
||||
setTitle(PyBundle.message("ACT.qualify.with.module")).
|
||||
setItemChoosenCallback(runnable).
|
||||
createPopup().
|
||||
showInBestPositionFor(myEditor)
|
||||
;
|
||||
}
|
||||
|
||||
private void doIt(final PyImportElement src) {
|
||||
// did user choose 'import' or 'from import'?
|
||||
PsiElement parent = src.getParent();
|
||||
if (parent instanceof PyFromImportStatement) {
|
||||
// add another import element right after the one we got
|
||||
final PyElementGenerator gen = PythonLanguage.getInstance().getElementGenerator();
|
||||
final Project project = myTarget.getProject();
|
||||
PsiElement new_elt = gen.
|
||||
createFromText(project, PyImportElement.class, "from foo import " + myName, new int[]{0,6})
|
||||
;
|
||||
PyUtil.addListNode(parent, new_elt, null, false, true);
|
||||
}
|
||||
else { // just 'import'
|
||||
// all we need is to qualify our target
|
||||
myTarget.replace(
|
||||
PythonLanguage.getInstance().
|
||||
getElementGenerator().
|
||||
createExpressionFromText(myTarget.getProject(), src.getVisibleName()+ "." + myName)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void doWriteAction(final PyImportElement src) {
|
||||
CommandProcessor.getInstance().executeCommand(src.getProject(), new Runnable() {
|
||||
public void run() {
|
||||
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
||||
public void run() {
|
||||
doIt(src);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, PyBundle.message("ACT.CMD.use.import"), null);
|
||||
}
|
||||
|
||||
|
||||
// items to store in list
|
||||
private static class QualifiedHolder {
|
||||
final PyImportElement mySrc;
|
||||
final PsiElement myItem;
|
||||
final String myName;
|
||||
|
||||
public QualifiedHolder(PyImportElement src, PsiElement item, String name) {
|
||||
mySrc = src;
|
||||
myItem = item;
|
||||
myName = name;
|
||||
}
|
||||
|
||||
public Icon getIcon() {
|
||||
return myItem.getIcon(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
PsiElement parent = mySrc.getParent();
|
||||
if (parent instanceof PyFromImportStatement) {
|
||||
sb.append(myName);
|
||||
}
|
||||
else {
|
||||
sb.append(mySrc.getVisibleName()).append(".").append(myName);
|
||||
}
|
||||
if (myItem instanceof PyFunction) {
|
||||
sb.append("(");
|
||||
// below: ", ".join([x.getRepr(False) for x in getParameters()])
|
||||
PyParameter[] params = ((PyFunction)myItem).getParameterList().getParameters();
|
||||
String[] param_reprs = new String[params.length];
|
||||
for (int i=0; i < params.length; i += 1) param_reprs[i] = params[i].getRepr(false);
|
||||
PyUtil.joinSubarray(param_reprs, 0, params.length, ", ", sb);
|
||||
sb.append(")");
|
||||
}
|
||||
else if (myItem instanceof PyClass) {
|
||||
PyClass[] supers = ((PyClass)myItem).getSuperClasses();
|
||||
if (supers.length > 0) {
|
||||
sb.append("(");
|
||||
// ", ".join(x.getName() for x in getSuperClasses())
|
||||
String[] super_names = new String[supers.length];
|
||||
for (int i=0; i < supers.length; i += 1) super_names[i] = supers[i].getName();
|
||||
PyUtil.joinSubarray(super_names, 0, supers.length, ", ", sb);
|
||||
sb.append(")");
|
||||
}
|
||||
}
|
||||
if (parent instanceof PyFromImportStatement) {
|
||||
sb.append(" from ").append(((PyFromImportStatement)parent).getImportSource().getReferencedName());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// Stolen from FQNameCellRenderer
|
||||
private static class CellRenderer extends SimpleColoredComponent implements ListCellRenderer {
|
||||
private final Font FONT;
|
||||
|
||||
public CellRenderer() {
|
||||
EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme();
|
||||
FONT = new Font(scheme.getEditorFontName(), Font.PLAIN, scheme.getEditorFontSize());
|
||||
setOpaque(true);
|
||||
}
|
||||
|
||||
// value is a QualifiedHolder
|
||||
public Component getListCellRendererComponent(
|
||||
JList list,
|
||||
Object value, // expected to be
|
||||
int index,
|
||||
boolean isSelected,
|
||||
boolean cellHasFocus
|
||||
){
|
||||
|
||||
clear();
|
||||
|
||||
QualifiedHolder item = (QualifiedHolder)value;
|
||||
setIcon(item.getIcon());
|
||||
String item_name = item.toString();
|
||||
append(item_name, SimpleTextAttributes.REGULAR_ATTRIBUTES);
|
||||
setFont(FONT);
|
||||
if (isSelected) {
|
||||
setBackground(list.getSelectionBackground());
|
||||
setForeground(list.getSelectionForeground());
|
||||
}
|
||||
else {
|
||||
setBackground(list.getBackground());
|
||||
setForeground(list.getForeground());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package com.jetbrains.python.actions;
|
||||
|
||||
import com.intellij.codeInsight.CodeInsightUtilBase;
|
||||
import com.intellij.codeInsight.daemon.impl.ShowAutoImportPass;
|
||||
import com.intellij.codeInsight.hint.HintManager;
|
||||
import com.intellij.codeInspection.HintAction;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.psi.PyElement;
|
||||
import com.jetbrains.python.psi.PyImportElement;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Handles cases when an unresolved name may be imported from one of existing imported modules.
|
||||
* The object contains a list of modules from which given name might be imported.
|
||||
* User: dcheryasov
|
||||
* Date: Apr 15, 2009 2:06:25 PM
|
||||
*/
|
||||
public class ImportFromExistingFix implements HintAction {
|
||||
|
||||
PyElement myNode;
|
||||
|
||||
List<Pair<PyImportElement, PsiElement>> myImports; // from where and what to import
|
||||
String myName;
|
||||
|
||||
/**
|
||||
* Creates a new, empty fix object.
|
||||
* @param node to which the fix applies.
|
||||
*/
|
||||
public ImportFromExistingFix(PyElement node, String name) {
|
||||
myNode = node;
|
||||
myImports = new ArrayList<Pair<PyImportElement, PsiElement>>();
|
||||
myName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a mew fix object with one import variant.
|
||||
* @param node to which the fix applies.
|
||||
* @param source from which the name is importable.
|
||||
* @param name
|
||||
*/
|
||||
public ImportFromExistingFix(PyElement node, PyImportElement source, PsiElement item, String name) {
|
||||
this(node, name);
|
||||
addImport(source, item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds another import source.
|
||||
* @param source an import statement from which the name is importable.
|
||||
*/
|
||||
public void addImport(PyImportElement source, PsiElement item) {
|
||||
myImports.add(new Pair<PyImportElement, PsiElement>(source, item));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getText() {
|
||||
return PyBundle.message("ACT.NAME.use.import");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getFamilyName() {
|
||||
return PyBundle.message("ACT.FAMILY.import");
|
||||
}
|
||||
|
||||
public boolean showHint(Editor editor) {
|
||||
if (myNode == null || !myNode.isValid() || myNode.getName() == null || myImports.size() <= 0) {
|
||||
return false; // TODO: also return false if an on-the-fly unambiguous fix is possible?
|
||||
}
|
||||
final String message = ShowAutoImportPass.getMessage(
|
||||
myImports.size() > 1,
|
||||
myImports.get(0).getFirst().getVisibleName()+"."+myNode.getName()
|
||||
);
|
||||
final ImportFromExistingAction action = new ImportFromExistingAction(myNode, myImports, myName, editor);
|
||||
HintManager.getInstance().showQuestionHint(
|
||||
editor, message,
|
||||
myNode.getTextOffset(),
|
||||
myNode.getTextRange().getEndOffset(), action);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
|
||||
return myNode != null && myNode.isValid() && myImports.size() > 0;
|
||||
}
|
||||
|
||||
public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
|
||||
// make sure file is committed, writable, etc
|
||||
if (!CodeInsightUtilBase.prepareFileForWrite(file)) return;
|
||||
// act
|
||||
ImportFromExistingAction action = new ImportFromExistingAction(myNode, myImports, myName, editor);
|
||||
action.execute(); // assume that action runs in WriteAction on its own behalf
|
||||
}
|
||||
|
||||
public boolean startInWriteAction() {
|
||||
return false; // multiple variants may make us show a menu
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import com.jetbrains.python.actions.AddSelfQuickFix;
|
||||
import com.jetbrains.python.actions.RenameToSelfQuickFix;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.PyDecorator;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -8,6 +8,8 @@ import com.intellij.psi.PsiElementVisitor;
|
||||
import com.jetbrains.python.PyTokenTypes;
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil;
|
||||
import com.jetbrains.python.psi.resolve.ResolveProcessor;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -68,7 +70,7 @@ public class PyRedeclarationInspection extends LocalInspectionTool {
|
||||
private void _checkAbove(PyElement node, String kind) {
|
||||
String name = node.getName();
|
||||
if (name != null) {
|
||||
PyResolveUtil.ResolveProcessor proc = new PyResolveUtil.ResolveProcessor(node.getName());
|
||||
ResolveProcessor proc = new ResolveProcessor(node.getName());
|
||||
PyResolveUtil.treeCrawlUp(proc, node);
|
||||
PsiElement found = proc.getResult();
|
||||
// TODO: check if the redefined name is used somehow
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package com.jetbrains.python.inspections;
|
||||
|
||||
import com.intellij.codeHighlighting.HighlightDisplayLevel;
|
||||
import com.intellij.codeInspection.LocalInspectionTool;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.ProblemHighlightType;
|
||||
import com.intellij.codeInspection.ProblemsHolder;
|
||||
import com.intellij.codeInspection.*;
|
||||
import com.intellij.lang.annotation.HighlightSeverity;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiElementVisitor;
|
||||
@@ -15,14 +12,20 @@ import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.actions.AddFieldQuickFix;
|
||||
import com.jetbrains.python.actions.AddImportAction;
|
||||
import com.jetbrains.python.actions.AddMethodQuickFix;
|
||||
import com.jetbrains.python.actions.ImportFromExistingFix;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.PyBuiltinCache;
|
||||
import com.jetbrains.python.psi.resolve.CollectProcessor;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil;
|
||||
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 org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Marks references that fail to resolve.
|
||||
@@ -69,6 +72,62 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool {
|
||||
super(holder);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static HintAction proposeImportFixes(final PyElement node, String ref_text) {
|
||||
boolean worthy_fix = false;
|
||||
ImportFromExistingFix fix = null;
|
||||
// maybe the name is importable via some exisitng 'import foo' statement, and only needs a qualifier.
|
||||
// walk up collecting all such statements and analyzing
|
||||
CollectProcessor import_prc = new CollectProcessor(PyImportStatement.class);
|
||||
PyResolveUtil.treeCrawlUp(import_prc, node);
|
||||
List<PsiElement> result = import_prc.getResult();
|
||||
if (result.size() > 0) {
|
||||
fix = new ImportFromExistingFix(node, ref_text); // initially it is almost as lightweight as a plain list
|
||||
for (PsiElement stmt : import_prc.getResult()) {
|
||||
for (PyImportElement ielt : ((PyImportStatement)stmt).getImportElements()) {
|
||||
final PyReferenceExpression src = ielt.getImportReference();
|
||||
if (src != null) {
|
||||
PsiElement dst = src.resolve();
|
||||
if (dst instanceof PyFile) {
|
||||
PsiElement res = ((PyFile)dst).findExportedName(ref_text);
|
||||
if (res != null) {
|
||||
fix.addImport(ielt, res);
|
||||
worthy_fix = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// maybe the name is importable via some exisitng 'from foo import ...' statement, and only needs another name to be imported.
|
||||
// walk up collecting all such statements and analyzing
|
||||
CollectProcessor from_import_prc = new CollectProcessor(PyFromImportStatement.class);
|
||||
PyResolveUtil.treeCrawlUp(from_import_prc, node);
|
||||
result = from_import_prc.getResult();
|
||||
if (result.size() > 0) {
|
||||
if (fix == null) fix = new ImportFromExistingFix(node, ref_text); // it might have been created in the previous scan, or not.
|
||||
for (PsiElement stmt : from_import_prc.getResult()) {
|
||||
PyFromImportStatement from_stmt = (PyFromImportStatement)stmt;
|
||||
PyImportElement[] ielts = from_stmt.getImportElements();
|
||||
if (ielts != null && ielts.length > 0) {
|
||||
final PyReferenceExpression src = from_stmt.getImportSource();
|
||||
if (src != null) {
|
||||
PsiElement dst = src.resolve();
|
||||
if (dst instanceof PyFile) {
|
||||
PsiElement res = ((PyFile)dst).findExportedName(ref_text);
|
||||
if (res != null) {
|
||||
fix.addImport(ielts[ielts.length-1], res); // last element; action expects to add to tail
|
||||
worthy_fix = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (worthy_fix) return fix;
|
||||
else return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyElement(final PyElement node) {
|
||||
super.visitPyElement(node); //To change body of overridden methods use File | Settings | File Templates.
|
||||
@@ -88,10 +147,11 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool {
|
||||
unresolved = (reference.resolve() == null);
|
||||
}
|
||||
if (unresolved) {
|
||||
StringBuffer description_buf = new StringBuffer("");
|
||||
StringBuffer description_buf = new StringBuffer(""); // TODO: clear description_buf logic. maybe a flag is needed instead.
|
||||
String text = reference.getElement().getText();
|
||||
String ref_text = reference.getRangeInElement().substring(text); // text of the part we're working with
|
||||
LocalQuickFix action = null;
|
||||
HintAction hint_action = null;
|
||||
if (ref_text.length() <= 0) return; // empty text, nothing to highlight
|
||||
if (reference instanceof PyReferenceExpression) {
|
||||
PyReferenceExpression refex = (PyReferenceExpression)reference;
|
||||
@@ -102,15 +162,20 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool {
|
||||
}
|
||||
// unqualified:
|
||||
// may be module's
|
||||
if ((new PyModuleType(node.getContainingFile())).getPossibleInstanceMembers().contains(refname)) continue;
|
||||
// may be try: import; not an error not to resolve
|
||||
if ((PsiTreeUtil.getParentOfType(
|
||||
PsiTreeUtil.getParentOfType(node, PyImportElement.class), PyTryExceptStatement.class, PyIfStatement.class) != null)
|
||||
) {
|
||||
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
|
||||
hint_action = proposeImportFixes(node, ref_text);
|
||||
}
|
||||
if (reference instanceof PsiReferenceEx) {
|
||||
final String s = ((PsiReferenceEx)reference).getUnresolvedDescription();
|
||||
@@ -127,11 +192,7 @@ 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;
|
||||
}
|
||||
/*
|
||||
PyReferenceExpression qref = (PyReferenceExpression)qexpr;
|
||||
PsiElement qual_resolved = qref.resolve();
|
||||
*/
|
||||
if (/*qual_resolved instanceof PyClass*/ qtype != null && qtype instanceof PyClassType) {
|
||||
if (qtype != null && qtype instanceof PyClassType) {
|
||||
PyClass cls = ((PyClassType)qtype).getPyClass();
|
||||
if (cls != null) {
|
||||
if (reference.getElement().getParent() instanceof PyCallExpression) {
|
||||
@@ -160,7 +221,7 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool {
|
||||
}
|
||||
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, null, action);
|
||||
registerProblem(point, description, hl_type, hint_action, action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
package com.jetbrains.python.psi;
|
||||
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -20,7 +22,16 @@ public interface PyFile extends PyElement, PsiFile {
|
||||
List<PyFunction> getTopLevelFunctions();
|
||||
|
||||
List<PyTargetExpression> getTopLevelAttributes();
|
||||
|
||||
|
||||
/**
|
||||
* Looks for a name exported by this file, preferably in an efficient way.
|
||||
* @param name what to find
|
||||
* @param project project to use (useful for out-of-hierarchy files)
|
||||
* @return found element, or null.
|
||||
*/
|
||||
@Nullable
|
||||
PsiElement findExportedName(String name);
|
||||
|
||||
/**
|
||||
@return an URL of file, maybe bogus if virtual file is not present.
|
||||
*/
|
||||
|
||||
@@ -33,5 +33,10 @@ public interface PyImportElement extends PyElement, NameDefiner {
|
||||
@Nullable
|
||||
PyTargetExpression getAsName();
|
||||
|
||||
/**
|
||||
* @return name under which the element is wisible, that is, "as name" is there is one, or just name.
|
||||
*/
|
||||
@Nullable
|
||||
String getVisibleName();
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.jetbrains.python.psi;
|
||||
import com.intellij.psi.PsiNamedElement;
|
||||
import com.intellij.psi.StubBasedPsiElement;
|
||||
import com.jetbrains.python.psi.stubs.PyParameterStub;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
@@ -35,5 +36,12 @@ public interface PyParameter extends PyElement, PsiNamedElement, PyExpression, S
|
||||
|
||||
@Nullable
|
||||
PyExpression getDefaultValue();
|
||||
|
||||
/**
|
||||
* @param includeDefaultValue if true, include the default value after an " = ".
|
||||
* @return Canonical representation of parameter. Includes asterisks for *param and **param, and name.
|
||||
*/
|
||||
@NotNull
|
||||
String getRepr(boolean includeDefaultValue);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,643 +0,0 @@
|
||||
/*
|
||||
* Copyright 2005 Pythonid Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS"; BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.jetbrains.python.psi;
|
||||
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.codeInsight.lookup.LookupElementFactory;
|
||||
import com.intellij.codeInsight.lookup.LookupItem;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.scope.PsiScopeProcessor;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.jetbrains.python.psi.impl.PyScopeProcessor;
|
||||
import com.jetbrains.python.psi.impl.ResolveImportUtil;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Ref resolution routines.
|
||||
* User: yole
|
||||
* Date: 14.06.2005
|
||||
*/
|
||||
public class PyResolveUtil {
|
||||
|
||||
private PyResolveUtil() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tries to find nearest parent that conceals nams definded inside it. Such elements are 'class' and 'def':
|
||||
* anything defined within it does not seep to the namespace below them, but is concealed within.
|
||||
* @param elt starting point of search.
|
||||
* @return 'class' or 'def' element, or null if not found.
|
||||
*/
|
||||
@Nullable
|
||||
public static PsiElement getConcealingParent(PsiElement elt) {
|
||||
return PsiTreeUtil.getParentOfType(elt, PyClass.class, PyFunction.class);
|
||||
}
|
||||
|
||||
protected static PsiElement getInnermostChildOf(PsiElement elt) {
|
||||
PsiElement feeler = elt;
|
||||
PsiElement seeker;
|
||||
seeker = feeler;
|
||||
// find innermost last child of the subtree we're in
|
||||
while (feeler != null) {
|
||||
seeker = feeler;
|
||||
feeler = feeler.getLastChild();
|
||||
}
|
||||
return seeker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns closest previous node of given class, as input file would have it.
|
||||
* @param elt node from which to look for a previous atatement.
|
||||
* @param cls class of the previous node to find.
|
||||
* @return previous statement, or null.
|
||||
*/
|
||||
@Nullable
|
||||
public static <T> T getPrevNodeOf(PsiElement elt, Class<T> cls) {
|
||||
PsiElement seeker = elt;
|
||||
while (seeker != null) {
|
||||
PsiElement feeler = seeker.getPrevSibling();
|
||||
if (feeler != null) {
|
||||
seeker = getInnermostChildOf(feeler);
|
||||
}
|
||||
else { // we were the first subnode
|
||||
// find something above the parent node we've not exhausted yet
|
||||
seeker = seeker.getParent();
|
||||
if (seeker instanceof PyFile) return null; // all file nodes have been looked up, in vain
|
||||
}
|
||||
if (cls.isInstance(seeker)) return (T)seeker;
|
||||
}
|
||||
// here elt is null or a PsiFile is not up in the parent chain.
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crawls up the PSI tree, checking nodes as if crawling backwards through source lexemes.
|
||||
* @param processor a visitor that says when the crawl is done and collects info.
|
||||
* @param elt element from which we start (not checked by processor); if null, the search immediately returns null.
|
||||
* @param roof if not null, search continues only below the roof and including it.
|
||||
* @param fromunder if true, begin search not above elt, but from a [possibly imaginary] node right below elt; so elt gets analyzed, too.
|
||||
* @return first element that the processor accepted.
|
||||
*/
|
||||
@Nullable
|
||||
public static PsiElement treeCrawlUp(PsiScopeProcessor processor, boolean fromunder, PsiElement elt, PsiElement roof) {
|
||||
if (elt == null) return null; // can't find anyway.
|
||||
PsiElement seeker = elt;
|
||||
PsiElement cap = getConcealingParent(elt);
|
||||
do {
|
||||
ProgressManager.getInstance().checkCanceled();
|
||||
if (fromunder) {
|
||||
fromunder = false; // only honour fromunder once per call
|
||||
seeker = getPrevNodeOf(getInnermostChildOf(seeker), NameDefiner.class);
|
||||
}
|
||||
else { // main case
|
||||
seeker = getPrevNodeOf(seeker, NameDefiner.class);
|
||||
}
|
||||
// aren't we in the same defining assignment, global, etc?
|
||||
if ((seeker != null) && ((NameDefiner)seeker).mustResolveOutside() && PsiTreeUtil.isAncestor(seeker, elt, true)
|
||||
) {
|
||||
seeker = getPrevNodeOf(seeker, NameDefiner.class);
|
||||
}
|
||||
// maybe we're under a cap?
|
||||
while (true) {
|
||||
PsiElement local_cap = getConcealingParent(seeker);
|
||||
if (local_cap == null) break; // seeker is in global context
|
||||
if (local_cap == cap) break; // seeker is in the same context as elt
|
||||
if ((cap != null) && PsiTreeUtil.isAncestor(local_cap, cap, true)) break; // seeker is in a context above elt's
|
||||
if (
|
||||
(local_cap != elt) && // elt isn't the cap of seeker itself
|
||||
((cap == null) || !PsiTreeUtil.isAncestor(local_cap, cap, true)) // elt's cap is not under local cap
|
||||
) { // only look at local cap and above
|
||||
if (local_cap instanceof NameDefiner) seeker = local_cap;
|
||||
else seeker = getPrevNodeOf(local_cap, NameDefiner.class);
|
||||
}
|
||||
else break; // seeker is contextually under elt already
|
||||
}
|
||||
// are we still under the roof?
|
||||
if ((roof != null) && (seeker != null) && ! PsiTreeUtil.isAncestor(roof, seeker, false)) return null;
|
||||
// maybe we're capped by a class?
|
||||
if (refersFromMethodToClass(cap, seeker)) continue;
|
||||
// check what we got
|
||||
if (seeker != null) {
|
||||
if (!processor.execute(seeker, ResolveState.initial())) {
|
||||
if (processor instanceof ResolveProcessor) {
|
||||
return ((ResolveProcessor)processor).getResult();
|
||||
}
|
||||
else return seeker; // can't point to exact element, but somewhere here
|
||||
}
|
||||
}
|
||||
} while (seeker != null);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PsiElement resolveOffContext(@NotNull PyReferenceExpression refex) {
|
||||
// if we're under a cap, an external object that we want to use might be also defined below us.
|
||||
// look through all contexts, closest first.
|
||||
PsiElement ret = null;
|
||||
PsiElement our_cap = getConcealingParent(refex);
|
||||
ResolveProcessor proc = new ResolveProcessor(refex.getReferencedName()); // processor reusable till first hit
|
||||
if (our_cap != null) {
|
||||
PsiElement cap = our_cap;
|
||||
while (true) {
|
||||
cap = getConcealingParent(cap);
|
||||
if (cap == null) cap = refex.getContainingFile();
|
||||
ret = treeCrawlUp(proc, true, cap);
|
||||
if ((ret != null) && !PsiTreeUtil.isAncestor(our_cap, ret, true)) { // found something and it is below our cap
|
||||
// maybe we're in a method, and what we found is in its class context?
|
||||
if (! refersFromMethodToClass(our_cap, ret)) {
|
||||
break; // not in method -> must be all right
|
||||
}
|
||||
}
|
||||
if (cap instanceof PsiFile) break; // file level, can't try more
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param inner an element presumably inside a method within a class, or a method itself.
|
||||
* @param outer an element presumably in the class context.
|
||||
* @return true if an outer element is in a class context, while the inner is a method or function inside it.
|
||||
* @see com.jetbrains.python.psi.PyResolveUtil#getConcealingParent(com.intellij.psi.PsiElement)
|
||||
*/
|
||||
protected static boolean refersFromMethodToClass(final PsiElement inner, final PsiElement outer) {
|
||||
return (
|
||||
(getConcealingParent(outer) instanceof PyClass) && // outer is in a class context
|
||||
(PsiTreeUtil.getParentOfType(inner, PyFunction.class, false) != null) // inner is a function or method within the class
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crawls up the PSI tree, checking nodes as if crawling backwards through source lexemes.
|
||||
* @param processor a visitor that says when the crawl is done and collects info.
|
||||
* @param fromunder if true, search not above elt, but from a [possibly imaginary] node right below elt; so elt gets analyzed, too.
|
||||
* @param elt element from which we start (not checked by processor); if null, the search immediately fails.
|
||||
* @return first element that the processor accepted.
|
||||
*/
|
||||
@Nullable
|
||||
public static PsiElement treeCrawlUp(PsiScopeProcessor processor, boolean fromunder, PsiElement elt) {
|
||||
return treeCrawlUp(processor, fromunder, elt, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns treeCrawlUp(processor, elt, false). A convenience method.
|
||||
* @see PyResolveUtil#treeCrawlUp(com.intellij.psi.scope.PsiScopeProcessor,boolean,com.intellij.psi.PsiElement)
|
||||
*/
|
||||
@Nullable
|
||||
public static PsiElement treeCrawlUp(PsiScopeProcessor processor, PsiElement elt) {
|
||||
return treeCrawlUp(processor, false, elt);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tries to match two [qualified] reference expression paths by names; target must be a 'sublist' of source to match.
|
||||
* E.g., 'a.b.c.d' and 'a.b.c' would match, while 'a.b.c' and 'a.b.c.d' would not. Eqaully, 'a.b.c' and 'a.b.d' would not match.
|
||||
* If either source or target is null, false is returned.
|
||||
* @see #unwindQualifiers(PyQualifiedExpression) .
|
||||
* @param source_path expression path to match (the longer list of qualifiers).
|
||||
* @param target_path expression path to match against (hopeful sublist of qualifiers of source).
|
||||
* @return true if source matches target.
|
||||
*/
|
||||
public static <S extends PyExpression, T extends PyExpression> boolean pathsMatch(List<S> source_path, List<T> target_path) {
|
||||
// turn qualifiers into lists
|
||||
if ((source_path == null) || (target_path == null)) return false;
|
||||
// compare until target is exhausted
|
||||
Iterator<S> source_iter = source_path.iterator();
|
||||
for (final T target_elt : target_path) {
|
||||
if (source_iter.hasNext()) {
|
||||
S source_elt = source_iter.next();
|
||||
if (!target_elt.getText().equals(source_elt.getText())) return false;
|
||||
}
|
||||
else return false; // source exhausted before target
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwinds a [multi-level] qualified expression into a path, as seen in source text, i.e. outermost qualifier first.
|
||||
* If any qualifier happens to be not a PyQualifiedExpression, or expr is null, null is returned.
|
||||
* @param expr an experssion to unwind.
|
||||
* @return path as a list of ref expressions, or null.
|
||||
*/
|
||||
@Nullable
|
||||
public static <T extends PyQualifiedExpression> List<T> unwindQualifiers(final T expr) {
|
||||
final List<T> path = new LinkedList<T>();
|
||||
PyExpression maybe_step;
|
||||
T step = expr;
|
||||
try {
|
||||
while (step != null) {
|
||||
path.add(0, step);
|
||||
maybe_step = step.getQualifier();
|
||||
step = (T)maybe_step;
|
||||
}
|
||||
}
|
||||
catch (ClassCastException e) {
|
||||
return null;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
public static String toPath(PyQualifiedExpression expr, String separator) {
|
||||
if (expr == null) return "";
|
||||
List<PyQualifiedExpression> path = unwindQualifiers(expr);
|
||||
if (path != null) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
boolean is_not_first = false;
|
||||
for (PyQualifiedExpression ex : path) {
|
||||
if (is_not_first) buf.append(separator);
|
||||
else is_not_first = true;
|
||||
buf.append(ex.getName());
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
else return expr.getName();
|
||||
}
|
||||
|
||||
public static class CollectProcessor<T extends PsiElement> implements PsiScopeProcessor {
|
||||
|
||||
Class<T>[] my_collectables;
|
||||
List<T> my_result;
|
||||
|
||||
public CollectProcessor(Class<T>... collectables) {
|
||||
my_collectables = collectables;
|
||||
my_result = new ArrayList<T>();
|
||||
}
|
||||
|
||||
public boolean execute(final PsiElement element, final ResolveState state) {
|
||||
for (Class<T> cls : my_collectables) {
|
||||
if (cls.isInstance(element)) {
|
||||
my_result.add((T)element);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public <T> T getHint(final Class<T> hintClass) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void handleEvent(final Event event, final Object associated) {
|
||||
}
|
||||
|
||||
public List<T> getResult() {
|
||||
return my_result;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ResolveProcessor implements PyScopeProcessor {
|
||||
private final String myName;
|
||||
private PsiElement myResult = null;
|
||||
/*private Set<String> mySeen;*/
|
||||
private final List<NameDefiner> myDefiners;
|
||||
|
||||
public ResolveProcessor(final String name) {
|
||||
myName = name;
|
||||
/*mySeen = new HashSet<String>();*/
|
||||
myDefiners = new ArrayList<NameDefiner>(2); // 1 is typical, 2 is sometimes, more is rare.
|
||||
}
|
||||
|
||||
public PsiElement getResult() {
|
||||
return myResult;
|
||||
}
|
||||
|
||||
@NonNls
|
||||
static String _nvl(Object s) {
|
||||
if (s != null) return "'" + s.toString() + "'";
|
||||
else return "null";
|
||||
}
|
||||
|
||||
/*
|
||||
public Set<String> getSeen() {
|
||||
return mySeen;
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Adds a NameDefiner point which is a secondary resolution target. E.g. import statement for imported name.
|
||||
* @param definer
|
||||
*/
|
||||
protected void addNameDefiner(NameDefiner definer) {
|
||||
myDefiners.add(definer);
|
||||
}
|
||||
|
||||
public List<NameDefiner>getDefiners() {
|
||||
return myDefiners;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return _nvl(myName) + ", " + _nvl(myResult);
|
||||
}
|
||||
|
||||
public boolean execute(PsiElement element, ResolveState substitutor) {
|
||||
if (element instanceof PyFile) {
|
||||
final VirtualFile file = ((PyFile)element).getVirtualFile();
|
||||
if (file != null) {
|
||||
if (myName.equals(file.getNameWithoutExtension())) {
|
||||
myResult = element;
|
||||
return false;
|
||||
}
|
||||
else if (ResolveImportUtil.INIT_PY.equals(file.getName())) {
|
||||
VirtualFile dir = file.getParent();
|
||||
if ((dir != null) && myName.equals(dir.getName())) {
|
||||
myResult = element;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (element instanceof PsiNamedElement) {
|
||||
if (myName.equals(((PsiNamedElement)element).getName())) {
|
||||
myResult = element;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (element instanceof PyReferenceExpression) {
|
||||
PyReferenceExpression expr = (PyReferenceExpression)element;
|
||||
String referencedName = expr.getReferencedName();
|
||||
if (referencedName != null && referencedName.equals(myName)) {
|
||||
myResult = element;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (element instanceof NameDefiner) {
|
||||
final NameDefiner definer = (NameDefiner)element;
|
||||
PsiElement by_name = definer.getElementNamed(myName);
|
||||
if (by_name != null) {
|
||||
myResult = by_name;
|
||||
if (!PsiTreeUtil.isAncestor(element, by_name, true)) { // non-trivial definer
|
||||
addNameDefiner(definer);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean execute(final PsiElement element, final String asName) {
|
||||
if (asName.equals(myName)) {
|
||||
myResult = element;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public <T> T getHint(Class<T> hintClass) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void handleEvent(Event event, Object associated) {
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static class MultiResolveProcessor implements PsiScopeProcessor {
|
||||
private final String _name;
|
||||
private final List<ResolveResult> _results = new ArrayList<ResolveResult>();
|
||||
|
||||
public MultiResolveProcessor(String name) {
|
||||
_name = name;
|
||||
}
|
||||
|
||||
public ResolveResult[] getResults() {
|
||||
return _results.toArray(new ResolveResult[_results.size()]);
|
||||
}
|
||||
|
||||
public boolean execute(PsiElement element, ResolveState substitutor) {
|
||||
if (element instanceof PsiNamedElement) {
|
||||
if (_name.equals(((PsiNamedElement)element).getName())) {
|
||||
_results.add(new PsiElementResolveResult(element));
|
||||
}
|
||||
}
|
||||
else if (element instanceof PyReferenceExpression) {
|
||||
PyReferenceExpression expr = (PyReferenceExpression)element;
|
||||
String referencedName = expr.getReferencedName();
|
||||
if (referencedName != null && referencedName.equals(_name)) {
|
||||
_results.add(new PsiElementResolveResult(element));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public <T> T getHint(Class<T> hintClass) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void handleEvent(Event event, Object associated) {
|
||||
}
|
||||
}
|
||||
|
||||
public static class VariantsProcessor implements PsiScopeProcessor {
|
||||
private final Map<String, LookupElement> myVariants = new HashMap<String, LookupElement>();
|
||||
|
||||
protected String my_notice;
|
||||
|
||||
public VariantsProcessor() {
|
||||
// empty
|
||||
}
|
||||
|
||||
public VariantsProcessor(final Filter filter) {
|
||||
my_filter = filter;
|
||||
}
|
||||
|
||||
protected Filter my_filter;
|
||||
|
||||
public void setNotice(@Nullable String notice) {
|
||||
my_notice = notice;
|
||||
}
|
||||
|
||||
protected void setupItem(LookupItem item) {
|
||||
if (my_notice != null) {
|
||||
setItemNotice(item, my_notice);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void setItemNotice(final LookupItem item, String notice) {
|
||||
item.setAttribute(item.TAIL_TEXT_ATTR, notice);
|
||||
item.setAttribute(item.TAIL_TEXT_SMALL_ATTR, "");
|
||||
}
|
||||
|
||||
public LookupElement[] getResult() {
|
||||
final Collection<LookupElement> variants = myVariants.values();
|
||||
return variants.toArray(new LookupElement[variants.size()]);
|
||||
}
|
||||
|
||||
public List<LookupElement> getResultList() {
|
||||
return new ArrayList<LookupElement>(myVariants.values());
|
||||
}
|
||||
|
||||
public boolean execute(PsiElement element, ResolveState substitutor) {
|
||||
if (my_filter != null && !my_filter.accept(element)) return true; // skip whatever the filter rejects
|
||||
// TODO: refactor to look saner; much code duplication
|
||||
if (element instanceof PsiNamedElement) {
|
||||
final PsiNamedElement psiNamedElement = (PsiNamedElement)element;
|
||||
final String name = psiNamedElement.getName();
|
||||
if (!myVariants.containsKey(name)) {
|
||||
final LookupItem lookup_item = (LookupItem)LookupElementFactory.getInstance().createLookupElement(psiNamedElement);
|
||||
setupItem(lookup_item);
|
||||
myVariants.put(name, lookup_item);
|
||||
}
|
||||
}
|
||||
else if (element instanceof PyReferenceExpression) {
|
||||
PyReferenceExpression expr = (PyReferenceExpression)element;
|
||||
String referencedName = expr.getReferencedName();
|
||||
if (referencedName != null && !myVariants.containsKey(referencedName)) {
|
||||
final LookupItem lookup_item = (LookupItem)LookupElementFactory.getInstance().createLookupElement(referencedName);
|
||||
setupItem(lookup_item);
|
||||
myVariants.put(referencedName, lookup_item);
|
||||
}
|
||||
}
|
||||
else if (element instanceof NameDefiner) {
|
||||
final NameDefiner definer = (NameDefiner)element;
|
||||
for (PyElement expr: definer.iterateNames()) {
|
||||
if (expr != null) { // NOTE: maybe rather have SingleIterables skip nulls outright?
|
||||
String referencedName = expr.getName();
|
||||
if (referencedName != null && !myVariants.containsKey(referencedName)) {
|
||||
final LookupItem lookup_item = (LookupItem)LookupElementFactory.getInstance().createLookupElement(referencedName);
|
||||
setupItem(lookup_item);
|
||||
if (definer instanceof PyImportElement) { // set notice to imported module name if needed
|
||||
PsiElement maybe_from_import = definer.getParent();
|
||||
if (maybe_from_import instanceof PyFromImportStatement) {
|
||||
final PyFromImportStatement from_import = (PyFromImportStatement)maybe_from_import;
|
||||
PyReferenceExpression src = from_import.getImportSource();
|
||||
if (src != null) {
|
||||
setItemNotice(lookup_item, " | " + src.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
myVariants.put(referencedName, lookup_item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public <T> T getHint(Class<T> hintClass) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void handleEvent(Event event, Object associated) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple interface allowing to filter processor results.
|
||||
*/
|
||||
public interface Filter {
|
||||
/**
|
||||
* @param target the object a processor is currently looking at.
|
||||
* @return true if the object is acceptable as a processor result.
|
||||
*/
|
||||
boolean accept(Object target);
|
||||
}
|
||||
|
||||
public static class FilterNotInstance implements Filter {
|
||||
Object instance;
|
||||
|
||||
public FilterNotInstance(Object instance) {
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
public boolean accept(final Object target) {
|
||||
return (instance != target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects all assignments in context above given element, if they match given naming pattern.
|
||||
* Used to track creation of attributes by assignment (e.g in constructor).
|
||||
*/
|
||||
public static class AssignmentCollectProcessor<T extends PyExpression> implements PsiScopeProcessor {
|
||||
|
||||
List<T> my_qualifier;
|
||||
List<PyExpression> my_result;
|
||||
Set<String> my_seen_names;
|
||||
|
||||
/**
|
||||
* Creates an instance to collect assignments of attributes to the object identified by 'qualifier'.
|
||||
* E.g. if qualifier = {"foo", "bar"} then assignments like "foo.bar.baz = ..." will be considered.
|
||||
* The collection continues up to the point of latest redefinition of the object identified by 'qualifier',
|
||||
* that is, up to the point of something like "foo.bar = ..." or "foo = ...".
|
||||
* @param qualifier qualifying names, outermost first; must not be empty.
|
||||
*/
|
||||
public AssignmentCollectProcessor(@NotNull List<T> qualifier) {
|
||||
assert qualifier.size() > 0;
|
||||
my_qualifier = qualifier;
|
||||
my_result = new ArrayList<PyExpression>();
|
||||
my_seen_names = new HashSet<String>();
|
||||
}
|
||||
|
||||
public boolean execute(final PsiElement element, final ResolveState state) {
|
||||
if (element instanceof PyAssignmentStatement) {
|
||||
final PyAssignmentStatement assignment = (PyAssignmentStatement)element;
|
||||
for (PyExpression ex : assignment.getTargets()) {
|
||||
if (ex instanceof PyTargetExpression) {
|
||||
final PyTargetExpression target = (PyTargetExpression)ex;
|
||||
List<PyTargetExpression> quals = unwindQualifiers(target);
|
||||
if (quals != null) {
|
||||
if (quals.size() == my_qualifier.size()+1 && pathsMatch(quals, my_qualifier)) {
|
||||
// a new attribute follows last qualifier; collect it.
|
||||
PyTargetExpression last_elt = quals.get(quals.size() - 1); // last item is the outermost, new, attribute.
|
||||
String last_elt_name = last_elt.getName();
|
||||
if (!my_seen_names.contains(last_elt_name)) { // no dupes, only remember the latest
|
||||
my_result.add(last_elt);
|
||||
my_seen_names.add(last_elt_name);
|
||||
}
|
||||
}
|
||||
else if (quals.size() < my_qualifier.size()+1 && pathsMatch(my_qualifier, quals)) {
|
||||
// qualifier(s) get redefined; collect no more.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return true; // nothing interesting found, continue
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a collection of exressions (parts of assignment expressions) where new attributes were defined. E.g. for "a.b.c = 1",
|
||||
* the expression for 'c' is in the result.
|
||||
*/
|
||||
@NotNull
|
||||
public Collection<PyExpression> getResult() {
|
||||
return my_result;
|
||||
}
|
||||
|
||||
public <T> T getHint(final Class<T> hintClass) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void handleEvent(final Event event, final Object associated) {
|
||||
// empty
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,8 +42,8 @@ import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public class PyUtil {
|
||||
private PyUtil() {
|
||||
}
|
||||
private PyUtil() {
|
||||
}
|
||||
|
||||
public static void ensureWritable(PsiElement element) {
|
||||
PsiDocumentManager docmgr = PsiDocumentManager.getInstance(
|
||||
@@ -358,4 +358,31 @@ public class PyUtil {
|
||||
point, Balloon.Position.above
|
||||
);
|
||||
}
|
||||
|
||||
@NonNls
|
||||
/**
|
||||
* Returns a quoted string representation, or "null".
|
||||
*/
|
||||
public static String nvl(Object s) {
|
||||
if (s != null) {
|
||||
return "'" + s.toString() + "'";
|
||||
}
|
||||
else {
|
||||
return "null";
|
||||
}
|
||||
}
|
||||
|
||||
public static void addListNode(PsiElement target, PsiElement source, ASTNode beforeThis, boolean isFirst, boolean isLast) {
|
||||
ensureWritable(target);
|
||||
ASTNode node = target.getNode();
|
||||
assert node != null;
|
||||
ASTNode itemNode = source.getNode();
|
||||
assert itemNode != null;
|
||||
Project project = target.getProject();
|
||||
PyElementGenerator gen = PythonLanguage.getInstance().getElementGenerator();
|
||||
if (! isFirst) node.addChild(gen.createComma(project), beforeThis);
|
||||
node.addChild(itemNode, beforeThis);
|
||||
if (! isLast) node.addChild(gen.createComma(project), beforeThis);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -6,6 +6,6 @@ import com.intellij.psi.scope.PsiScopeProcessor;
|
||||
/**
|
||||
* @author yole
|
||||
*/
|
||||
public interface PyScopeProcessor extends PsiScopeProcessor {
|
||||
public interface PyAsScopeProcessor extends PsiScopeProcessor {
|
||||
boolean execute(PsiElement element, String asName);
|
||||
}
|
||||
@@ -27,6 +27,8 @@ import com.jetbrains.python.PyElementTypes;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.PyTokenTypes;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil;
|
||||
import com.jetbrains.python.psi.resolve.VariantsProcessor;
|
||||
import com.jetbrains.python.psi.stubs.PyClassStub;
|
||||
import com.jetbrains.python.psi.stubs.PyFunctionStub;
|
||||
import com.jetbrains.python.validation.DocStringAnnotator;
|
||||
@@ -326,7 +328,7 @@ public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implement
|
||||
if (!processor.execute(expr, substitutor)) return false;
|
||||
}
|
||||
//
|
||||
if (processor instanceof PyResolveUtil.VariantsProcessor) {
|
||||
if (processor instanceof VariantsProcessor) {
|
||||
return true;
|
||||
}
|
||||
return processor.execute(this, substitutor);
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.jetbrains.python.psi.impl;
|
||||
|
||||
import com.intellij.extapi.psi.PsiFileBase;
|
||||
import com.intellij.openapi.fileTypes.FileType;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.FileViewProvider;
|
||||
import com.intellij.psi.PsiElement;
|
||||
@@ -31,6 +32,8 @@ import com.jetbrains.python.PyElementTypes;
|
||||
import com.jetbrains.python.PythonFileType;
|
||||
import com.jetbrains.python.PythonLanguage;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil;
|
||||
import com.jetbrains.python.psi.resolve.ResolveProcessor;
|
||||
import com.jetbrains.python.psi.types.PyClassType;
|
||||
import com.jetbrains.python.psi.types.PyType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -151,6 +154,13 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression {
|
||||
return getTopLevelItems(PyElementTypes.TARGET_EXPRESSION, PyTargetExpression.class);
|
||||
}
|
||||
|
||||
public PsiElement findExportedName(String name) {
|
||||
// dull plain resolve, as fast as stub index or better
|
||||
ResolveProcessor proc = new ResolveProcessor(name);
|
||||
PyResolveUtil.treeCrawlUp(proc, true, getLastChild());
|
||||
return proc.getResult();
|
||||
}
|
||||
|
||||
public List<PyExpression> getImportTargets() {
|
||||
List<PyExpression> ret = new ArrayList<PyExpression>();
|
||||
List<PyImportStatement> imports = getTopLevelItems(PyElementTypes.IMPORT_STATEMENT, PyImportStatement.class);
|
||||
|
||||
@@ -25,6 +25,8 @@ import com.intellij.psi.scope.PsiScopeProcessor;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.jetbrains.python.PyElementTypes;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.resolve.PyAsScopeProcessor;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -54,6 +56,16 @@ public class PyImportElementImpl extends PyElementImpl implements PyImportElemen
|
||||
return (PyTargetExpression)asNameNode.getPsi();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getVisibleName() {
|
||||
PyTargetExpression asname = getAsName();
|
||||
if (asname != null) return asname.getName();
|
||||
for (PyElement name_elt : iterateNames()) {
|
||||
return name_elt.getName(); // first to come must be right
|
||||
}
|
||||
return null; // we might have not found any names
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processDeclarations(@NotNull final PsiScopeProcessor processor, @NotNull final ResolveState state, final PsiElement lastParent,
|
||||
@NotNull final PsiElement place) {
|
||||
@@ -65,10 +77,10 @@ public class PyImportElementImpl extends PyElementImpl implements PyImportElemen
|
||||
if (importRef != null) {
|
||||
final PsiElement element = importRef.resolve();
|
||||
if (element != null) {
|
||||
if (processor instanceof PyScopeProcessor) {
|
||||
if (processor instanceof PyAsScopeProcessor) {
|
||||
PyTargetExpression asName = getAsName();
|
||||
if (asName != null) {
|
||||
return ((PyScopeProcessor) processor).execute(element, asName.getText()); // might resolve to asName to show the source of name
|
||||
return ((PyAsScopeProcessor) processor).execute(element, asName.getText()); // might resolve to asName to show the source of name
|
||||
}
|
||||
// maybe the incoming name is qualified
|
||||
PyReferenceExpression place_ref = PsiTreeUtil.getChildOfType(place, PyReferenceExpression.class);
|
||||
|
||||
@@ -102,6 +102,19 @@ public class PyParameterImpl extends PyPresentableElementImpl<PyParameterStub> i
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getRepr(boolean includeDefaultValue) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
if (isPositionalContainer()) sb.append("*");
|
||||
else if (isKeywordContainer()) sb.append("**");
|
||||
sb.append(getName());
|
||||
if (includeDefaultValue) {
|
||||
PyExpression default_v = getDefaultValue();
|
||||
if (default_v != null) sb.append("=").append(PyUtil.getReadableRepr(default_v, true));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public Icon getIcon(final int flags) {
|
||||
return Icons.PARAMETER_ICON;
|
||||
}
|
||||
|
||||
@@ -54,21 +54,7 @@ public class PyParameterListImpl extends PyBaseElementImpl<PyParameterListStub>
|
||||
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);
|
||||
PyUtil.addListNode(this, param, beforeWhat, true, params.length == 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import com.jetbrains.python.PyIcons;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.PyTokenTypes;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.resolve.*;
|
||||
import com.jetbrains.python.psi.types.PyClassType;
|
||||
import com.jetbrains.python.psi.types.PyModuleType;
|
||||
import com.jetbrains.python.psi.types.PyNoneType;
|
||||
@@ -204,7 +205,7 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
|
||||
|
||||
// here we have an unqualified expr. it may be defined:
|
||||
// ...in current file
|
||||
PyResolveUtil.ResolveProcessor processor = new PyResolveUtil.ResolveProcessor(referencedName);
|
||||
ResolveProcessor processor = new ResolveProcessor(referencedName);
|
||||
PsiElement uexpr = PyResolveUtil.treeCrawlUp(processor, this);
|
||||
if ((uexpr != null)) {
|
||||
if ((uexpr instanceof PyClass)) {
|
||||
@@ -230,7 +231,7 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
|
||||
if (uexpr == null) {
|
||||
// ...as a builtin symbol
|
||||
PyFile bfile = PyBuiltinCache.getInstance(getProject()).getBuiltinsFile();
|
||||
uexpr = PyResolveUtil.treeCrawlUp(new PyResolveUtil.ResolveProcessor(referencedName), true, bfile);
|
||||
uexpr = PyResolveUtil.treeCrawlUp(new ResolveProcessor(referencedName), true, bfile);
|
||||
}
|
||||
if (uexpr == null) {
|
||||
uexpr = PyResolveUtil.resolveOffContext(this);
|
||||
@@ -251,8 +252,7 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
|
||||
private static Collection<PyExpression> collectAssignedAttributes(PyQualifiedExpression qualifier) {
|
||||
List<PyQualifiedExpression> qualifier_path = PyResolveUtil.unwindQualifiers(qualifier);
|
||||
if (qualifier_path != null) {
|
||||
PyResolveUtil.AssignmentCollectProcessor<PyQualifiedExpression> proc =
|
||||
new PyResolveUtil.AssignmentCollectProcessor<PyQualifiedExpression>(qualifier_path)
|
||||
AssignmentCollectProcessor proc = new AssignmentCollectProcessor(qualifier_path)
|
||||
;
|
||||
PyResolveUtil.treeCrawlUp(proc, qualifier);
|
||||
return proc.getResult();
|
||||
@@ -391,14 +391,14 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere
|
||||
}
|
||||
|
||||
// include our own names
|
||||
final PyResolveUtil.VariantsProcessor processor = new PyResolveUtil.VariantsProcessor();
|
||||
final VariantsProcessor processor = new VariantsProcessor();
|
||||
PyResolveUtil.treeCrawlUp(processor, this); // names from here
|
||||
// scan all "import *" and include names provided by them
|
||||
PyResolveUtil.CollectProcessor<PyStarImportElement> collect_proc;
|
||||
collect_proc = new PyResolveUtil.CollectProcessor<PyStarImportElement>(PyStarImportElement.class);
|
||||
CollectProcessor collect_proc;
|
||||
collect_proc = new CollectProcessor(PyStarImportElement.class);
|
||||
PyResolveUtil.treeCrawlUp(collect_proc, this);
|
||||
List<PyStarImportElement> stars = collect_proc.getResult();
|
||||
for (PyStarImportElement star_elt : stars) {
|
||||
List<PsiElement> stars = collect_proc.getResult();
|
||||
for (PsiElement star_elt : stars) {
|
||||
final PyFromImportStatement from_import_stmt = (PyFromImportStatement)star_elt.getParent();
|
||||
if (from_import_stmt != null) {
|
||||
final PyReferenceExpression import_src = from_import_stmt.getImportSource();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.jetbrains.python.psi.impl;
|
||||
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.lang.ASTNode;
|
||||
|
||||
@@ -13,6 +13,9 @@ import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.containers.HashSet;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil;
|
||||
import com.jetbrains.python.psi.resolve.ResolveProcessor;
|
||||
import com.jetbrains.python.psi.resolve.VariantsProcessor;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -370,13 +373,13 @@ public class ResolveImportUtil {
|
||||
public static PsiElement resolveChild(final PsiElement parent, final String referencedName, final PsiFile containingFile, boolean fileOnly) {
|
||||
PsiDirectory dir = null;
|
||||
PsiElement ret = null;
|
||||
PyResolveUtil.ResolveProcessor processor = null;
|
||||
ResolveProcessor processor = null;
|
||||
if (parent instanceof PyFile) {
|
||||
boolean is_dir = (parent.getCopyableUserData(PyFile.KEY_IS_DIRECTORY) == Boolean.TRUE);
|
||||
PyFile pfparent = (PyFile)parent;
|
||||
if (! is_dir) {
|
||||
// look for name in the file:
|
||||
processor = new PyResolveUtil.ResolveProcessor(referencedName);
|
||||
processor = new ResolveProcessor(referencedName);
|
||||
//ret = PyResolveUtil.treeWalkUp(processor, parent, null, importRef);
|
||||
ret = PyResolveUtil.treeCrawlUp(processor, true, parent);
|
||||
if (ret != null) return ret;
|
||||
@@ -407,8 +410,7 @@ public class ResolveImportUtil {
|
||||
@Nullable
|
||||
private static PsiElement resolveInDirectory(final String referencedName,
|
||||
final PsiFile containingFile,
|
||||
final PsiDirectory dir,
|
||||
PyResolveUtil.ResolveProcessor processor) {
|
||||
final PsiDirectory dir, ResolveProcessor processor) {
|
||||
final PsiFile file = dir.findFile(referencedName + PY_SUFFIX);
|
||||
if (file != null) return file;
|
||||
final PsiDirectory subdir = dir.findSubdirectory(referencedName);
|
||||
@@ -417,7 +419,7 @@ public class ResolveImportUtil {
|
||||
final PsiFile initPy = dir.findFile(INIT_PY);
|
||||
if (initPy == containingFile) return null; // don't dive into the file we're in
|
||||
if (initPy != null) {
|
||||
if (processor == null) processor = new PyResolveUtil.ResolveProcessor(referencedName); // should not normally happen
|
||||
if (processor == null) processor = new ResolveProcessor(referencedName); // should not normally happen
|
||||
return PyResolveUtil.treeCrawlUp(processor, true, initPy);//PyResolveUtil.treeWalkUp(processor, initPy, null, importRef);
|
||||
}
|
||||
}
|
||||
@@ -440,7 +442,7 @@ public class ResolveImportUtil {
|
||||
if (src != null) {
|
||||
PsiElement mod = src.resolve();
|
||||
if (mod != null) {
|
||||
final PyResolveUtil.VariantsProcessor processor = new PyResolveUtil.VariantsProcessor();
|
||||
final VariantsProcessor processor = new VariantsProcessor();
|
||||
PyResolveUtil.treeCrawlUp(processor, true, mod);
|
||||
/*
|
||||
for (LookupElement le : processor.getResult()) {
|
||||
@@ -492,7 +494,7 @@ public class ResolveImportUtil {
|
||||
variants.addAll(visitor.getResult());
|
||||
}
|
||||
|
||||
return variants.toArray(new String[variants.size()]);
|
||||
return variants.toArray(new Object[variants.size()]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.jetbrains.python.psi.resolve;
|
||||
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.ResolveState;
|
||||
import com.intellij.psi.scope.PsiScopeProcessor;
|
||||
import com.jetbrains.python.psi.PyAssignmentStatement;
|
||||
import com.jetbrains.python.psi.PyExpression;
|
||||
import com.jetbrains.python.psi.PyTargetExpression;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class AssignmentCollectProcessor implements PsiScopeProcessor {
|
||||
/**
|
||||
* Collects all assignments in context above given element, if they match given naming pattern.
|
||||
* Used to track creation of attributes by assignment (e.g in constructor).
|
||||
*/
|
||||
List<? extends PyExpression> my_qualifier;
|
||||
List<PyExpression> my_result;
|
||||
Set<String> my_seen_names;
|
||||
|
||||
/**
|
||||
* Creates an instance to collect assignments of attributes to the object identified by 'qualifier'.
|
||||
* E.g. if qualifier = {"foo", "bar"} then assignments like "foo.bar.baz = ..." will be considered.
|
||||
* The collection continues up to the point of latest redefinition of the object identified by 'qualifier',
|
||||
* that is, up to the point of something like "foo.bar = ..." or "foo = ...".
|
||||
*
|
||||
* @param qualifier qualifying names, outermost first; must not be empty.
|
||||
*/
|
||||
public AssignmentCollectProcessor(@NotNull List<? extends PyExpression> qualifier) {
|
||||
assert qualifier.size() > 0;
|
||||
my_qualifier = qualifier;
|
||||
my_result = new ArrayList<PyExpression>();
|
||||
my_seen_names = new HashSet<String>();
|
||||
}
|
||||
|
||||
public boolean execute(final PsiElement element, final ResolveState state) {
|
||||
if (element instanceof PyAssignmentStatement) {
|
||||
final PyAssignmentStatement assignment = (PyAssignmentStatement)element;
|
||||
for (PyExpression ex : assignment.getTargets()) {
|
||||
if (ex instanceof PyTargetExpression) {
|
||||
final PyTargetExpression target = (PyTargetExpression)ex;
|
||||
List<PyTargetExpression> quals = PyResolveUtil.unwindQualifiers(target);
|
||||
if (quals != null) {
|
||||
if (quals.size() == my_qualifier.size() + 1 && PyResolveUtil.pathsMatch(quals, my_qualifier)) {
|
||||
// a new attribute follows last qualifier; collect it.
|
||||
PyTargetExpression last_elt = quals.get(quals.size() - 1); // last item is the outermost, new, attribute.
|
||||
String last_elt_name = last_elt.getName();
|
||||
if (!my_seen_names.contains(last_elt_name)) { // no dupes, only remember the latest
|
||||
my_result.add(last_elt);
|
||||
my_seen_names.add(last_elt_name);
|
||||
}
|
||||
}
|
||||
else if (quals.size() < my_qualifier.size() + 1 && PyResolveUtil.pathsMatch(my_qualifier, quals)) {
|
||||
// qualifier(s) get redefined; collect no more.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return true; // nothing interesting found, continue
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a collection of exressions (parts of assignment expressions) where new attributes were defined. E.g. for "a.b.c = 1",
|
||||
* the expression for 'c' is in the result.
|
||||
*/
|
||||
@NotNull
|
||||
public Collection<PyExpression> getResult() {
|
||||
return my_result;
|
||||
}
|
||||
|
||||
public <T> T getHint(final Class<T> hintClass) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void handleEvent(final Event event, final Object associated) {
|
||||
// empty
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.jetbrains.python.psi.resolve;
|
||||
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.ResolveState;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CollectProcessor implements PyClassScopeProcessor {
|
||||
|
||||
Class<? extends PsiElement>[] my_collectables;
|
||||
List<PsiElement> my_result;
|
||||
|
||||
public CollectProcessor(Class<? extends PsiElement>... collectables) {
|
||||
my_collectables = collectables;
|
||||
my_result = new ArrayList<PsiElement>();
|
||||
}
|
||||
|
||||
public boolean execute(final PsiElement element, final ResolveState state) {
|
||||
for (Class cls : my_collectables) {
|
||||
if (cls.isInstance(element)) {
|
||||
my_result.add(element);
|
||||
}
|
||||
}
|
||||
return true; // collect till we drop
|
||||
}
|
||||
|
||||
public <T> T getHint(final Class<T> hintClass) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void handleEvent(final Event event, final Object associated) {
|
||||
}
|
||||
|
||||
public List<PsiElement> getResult() {
|
||||
return my_result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Class[] getPossibleTargets() {
|
||||
return my_collectables;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.jetbrains.python.psi.resolve;
|
||||
|
||||
import com.intellij.psi.scope.PsiScopeProcessor;
|
||||
import com.intellij.psi.*;
|
||||
import com.jetbrains.python.psi.PyReferenceExpression;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class MultiResolveProcessor implements PsiScopeProcessor {
|
||||
private final String _name;
|
||||
private final List<ResolveResult> _results = new ArrayList<ResolveResult>();
|
||||
|
||||
public MultiResolveProcessor(String name) {
|
||||
_name = name;
|
||||
}
|
||||
|
||||
public ResolveResult[] getResults() {
|
||||
return _results.toArray(new ResolveResult[_results.size()]);
|
||||
}
|
||||
|
||||
public boolean execute(PsiElement element, ResolveState substitutor) {
|
||||
if (element instanceof PsiNamedElement) {
|
||||
if (_name.equals(((PsiNamedElement)element).getName())) {
|
||||
_results.add(new PsiElementResolveResult(element));
|
||||
}
|
||||
}
|
||||
else if (element instanceof PyReferenceExpression) {
|
||||
PyReferenceExpression expr = (PyReferenceExpression)element;
|
||||
String referencedName = expr.getReferencedName();
|
||||
if (referencedName != null && referencedName.equals(_name)) {
|
||||
_results.add(new PsiElementResolveResult(element));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public <T> T getHint(Class<T> hintClass) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void handleEvent(Event event, Object associated) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.jetbrains.python.psi.resolve;
|
||||
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.scope.PsiScopeProcessor;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
*/
|
||||
public interface PyAsScopeProcessor extends PsiScopeProcessor {
|
||||
boolean execute(PsiElement element, String asName);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.jetbrains.python.psi.resolve;
|
||||
|
||||
import com.intellij.psi.scope.PsiScopeProcessor;
|
||||
import com.jetbrains.python.psi.NameDefiner;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Processor capable of giving multiple hints on what it's looking for.
|
||||
* User: dcheryasov
|
||||
* Date: Apr 19, 2009
|
||||
*/
|
||||
public interface PyClassScopeProcessor extends PsiScopeProcessor {
|
||||
/**
|
||||
* @return classes of nodes that might be interesting for the processor.
|
||||
* ??? Instances of NameDefiner are always considered interesting.
|
||||
* ??? An empty list makes processor see only NameDefiners.
|
||||
* @see com.jetbrains.python.psi.NameDefiner
|
||||
*/
|
||||
@NotNull
|
||||
Class[] getPossibleTargets();
|
||||
|
||||
Class[] NAME_DEFINER_ONLY = {NameDefiner.class};
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
* Copyright 2005 Pythonid Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS"; BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.jetbrains.python.psi.resolve;
|
||||
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.ResolveState;
|
||||
import com.intellij.psi.scope.PsiScopeProcessor;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Ref resolution routines.
|
||||
* User: yole
|
||||
* Date: 14.06.2005
|
||||
*/
|
||||
public class PyResolveUtil {
|
||||
|
||||
private PyResolveUtil() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tries to find nearest parent that conceals nams definded inside it. Such elements are 'class' and 'def':
|
||||
* anything defined within it does not seep to the namespace below them, but is concealed within.
|
||||
* @param elt starting point of search.
|
||||
* @return 'class' or 'def' element, or null if not found.
|
||||
*/
|
||||
@Nullable
|
||||
public static PsiElement getConcealingParent(PsiElement elt) {
|
||||
return PsiTreeUtil.getParentOfType(elt, PyClass.class, PyFunction.class);
|
||||
}
|
||||
|
||||
protected static PsiElement getInnermostChildOf(PsiElement elt) {
|
||||
PsiElement feeler = elt;
|
||||
PsiElement seeker;
|
||||
seeker = feeler;
|
||||
// find innermost last child of the subtree we're in
|
||||
while (feeler != null) {
|
||||
seeker = feeler;
|
||||
feeler = feeler.getLastChild();
|
||||
}
|
||||
return seeker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns closest previous node of given class, as input file would have it.
|
||||
* @param elt node from which to look for a previous atatement.
|
||||
* @param classes which class of the previous nodes to find.
|
||||
* @return previous statement, or null.
|
||||
*/
|
||||
@Nullable
|
||||
public static PsiElement getPrevNodeOf(PsiElement elt, Class... classes) {
|
||||
PsiElement seeker = elt;
|
||||
while (seeker != null) {
|
||||
PsiElement feeler = seeker.getPrevSibling();
|
||||
if (feeler != null) {
|
||||
seeker = getInnermostChildOf(feeler);
|
||||
}
|
||||
else { // we were the first subnode
|
||||
// find something above the parent node we've not exhausted yet
|
||||
seeker = seeker.getParent();
|
||||
if (seeker instanceof PyFile) return null; // all file nodes have been looked up, in vain
|
||||
}
|
||||
// ??? if (seeker instanceof NameDefiner) return seeker;
|
||||
for (Class cls : classes) {
|
||||
if (cls.isInstance(seeker)) return seeker;
|
||||
}
|
||||
}
|
||||
// here elt is null or a PsiFile is not up in the parent chain.
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PsiElement getPrevNodeOf(PsiElement elt, PsiScopeProcessor proc) {
|
||||
if (proc instanceof PyClassScopeProcessor) {
|
||||
return getPrevNodeOf(elt, ((PyClassScopeProcessor)proc).getPossibleTargets());
|
||||
}
|
||||
else return getPrevNodeOf(elt, PyClassScopeProcessor.NAME_DEFINER_ONLY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crawls up the PSI tree, checking nodes as if crawling backwards through source lexemes.
|
||||
* @param processor a visitor that says when the crawl is done and collects info.
|
||||
* @param elt element from which we start (not checked by processor); if null, the search immediately returns null.
|
||||
* @param roof if not null, search continues only below the roof and including it.
|
||||
* @param fromunder if true, begin search not above elt, but from a [possibly imaginary] node right below elt; so elt gets analyzed, too.
|
||||
* @return first element that the processor accepted.
|
||||
*/
|
||||
@Nullable
|
||||
public static PsiElement treeCrawlUp(PsiScopeProcessor processor, boolean fromunder, PsiElement elt, PsiElement roof) {
|
||||
if (elt == null) return null; // can't find anyway.
|
||||
PsiElement seeker = elt;
|
||||
PsiElement cap = getConcealingParent(elt);
|
||||
do {
|
||||
ProgressManager.getInstance().checkCanceled();
|
||||
if (fromunder) {
|
||||
fromunder = false; // only honour fromunder once per call
|
||||
seeker = getPrevNodeOf(getInnermostChildOf(seeker), processor);
|
||||
}
|
||||
else { // main case
|
||||
seeker = getPrevNodeOf(seeker, processor);
|
||||
}
|
||||
// aren't we in the same defining assignment, global, etc?
|
||||
if ((seeker instanceof NameDefiner) && ((NameDefiner)seeker).mustResolveOutside() && PsiTreeUtil.isAncestor(seeker, elt, true)) {
|
||||
seeker = getPrevNodeOf(seeker, processor);
|
||||
}
|
||||
// maybe we're under a cap?
|
||||
while (true) {
|
||||
PsiElement local_cap = getConcealingParent(seeker);
|
||||
if (local_cap == null) break; // seeker is in global context
|
||||
if (local_cap == cap) break; // seeker is in the same context as elt
|
||||
if ((cap != null) && PsiTreeUtil.isAncestor(local_cap, cap, true)) break; // seeker is in a context above elt's
|
||||
if (
|
||||
(local_cap != elt) && // elt isn't the cap of seeker itself
|
||||
((cap == null) || !PsiTreeUtil.isAncestor(local_cap, cap, true)) // elt's cap is not under local cap
|
||||
) { // only look at local cap and above
|
||||
if (local_cap instanceof NameDefiner) seeker = local_cap;
|
||||
else seeker = getPrevNodeOf(local_cap, processor);
|
||||
}
|
||||
else break; // seeker is contextually under elt already
|
||||
}
|
||||
// are we still under the roof?
|
||||
if ((roof != null) && (seeker != null) && ! PsiTreeUtil.isAncestor(roof, seeker, false)) return null;
|
||||
// maybe we're capped by a class?
|
||||
if (refersFromMethodToClass(cap, seeker)) continue;
|
||||
// check what we got
|
||||
if (seeker != null) {
|
||||
if (!processor.execute(seeker, ResolveState.initial())) {
|
||||
if (processor instanceof ResolveProcessor) {
|
||||
return ((ResolveProcessor)processor).getResult();
|
||||
}
|
||||
else return seeker; // can't point to exact element, but somewhere here
|
||||
}
|
||||
}
|
||||
} while (seeker != null);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PsiElement resolveOffContext(@NotNull PyReferenceExpression refex) {
|
||||
// if we're under a cap, an external object that we want to use might be also defined below us.
|
||||
// look through all contexts, closest first.
|
||||
PsiElement ret = null;
|
||||
PsiElement our_cap = getConcealingParent(refex);
|
||||
ResolveProcessor proc = new ResolveProcessor(refex.getReferencedName()); // processor reusable till first hit
|
||||
if (our_cap != null) {
|
||||
PsiElement cap = our_cap;
|
||||
while (true) {
|
||||
cap = getConcealingParent(cap);
|
||||
if (cap == null) cap = refex.getContainingFile();
|
||||
ret = treeCrawlUp(proc, true, cap);
|
||||
if ((ret != null) && !PsiTreeUtil.isAncestor(our_cap, ret, true)) { // found something and it is below our cap
|
||||
// maybe we're in a method, and what we found is in its class context?
|
||||
if (! refersFromMethodToClass(our_cap, ret)) {
|
||||
break; // not in method -> must be all right
|
||||
}
|
||||
}
|
||||
if (cap instanceof PsiFile) break; // file level, can't try more
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param inner an element presumably inside a method within a class, or a method itself.
|
||||
* @param outer an element presumably in the class context.
|
||||
* @return true if an outer element is in a class context, while the inner is a method or function inside it.
|
||||
* @see PyResolveUtil#getConcealingParent(com.intellij.psi.PsiElement)
|
||||
*/
|
||||
protected static boolean refersFromMethodToClass(final PsiElement inner, final PsiElement outer) {
|
||||
return (
|
||||
(getConcealingParent(outer) instanceof PyClass) && // outer is in a class context
|
||||
(PsiTreeUtil.getParentOfType(inner, PyFunction.class, false) != null) // inner is a function or method within the class
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crawls up the PSI tree, checking nodes as if crawling backwards through source lexemes.
|
||||
* @param processor a visitor that says when the crawl is done and collects info.
|
||||
* @param fromunder if true, search not above elt, but from a [possibly imaginary] node right below elt; so elt gets analyzed, too.
|
||||
* @param elt element from which we start (not checked by processor); if null, the search immediately fails.
|
||||
* @return first element that the processor accepted.
|
||||
*/
|
||||
@Nullable
|
||||
public static PsiElement treeCrawlUp(PsiScopeProcessor processor, boolean fromunder, PsiElement elt) {
|
||||
return treeCrawlUp(processor, fromunder, elt, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns treeCrawlUp(processor, elt, false). A convenience method.
|
||||
* @see PyResolveUtil#treeCrawlUp(com.intellij.psi.scope.PsiScopeProcessor,boolean,com.intellij.psi.PsiElement)
|
||||
*/
|
||||
@Nullable
|
||||
public static PsiElement treeCrawlUp(PsiScopeProcessor processor, PsiElement elt) {
|
||||
return treeCrawlUp(processor, false, elt);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tries to match two [qualified] reference expression paths by names; target must be a 'sublist' of source to match.
|
||||
* E.g., 'a.b.c.d' and 'a.b.c' would match, while 'a.b.c' and 'a.b.c.d' would not. Eqaully, 'a.b.c' and 'a.b.d' would not match.
|
||||
* If either source or target is null, false is returned.
|
||||
* @see #unwindQualifiers(PyQualifiedExpression) .
|
||||
* @param source_path expression path to match (the longer list of qualifiers).
|
||||
* @param target_path expression path to match against (hopeful sublist of qualifiers of source).
|
||||
* @return true if source matches target.
|
||||
*/
|
||||
public static <S extends PyExpression, T extends PyExpression> boolean pathsMatch(List<S> source_path, List<T> target_path) {
|
||||
// turn qualifiers into lists
|
||||
if ((source_path == null) || (target_path == null)) return false;
|
||||
// compare until target is exhausted
|
||||
Iterator<S> source_iter = source_path.iterator();
|
||||
for (final T target_elt : target_path) {
|
||||
if (source_iter.hasNext()) {
|
||||
S source_elt = source_iter.next();
|
||||
if (!target_elt.getText().equals(source_elt.getText())) return false;
|
||||
}
|
||||
else return false; // source exhausted before target
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwinds a [multi-level] qualified expression into a path, as seen in source text, i.e. outermost qualifier first.
|
||||
* If any qualifier happens to be not a PyQualifiedExpression, or expr is null, null is returned.
|
||||
* @param expr an experssion to unwind.
|
||||
* @return path as a list of ref expressions, or null.
|
||||
*/
|
||||
@Nullable
|
||||
public static <T extends PyQualifiedExpression> List<T> unwindQualifiers(final T expr) {
|
||||
final List<T> path = new LinkedList<T>();
|
||||
PyExpression maybe_step;
|
||||
T step = expr;
|
||||
try {
|
||||
while (step != null) {
|
||||
path.add(0, step);
|
||||
maybe_step = step.getQualifier();
|
||||
step = (T)maybe_step;
|
||||
}
|
||||
}
|
||||
catch (ClassCastException e) {
|
||||
return null;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
public static String toPath(PyQualifiedExpression expr, String separator) {
|
||||
if (expr == null) return "";
|
||||
List<PyQualifiedExpression> path = unwindQualifiers(expr);
|
||||
if (path != null) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
boolean is_not_first = false;
|
||||
for (PyQualifiedExpression ex : path) {
|
||||
if (is_not_first) buf.append(separator);
|
||||
else is_not_first = true;
|
||||
buf.append(ex.getName());
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
else {
|
||||
String s = expr.getName();
|
||||
return s != null? s : "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple interface allowing to filter processor results.
|
||||
*/
|
||||
public interface Filter {
|
||||
/**
|
||||
* @param target the object a processor is currently looking at.
|
||||
* @return true if the object is acceptable as a processor result.
|
||||
*/
|
||||
boolean accept(Object target);
|
||||
}
|
||||
|
||||
public static class FilterNotInstance implements Filter {
|
||||
Object instance;
|
||||
|
||||
public FilterNotInstance(Object instance) {
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
public boolean accept(final Object target) {
|
||||
return (instance != target);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package com.jetbrains.python.psi.resolve;
|
||||
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiNamedElement;
|
||||
import com.intellij.psi.ResolveState;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.jetbrains.python.psi.NameDefiner;
|
||||
import com.jetbrains.python.psi.PyFile;
|
||||
import com.jetbrains.python.psi.PyReferenceExpression;
|
||||
import com.jetbrains.python.psi.PyUtil;
|
||||
import com.jetbrains.python.psi.impl.ResolveImportUtil;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ResolveProcessor implements PyAsScopeProcessor {
|
||||
private final String myName;
|
||||
private PsiElement myResult = null;
|
||||
private final List<NameDefiner> myDefiners;
|
||||
|
||||
public ResolveProcessor(final String name) {
|
||||
myName = name;
|
||||
myDefiners = new ArrayList<NameDefiner>(2); // 1 is typical, 2 is sometimes, more is rare.
|
||||
}
|
||||
|
||||
public PsiElement getResult() {
|
||||
return myResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a NameDefiner point which is a secondary resolution target. E.g. import statement for imported name.
|
||||
*
|
||||
* @param definer
|
||||
*/
|
||||
protected void addNameDefiner(NameDefiner definer) {
|
||||
myDefiners.add(definer);
|
||||
}
|
||||
|
||||
public List<NameDefiner> getDefiners() {
|
||||
return myDefiners;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return PyUtil.nvl(myName) + ", " + PyUtil.nvl(myResult);
|
||||
}
|
||||
|
||||
public boolean execute(PsiElement element, ResolveState substitutor) {
|
||||
if (element instanceof PyFile) {
|
||||
final VirtualFile file = ((PyFile)element).getVirtualFile();
|
||||
if (file != null) {
|
||||
if (myName.equals(file.getNameWithoutExtension())) {
|
||||
myResult = element;
|
||||
return false;
|
||||
}
|
||||
else if (ResolveImportUtil.INIT_PY.equals(file.getName())) {
|
||||
VirtualFile dir = file.getParent();
|
||||
if ((dir != null) && myName.equals(dir.getName())) {
|
||||
myResult = element;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (element instanceof PsiNamedElement) {
|
||||
if (myName.equals(((PsiNamedElement)element).getName())) {
|
||||
myResult = element;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (element instanceof PyReferenceExpression) {
|
||||
PyReferenceExpression expr = (PyReferenceExpression)element;
|
||||
String referencedName = expr.getReferencedName();
|
||||
if (referencedName != null && referencedName.equals(myName)) {
|
||||
myResult = element;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (element instanceof NameDefiner) {
|
||||
final NameDefiner definer = (NameDefiner)element;
|
||||
PsiElement by_name = definer.getElementNamed(myName);
|
||||
if (by_name != null) {
|
||||
myResult = by_name;
|
||||
if (!PsiTreeUtil.isAncestor(element, by_name, true)) { // non-trivial definer
|
||||
addNameDefiner(definer);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean execute(final PsiElement element, final String asName) {
|
||||
if (asName.equals(myName)) {
|
||||
myResult = element;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public <T> T getHint(Class<T> hintClass) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void handleEvent(Event event, Object associated) {
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.jetbrains.python.psi.resolve;
|
||||
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.codeInsight.lookup.LookupElementFactory;
|
||||
import com.intellij.codeInsight.lookup.LookupItem;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiNamedElement;
|
||||
import com.intellij.psi.ResolveState;
|
||||
import com.intellij.psi.scope.PsiScopeProcessor;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class VariantsProcessor implements PsiScopeProcessor {
|
||||
private final Map<String, LookupElement> myVariants = new HashMap<String, LookupElement>();
|
||||
|
||||
protected String my_notice;
|
||||
|
||||
public VariantsProcessor() {
|
||||
// empty
|
||||
}
|
||||
|
||||
public VariantsProcessor(final PyResolveUtil.Filter filter) {
|
||||
my_filter = filter;
|
||||
}
|
||||
|
||||
protected PyResolveUtil.Filter my_filter;
|
||||
|
||||
public void setNotice(@Nullable String notice) {
|
||||
my_notice = notice;
|
||||
}
|
||||
|
||||
protected void setupItem(LookupItem item) {
|
||||
if (my_notice != null) {
|
||||
setItemNotice(item, my_notice);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void setItemNotice(final LookupItem item, String notice) {
|
||||
item.setAttribute(item.TAIL_TEXT_ATTR, notice);
|
||||
item.setAttribute(item.TAIL_TEXT_SMALL_ATTR, "");
|
||||
}
|
||||
|
||||
public LookupElement[] getResult() {
|
||||
final Collection<LookupElement> variants = myVariants.values();
|
||||
return variants.toArray(new LookupElement[variants.size()]);
|
||||
}
|
||||
|
||||
public List<LookupElement> getResultList() {
|
||||
return new ArrayList<LookupElement>(myVariants.values());
|
||||
}
|
||||
|
||||
public boolean execute(PsiElement element, ResolveState substitutor) {
|
||||
if (my_filter != null && !my_filter.accept(element)) return true; // skip whatever the filter rejects
|
||||
// TODO: refactor to look saner; much code duplication
|
||||
if (element instanceof PsiNamedElement) {
|
||||
final PsiNamedElement psiNamedElement = (PsiNamedElement)element;
|
||||
final String name = psiNamedElement.getName();
|
||||
if (!myVariants.containsKey(name)) {
|
||||
final LookupItem lookup_item = (LookupItem)LookupElementFactory.getInstance().createLookupElement(psiNamedElement);
|
||||
setupItem(lookup_item);
|
||||
myVariants.put(name, lookup_item);
|
||||
}
|
||||
}
|
||||
else if (element instanceof PyReferenceExpression) {
|
||||
PyReferenceExpression expr = (PyReferenceExpression)element;
|
||||
String referencedName = expr.getReferencedName();
|
||||
if (referencedName != null && !myVariants.containsKey(referencedName)) {
|
||||
final LookupItem lookup_item = (LookupItem)LookupElementFactory.getInstance().createLookupElement(referencedName);
|
||||
setupItem(lookup_item);
|
||||
myVariants.put(referencedName, lookup_item);
|
||||
}
|
||||
}
|
||||
else if (element instanceof NameDefiner) {
|
||||
final NameDefiner definer = (NameDefiner)element;
|
||||
for (PyElement expr : definer.iterateNames()) {
|
||||
if (expr != null) { // NOTE: maybe rather have SingleIterables skip nulls outright?
|
||||
String referencedName = expr.getName();
|
||||
if (referencedName != null && !myVariants.containsKey(referencedName)) {
|
||||
final LookupItem lookup_item = (LookupItem)LookupElementFactory.getInstance().createLookupElement(referencedName);
|
||||
setupItem(lookup_item);
|
||||
if (definer instanceof PyImportElement) { // set notice to imported module name if needed
|
||||
PsiElement maybe_from_import = definer.getParent();
|
||||
if (maybe_from_import instanceof PyFromImportStatement) {
|
||||
final PyFromImportStatement from_import = (PyFromImportStatement)maybe_from_import;
|
||||
PyReferenceExpression src = from_import.getImportSource();
|
||||
if (src != null) {
|
||||
setItemNotice(lookup_item, " | " + src.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
myVariants.put(referencedName, lookup_item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public <T> T getHint(Class<T> hintClass) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void handleEvent(Event event, Object associated) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,7 +5,9 @@ import com.intellij.psi.ResolveState;
|
||||
import com.jetbrains.python.psi.PyClass;
|
||||
import com.jetbrains.python.psi.PyExpression;
|
||||
import com.jetbrains.python.psi.PyReferenceExpression;
|
||||
import com.jetbrains.python.psi.PyResolveUtil;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil;
|
||||
import com.jetbrains.python.psi.resolve.ResolveProcessor;
|
||||
import com.jetbrains.python.psi.resolve.VariantsProcessor;
|
||||
import com.jetbrains.python.psi.impl.PyBuiltinCache;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -51,7 +53,7 @@ public class PyClassType implements PyType {
|
||||
@Nullable
|
||||
public PsiElement resolveMember(final String name) {
|
||||
if (myClass == null) return null;
|
||||
PyResolveUtil.ResolveProcessor processor = new PyResolveUtil.ResolveProcessor(name);
|
||||
ResolveProcessor processor = new ResolveProcessor(name);
|
||||
myClass.processDeclarations(processor, ResolveState.initial(), null, myClass); // our members are strictly within us.
|
||||
final PsiElement resolveResult = processor.getResult();
|
||||
//final PsiElement resolveResult = PyResolveUtil.treeWalkUp(new PyResolveUtil.ResolveProcessor(name), myClass, null, null);
|
||||
@@ -94,7 +96,7 @@ public class PyClassType implements PyType {
|
||||
}
|
||||
|
||||
public Object[] getCompletionVariants(final PyReferenceExpression referenceExpression) {
|
||||
final PyResolveUtil.VariantsProcessor processor = new PyResolveUtil.VariantsProcessor(new PyResolveUtil.FilterNotInstance(myClass));
|
||||
final VariantsProcessor processor = new VariantsProcessor(new PyResolveUtil.FilterNotInstance(myClass));
|
||||
myClass.processDeclarations(processor, ResolveState.initial(), null, referenceExpression);
|
||||
List<Object> ret = new ArrayList<Object>();
|
||||
ret.addAll(processor.getResultList());
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.ResolveState;
|
||||
import com.jetbrains.python.psi.PyReferenceExpression;
|
||||
import com.jetbrains.python.psi.PyResolveUtil;
|
||||
import com.jetbrains.python.psi.resolve.VariantsProcessor;
|
||||
import com.jetbrains.python.psi.impl.ResolveImportUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -43,7 +43,7 @@ public class PyModuleType implements PyType { // TODO: make it a PyClassType ref
|
||||
}
|
||||
|
||||
public Object[] getCompletionVariants(final PyReferenceExpression referenceExpression) {
|
||||
final PyResolveUtil.VariantsProcessor processor = new PyResolveUtil.VariantsProcessor();
|
||||
final VariantsProcessor processor = new VariantsProcessor();
|
||||
myModule.processDeclarations(processor, ResolveState.initial(), null, referenceExpression);
|
||||
return processor.getResult();
|
||||
}
|
||||
@@ -55,7 +55,7 @@ public class PyModuleType implements PyType { // TODO: make it a PyClassType ref
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Set<String> getPossibleInstanceMembers() {
|
||||
public static Set<String> getPossibleInstanceMembers() {
|
||||
return ourPossibleFields;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.PyTokenTypes;
|
||||
import com.jetbrains.python.psi.PyClass;
|
||||
import com.jetbrains.python.psi.PyReferenceExpression;
|
||||
import com.jetbrains.python.psi.PyResolveUtil;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil;
|
||||
import com.jetbrains.python.psi.impl.PyBuiltinCache;
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
def foo(a):
|
||||
pass
|
||||
|
||||
bar = 2
|
||||
@@ -0,0 +1,3 @@
|
||||
from AddToImportFromFoo import bar
|
||||
|
||||
<warning descr="Unresolved reference 'foo'">foo</warning> # must get imported
|
||||
@@ -0,0 +1,3 @@
|
||||
from AddToImportFromFoo import bar, foo
|
||||
|
||||
foo # must get imported
|
||||
@@ -0,0 +1,3 @@
|
||||
import QualifyByImportFoo
|
||||
|
||||
<warning descr="Unresolved reference 'foo'">foo</warning> # must be qualified
|
||||
@@ -0,0 +1 @@
|
||||
foo = "yes"
|
||||
@@ -19,7 +19,7 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Test action that various inspections add.
|
||||
* Test actions that various inspections add.
|
||||
* User: dcheryasov
|
||||
* Date: Nov 29, 2008 12:47:08 AM
|
||||
*/
|
||||
@@ -29,6 +29,22 @@ public class QuickFixTest extends DaemonAnalyzerTestCase {
|
||||
doInspectionTest("AddImport.py", PyUnresolvedReferencesInspection.class, PyBundle.message("ACT.NAME.add.import"), true, true);
|
||||
}
|
||||
|
||||
public void testQualifyByImport() throws Exception {
|
||||
doInspectionTest(
|
||||
new String[]{"QualifyByImport.py", "QualifyByImportFoo.py"},
|
||||
PyUnresolvedReferencesInspection.class, "QualifyByImportFoo.foo?", true, false
|
||||
);
|
||||
}
|
||||
|
||||
public void testAddToImportFromList() throws Exception {
|
||||
doInspectionTest(
|
||||
new String[]{"AddToImportFromList.py", "AddToImportFromFoo.py"},
|
||||
PyUnresolvedReferencesInspection.class, "foo(a) from AddToImportFromFoo?", true, false
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: add a test for multiple variants of above
|
||||
|
||||
public void testAddSelf() throws Exception {
|
||||
doInspectionTest("AddSelf.py", PyMethodParametersInspection.class, PyBundle.message("QFIX.add.parameter.self"), true, true);
|
||||
}
|
||||
@@ -92,7 +108,7 @@ public class QuickFixTest extends DaemonAnalyzerTestCase {
|
||||
}
|
||||
|
||||
protected String getTestDataPath() {
|
||||
return PathManager.getHomePath() + "/plugins/python/testData/";
|
||||
return PathManager.getHomePath() + "/plugins/python/testData/inspections/";
|
||||
}
|
||||
|
||||
protected void doInspectionTest(String testFileName,
|
||||
@@ -100,14 +116,31 @@ public class QuickFixTest extends DaemonAnalyzerTestCase {
|
||||
String quickFixName,
|
||||
boolean applyFix,
|
||||
boolean available
|
||||
) throws Exception {
|
||||
doInspectionTest(new String[]{testFileName}, inspectionClass, quickFixName, applyFix, available);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs daemon passes and looks for given fix within infos.
|
||||
* @param testFiles names of files to participate; first is used for inspection and then for check by "_after".
|
||||
* @param inspectionClass what inspection to run
|
||||
* @param quickFixName how the resulting fix should be named (the human-readable name users see)
|
||||
* @param applyFix true if the fix needs to be applied
|
||||
* @param available true if the fix should be available, false if it should be explicitly not available.
|
||||
* @throws Exception
|
||||
*/
|
||||
protected void doInspectionTest(String[] testFiles,
|
||||
final Class inspectionClass,
|
||||
String quickFixName,
|
||||
boolean applyFix,
|
||||
boolean available
|
||||
) throws Exception {
|
||||
final LocalInspectionTool tool = (LocalInspectionTool)inspectionClass.newInstance();
|
||||
enableInspectionTool(tool);
|
||||
final String s = "inspections/" + testFileName;
|
||||
configureByFile(s);
|
||||
configureByFiles(null, testFiles);
|
||||
Collection<HighlightInfo> infos = doDoTest(true, false);
|
||||
|
||||
doQuickFixTest(infos, quickFixName, applyFix, available, s);
|
||||
doQuickFixTest(infos, quickFixName, applyFix, available, testFiles[0]);
|
||||
disableInspectionTool(tool.getShortName());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user