mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Makes 'as' clauses in autoimport avoid unwanted name redefinitions.
Removes '__init__.py' from autoimport candidates.
No tests for these yet due to stub index not being available at test time; to be added later.
Fixed stupid error in previous autoimport tests that gave false positives.
This commit is contained in:
@@ -15,6 +15,7 @@ public class PyNames {
|
||||
|
||||
@NonNls public static final String INIT = "__init__";
|
||||
@NonNls public static final String DOT_PY = ".py";
|
||||
@NonNls public static final String INIT_DOT_PY = INIT + DOT_PY;
|
||||
|
||||
@NonNls public static final String OBJECT = "object";
|
||||
@NonNls public static final String NONE = "None";
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.jetbrains.python.inspections;
|
||||
|
||||
import com.intellij.codeHighlighting.HighlightDisplayLevel;
|
||||
import com.intellij.codeInspection.*;
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.lang.annotation.HighlightSeverity;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
@@ -11,7 +10,6 @@ import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.stubs.StubIndex;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.PyTokenTypes;
|
||||
import com.jetbrains.python.actions.AddFieldQuickFix;
|
||||
import com.jetbrains.python.actions.AddImportAction;
|
||||
import com.jetbrains.python.actions.AddMethodQuickFix;
|
||||
@@ -78,12 +76,18 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool {
|
||||
super(holder);
|
||||
}
|
||||
|
||||
static String getFirstQualifier(String name) {
|
||||
// foo.bar.baz -> foo
|
||||
int pos = name.indexOf('.');
|
||||
if (pos > 0) return name.substring(0, pos);
|
||||
else return name;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static HintAction proposeImportFixes(final PyElement node, String ref_text) {
|
||||
boolean worthy_fix = false;
|
||||
ImportFromExistingFix fix = null;
|
||||
Set<String> seen_file_names = new HashSet<String>(); // true import names
|
||||
Set<String> seen_as_names = new HashSet<String>(); // 'as' parts: we don't want to clas with them either
|
||||
// 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);
|
||||
@@ -98,8 +102,8 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool {
|
||||
PsiElement dst = src.resolve();
|
||||
if (dst instanceof PyFile) {
|
||||
PyFile dst_file = (PyFile)dst;
|
||||
seen_file_names.add(ielt.getImportReference().getReferencedName()); // ref is ok or matching would fail
|
||||
seen_as_names.add(ielt.getVisibleName());
|
||||
String name = ielt.getImportReference().getReferencedName(); // ref is ok or matching would fail
|
||||
seen_file_names.add(name);
|
||||
PsiElement res = (dst_file).findExportedName(ref_text);
|
||||
if (res != null) {
|
||||
fix.addImport(res, dst_file, ielt);
|
||||
@@ -126,7 +130,8 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool {
|
||||
PsiElement dst = src.resolve();
|
||||
if (dst instanceof PyFile) {
|
||||
PyFile dst_file = (PyFile)dst;
|
||||
seen_file_names.add(from_stmt.getImportSource().getReferencedName()); // source is ok, else it won't match and we'd not be adding it
|
||||
String name = from_stmt.getImportSource().getReferencedName(); // source is ok, else it won't match and we'd not be adding it
|
||||
seen_file_names.add(name);
|
||||
PsiElement res = (dst_file).findExportedName(ref_text);
|
||||
if (res != null) {
|
||||
fix.addImport(res, dst_file, ielts[ielts.length-1]); // last element; action expects to add to tail
|
||||
@@ -155,14 +160,9 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool {
|
||||
String import_path = ResolveImportUtil.findShortestImportableName(node, vfile);
|
||||
if (import_path != null && !seen_file_names.contains(import_path)) {
|
||||
// a new, valid hit
|
||||
String as_name = null;
|
||||
if (seen_as_names.contains(import_path)) {
|
||||
// an 'as' name somewhere above eclipses the true name. get us a unique 'as' name.
|
||||
as_name = propseAsName(node.getContainingFile(), import_path);
|
||||
seen_as_names.add(as_name); // just in case
|
||||
}
|
||||
fix.addImport(symbol, srcfile, null, import_path, as_name);
|
||||
fix.addImport(symbol, srcfile, null, import_path, propseAsName(node.getContainingFile(), ref_text, import_path));
|
||||
seen_file_names.add(import_path); // just in case, again
|
||||
worthy_fix = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -177,20 +177,26 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool {
|
||||
private final static String[] AS_PREFIXES = {"other_", "one_more_", "different_", "pseudo_", "true_"};
|
||||
|
||||
// a no-frills recursive accumulating scan
|
||||
private static void collectIdentifiers(ASTNode node, Collection<String> dst) {
|
||||
ASTNode seeker = node.getFirstChildNode();
|
||||
private static void collectIdentifiers(PsiElement node, Collection<String> dst) {
|
||||
PsiElement seeker = node.getFirstChild();
|
||||
while (seeker != null) {
|
||||
if (seeker.getElementType() == PyTokenTypes.IDENTIFIER) dst.add(seeker.getText());
|
||||
if (seeker instanceof NameDefiner) {
|
||||
for (PyElement named : ((NameDefiner)seeker).iterateNames()) {
|
||||
if (named != null) dst.add(named.getName());
|
||||
}
|
||||
}
|
||||
else collectIdentifiers(seeker, dst);
|
||||
seeker = seeker.getTreeNext();
|
||||
seeker = seeker.getNextSibling();
|
||||
}
|
||||
}
|
||||
|
||||
// find an unique name that does not clash with anything in the file, using ref_test and import_path as hints
|
||||
private static String propseAsName(PsiFile file, String import_path) {
|
||||
// find an unique name that does not clash with anything in the file, using ref_name and import_path as hints
|
||||
private static String propseAsName(PsiFile file, String ref_name, String import_path) {
|
||||
// a somehow brute-force approach: collect all identifiers wholesale and avoid clashes with any of them
|
||||
Set<String> ident_set = new HashSet<String>();
|
||||
collectIdentifiers(file.getNode(), ident_set);
|
||||
collectIdentifiers(file, ident_set);
|
||||
// try the default, 'normal' name first; if it does not clash, propose no sustitute!
|
||||
if (! ident_set.contains(ref_name)) return null;
|
||||
// try flattened import path
|
||||
String path_name = import_path.replace('.', '_');
|
||||
if (! ident_set.contains(path_name)) return path_name;
|
||||
@@ -199,7 +205,7 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool {
|
||||
String variant = prefix + path_name;
|
||||
if (! ident_set.contains(variant)) return variant;
|
||||
}
|
||||
// if nothing helped, just bluntly add a number to the end. guaranteed to finish in ident_set.size() iterations.
|
||||
// if nothing helped, just bluntly add a number to the end. guaranteed to finish in ident_set.size()+1 iterations.
|
||||
int cnt = 1;
|
||||
while (cnt < Integer.MAX_VALUE) {
|
||||
String variant = path_name + Integer.toString(cnt);
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.containers.HashSet;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.PyImportResolver;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
@@ -560,10 +561,13 @@ public class ResolveImportUtil {
|
||||
|
||||
private PathChoosingVisitor(VirtualFile file) {
|
||||
myFile = file;
|
||||
// cut off the ext
|
||||
myFname = file.getPath();
|
||||
// cut off the ext
|
||||
int pos = myFname.lastIndexOf('.');
|
||||
if (pos > 0) myFname = myFname.substring(0, pos);
|
||||
// cut off the final __init__ if it's there; we want imports directly from a module
|
||||
pos = myFname.lastIndexOf(PyNames.INIT);
|
||||
if (pos > 0) myFname = myFname.substring(0, pos-1); // pos-1 also cuts the '/' that came before "__init__"
|
||||
}
|
||||
|
||||
public boolean visitRoot(VirtualFile root) {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
from AddToImportFromFoo import bar, foo
|
||||
from AddToImportFromFoo import bar,foo
|
||||
|
||||
foo # must get imported
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import QualifyByImportFoo
|
||||
|
||||
QualifyByImportFoo.foo # must be qualified
|
||||
@@ -32,19 +32,20 @@ public class QuickFixTest extends DaemonAnalyzerTestCase {
|
||||
public void testQualifyByImport() throws Exception {
|
||||
doInspectionTest(
|
||||
new String[]{"QualifyByImport.py", "QualifyByImportFoo.py"},
|
||||
PyUnresolvedReferencesInspection.class, "QualifyByImportFoo.foo?", true, false
|
||||
PyUnresolvedReferencesInspection.class, PyBundle.message("ACT.NAME.use.import"), true, true
|
||||
);
|
||||
}
|
||||
|
||||
public void testAddToImportFromList() throws Exception {
|
||||
doInspectionTest(
|
||||
new String[]{"AddToImportFromList.py", "AddToImportFromFoo.py"},
|
||||
PyUnresolvedReferencesInspection.class, "foo(a) from AddToImportFromFoo?", true, false
|
||||
PyUnresolvedReferencesInspection.class, PyBundle.message("ACT.NAME.use.import"), true, true
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: add a test for multiple variants of above
|
||||
|
||||
// TODO: add tests for stub indexes-based autoimport of unimported somehow.
|
||||
|
||||
public void testAddSelf() throws Exception {
|
||||
doInspectionTest("AddSelf.py", PyMethodParametersInspection.class, PyBundle.message("QFIX.add.parameter.self"), true, true);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user