mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Refactored API for resolving Python qualified names in roots
Rewritten it in Kotlin and transformed mutable build-style API to a more functional immutable version. It should become easier to extend the PyQualifiedNameResolveContext and the PyResolveImportUtil.resolveQualfiedName() in order to add resolve to Python stub files by default.
This commit is contained in:
committed by
Andrey Vlasovskikh
parent
7a1b4d789c
commit
f0d6fdce64
+2
-2
@@ -5,12 +5,12 @@ import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.util.QualifiedName;
|
||||
import com.jetbrains.python.psi.impl.PyImportResolver;
|
||||
import com.jetbrains.python.psi.resolve.QualifiedNameResolveContext;
|
||||
import com.jetbrains.python.psi.resolve.PyQualifiedNameResolveContext;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class PyStudyImportResolver implements PyImportResolver {
|
||||
@Nullable
|
||||
public PsiElement resolveImportReference(QualifiedName name, QualifiedNameResolveContext context, boolean withRoots) {
|
||||
public PsiElement resolveImportReference(QualifiedName name, PyQualifiedNameResolveContext context, boolean withRoots) {
|
||||
if (StudyTaskManager.getInstance(context.getProject()).getCourse() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiPackage;
|
||||
import com.intellij.psi.util.QualifiedName;
|
||||
import com.jetbrains.python.psi.resolve.QualifiedNameResolveContext;
|
||||
import com.jetbrains.python.psi.resolve.PyQualifiedNameResolveContext;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
@@ -29,7 +29,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
*/
|
||||
public class PyJavaImportResolver implements PyImportResolver {
|
||||
@Nullable
|
||||
public PsiElement resolveImportReference(QualifiedName name, QualifiedNameResolveContext context, boolean withRoots) {
|
||||
public PsiElement resolveImportReference(QualifiedName name, PyQualifiedNameResolveContext context, boolean withRoots) {
|
||||
String fqn = name.toString();
|
||||
final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(context.getProject());
|
||||
final PsiPackage aPackage = psiFacade.findPackage(fqn);
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
package com.jetbrains.python.codeInsight;
|
||||
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.intellij.psi.util.QualifiedName;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.resolve.QualifiedNameResolver;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -39,9 +39,9 @@ public abstract class PyPsiPath {
|
||||
@Nullable
|
||||
@Override
|
||||
public PsiElement resolve(PsiElement context) {
|
||||
PyPsiFacade pyPsiFacade = PyPsiFacade.getInstance(context.getProject());
|
||||
QualifiedNameResolver visitor = pyPsiFacade.qualifiedNameResolver(myQualifiedName).fromElement(context);
|
||||
return visitor.firstResult();
|
||||
final PyPsiFacade facade = PyPsiFacade.getInstance(context.getProject());
|
||||
return facade.resolveQualifiedName(myQualifiedName, facade.createResolveContextFromFoothold(context))
|
||||
.stream().findFirst().orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.util.QualifiedName;
|
||||
import com.jetbrains.python.psi.resolve.QualifiedNameResolver;
|
||||
import com.jetbrains.python.psi.resolve.PyQualifiedNameResolveContext;
|
||||
import com.jetbrains.python.psi.types.PyClassType;
|
||||
import com.jetbrains.python.psi.types.PyType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -38,9 +38,10 @@ public abstract class PyPsiFacade {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public abstract QualifiedNameResolver qualifiedNameResolver(String qNameString);
|
||||
public abstract QualifiedNameResolver qualifiedNameResolver(QualifiedName qualifiedName);
|
||||
public abstract List<PsiElement> resolveQualifiedName(@NotNull QualifiedName name, @NotNull PyQualifiedNameResolveContext context);
|
||||
|
||||
@NotNull
|
||||
public abstract PyQualifiedNameResolveContext createResolveContextFromFoothold(@NotNull PsiElement foothold);
|
||||
/**
|
||||
* @deprecated use {@link #createClassByQName(String, PsiElement)} or skeleton may be found
|
||||
*/
|
||||
|
||||
@@ -18,7 +18,7 @@ package com.jetbrains.python.psi.impl;
|
||||
import com.intellij.openapi.extensions.ExtensionPointName;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.util.QualifiedName;
|
||||
import com.jetbrains.python.psi.resolve.QualifiedNameResolveContext;
|
||||
import com.jetbrains.python.psi.resolve.PyQualifiedNameResolveContext;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
@@ -28,5 +28,5 @@ public interface PyImportResolver {
|
||||
ExtensionPointName<PyImportResolver> EP_NAME = ExtensionPointName.create("Pythonid.importResolver");
|
||||
|
||||
@Nullable
|
||||
PsiElement resolveImportReference(QualifiedName name, QualifiedNameResolveContext context, boolean withRoots);
|
||||
PsiElement resolveImportReference(QualifiedName name, PyQualifiedNameResolveContext context, boolean withRoots);
|
||||
}
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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.module.Module;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.projectRoots.Sdk;
|
||||
import com.intellij.psi.PsiDirectory;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Context for resolving Python qualified names with various options.
|
||||
*
|
||||
* @author vlan
|
||||
*/
|
||||
public interface PyQualifiedNameResolveContext {
|
||||
@Nullable
|
||||
PsiElement getFoothold();
|
||||
int getRelativeLevel();
|
||||
@Nullable
|
||||
Sdk getSdk();
|
||||
@Nullable
|
||||
Module getModule();
|
||||
@NotNull
|
||||
Project getProject();
|
||||
boolean getWithoutRoots();
|
||||
boolean getWithoutForeign();
|
||||
@NotNull
|
||||
PsiManager getPsiManager();
|
||||
boolean getWithMembers();
|
||||
boolean getWithPlainDirectories();
|
||||
boolean getVisitAllModules();
|
||||
@Nullable
|
||||
Sdk getEffectiveSdk();
|
||||
|
||||
boolean isValid();
|
||||
@Nullable
|
||||
PsiFile getFootholdFile();
|
||||
@Nullable
|
||||
PsiDirectory getContainingDirectory();
|
||||
|
||||
@NotNull
|
||||
PyQualifiedNameResolveContext copyWithoutForeign();
|
||||
@NotNull
|
||||
PyQualifiedNameResolveContext copyWithMembers();
|
||||
@NotNull
|
||||
PyQualifiedNameResolveContext copyWithPlainDirectories();
|
||||
@NotNull
|
||||
PyQualifiedNameResolveContext copyWithRelative(int relativeLevel);
|
||||
@NotNull
|
||||
PyQualifiedNameResolveContext copyWithoutRoots();
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* 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.module.Module;
|
||||
import com.intellij.openapi.module.ModuleUtilCore;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.projectRoots.Sdk;
|
||||
import com.intellij.psi.PsiDirectory;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiManager;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
*/
|
||||
public class QualifiedNameResolveContext {
|
||||
@Nullable private Module myModule;
|
||||
private PsiFile myFootholdFile;
|
||||
@NotNull PsiManager myPsiManager;
|
||||
private Sdk mySdk;
|
||||
|
||||
public void copyFrom(QualifiedNameResolveContext context) {
|
||||
myModule = context.getModule();
|
||||
myPsiManager = context.getPsiManager();
|
||||
mySdk = context.getSdk();
|
||||
myFootholdFile = context.getFootholdFile();
|
||||
}
|
||||
|
||||
public void setFromElement(PsiElement foothold) {
|
||||
if (foothold instanceof PsiDirectory) {
|
||||
myFootholdFile = ((PsiDirectory)foothold).findFile(PyNames.INIT_DOT_PY);
|
||||
}
|
||||
else {
|
||||
myFootholdFile = foothold.getContainingFile().getOriginalFile();
|
||||
}
|
||||
myPsiManager = foothold.getManager();
|
||||
myModule = ModuleUtilCore.findModuleForPsiElement(foothold);
|
||||
}
|
||||
|
||||
public void setFromModule(Module module) {
|
||||
myModule = module;
|
||||
myPsiManager = PsiManager.getInstance(module.getProject());
|
||||
}
|
||||
|
||||
public void setFromSdk(Project project, Sdk sdk) {
|
||||
myPsiManager = PsiManager.getInstance(project);
|
||||
mySdk = sdk;
|
||||
}
|
||||
|
||||
public void setSdk(Sdk sdk) {
|
||||
mySdk = sdk;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Module getModule() {
|
||||
return myModule;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
if (myFootholdFile != null) {
|
||||
return myFootholdFile.isValid();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PsiFile getFootholdFile() {
|
||||
return myFootholdFile;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PsiManager getPsiManager() {
|
||||
return myPsiManager;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Project getProject() {
|
||||
return myPsiManager.getProject();
|
||||
}
|
||||
|
||||
public Sdk getSdk() {
|
||||
return mySdk;
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* 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.module.Module;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.projectRoots.Sdk;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiNamedElement;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
*/
|
||||
public interface QualifiedNameResolver {
|
||||
QualifiedNameResolver fromElement(@NotNull PsiElement foothold);
|
||||
|
||||
QualifiedNameResolver fromModule(@NotNull Module module);
|
||||
|
||||
QualifiedNameResolver fromSdk(@NotNull Project project, @NotNull Sdk sdk);
|
||||
|
||||
QualifiedNameResolver withAllModules();
|
||||
|
||||
QualifiedNameResolver withSdk(Sdk sdk);
|
||||
|
||||
QualifiedNameResolver withRelative(int relativeLevel);
|
||||
|
||||
QualifiedNameResolver withoutRoots();
|
||||
|
||||
QualifiedNameResolver withPlainDirectories();
|
||||
|
||||
@NotNull
|
||||
List<PsiElement> resultsAsList();
|
||||
|
||||
@Nullable
|
||||
PsiElement firstResult();
|
||||
|
||||
@NotNull
|
||||
<T extends PsiElement> List<T> resultsOfType(Class<T> clazz);
|
||||
|
||||
@Nullable
|
||||
<T extends PsiElement> T firstResultOfType(Class<T> clazz);
|
||||
|
||||
QualifiedNameResolver withContext(QualifiedNameResolveContext context);
|
||||
|
||||
QualifiedNameResolver withoutForeign();
|
||||
|
||||
Module getModule();
|
||||
|
||||
QualifiedNameResolver withMembers();
|
||||
|
||||
/**
|
||||
* Resolves to some toplevel symbol like class or function.
|
||||
* i.e.: "foo.package.MyClass".
|
||||
* <strong>Module is required!</strong>
|
||||
* Call {@link #fromModule(com.intellij.openapi.module.Module)} first!
|
||||
* @param aClass expected class
|
||||
* @param <T> expected class
|
||||
* @return element if found
|
||||
*/
|
||||
@Nullable
|
||||
<T extends PsiNamedElement> T resolveTopLevelMember(@NotNull Class<T> aClass);
|
||||
}
|
||||
@@ -35,6 +35,7 @@ import com.jetbrains.python.documentation.docstrings.SectionBasedDocString.Secti
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.PyBuiltinCache;
|
||||
import com.jetbrains.python.psi.impl.PyExpressionCodeFragmentImpl;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveImportUtil;
|
||||
import com.jetbrains.python.psi.types.PyNoneType;
|
||||
import com.jetbrains.python.psi.types.PyType;
|
||||
import com.jetbrains.python.psi.types.PyTypeProviderBase;
|
||||
@@ -165,8 +166,8 @@ public class NumpyDocStringTypeProvider extends PyTypeProviderBase {
|
||||
private static PyFunction resolveRedirectToFunction(@NotNull String redirect, @NotNull PsiElement reference) {
|
||||
final QualifiedName qualifiedName = QualifiedName.fromDottedString(redirect);
|
||||
final String functionName = qualifiedName.getLastComponent();
|
||||
final PyPsiFacade facade = PyPsiFacade.getInstance(reference.getProject());
|
||||
final List<PsiElement> items = facade.qualifiedNameResolver(qualifiedName.removeLastComponent()).fromElement(reference).resultsAsList();
|
||||
final List<PsiElement> items = PyResolveImportUtil.resolveQualifiedName(qualifiedName.removeLastComponent(),
|
||||
PyResolveImportUtil.fromFoothold(reference));
|
||||
for (PsiElement item : items) {
|
||||
if (item instanceof PsiDirectory) {
|
||||
item = ((PsiDirectory)item).findFile(PyNames.INIT_DOT_PY);
|
||||
|
||||
@@ -19,9 +19,9 @@ import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.util.QualifiedName;
|
||||
import com.jetbrains.python.codeInsight.PyCustomMember;
|
||||
import com.jetbrains.python.psi.PyFile;
|
||||
import com.jetbrains.python.psi.PyPsiFacade;
|
||||
import com.jetbrains.python.psi.PyUtil;
|
||||
import com.jetbrains.python.psi.resolve.QualifiedNameResolver;
|
||||
import com.jetbrains.python.psi.resolve.PyQualifiedNameResolveContext;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveImportUtil;
|
||||
import com.jetbrains.python.psi.types.PyModuleMembersProvider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -68,10 +68,10 @@ public class NumpyModuleMembersProvider extends PyModuleMembersProvider {
|
||||
}
|
||||
|
||||
private static void addTestingModule(PyFile module, List<PyCustomMember> members) {
|
||||
PyPsiFacade psiFacade = PyPsiFacade.getInstance(module.getProject());
|
||||
final QualifiedNameResolver resolver =
|
||||
psiFacade.qualifiedNameResolver(QualifiedName.fromDottedString("numpy.testing")).withPlainDirectories().fromElement(module);
|
||||
PsiElement testingModule = PyUtil.turnDirIntoInit(resolver.firstResult());
|
||||
final PyQualifiedNameResolveContext context = PyResolveImportUtil.fromFoothold(module).copyWithPlainDirectories();
|
||||
final PsiElement resolved = PyResolveImportUtil.resolveQualifiedName(QualifiedName.fromDottedString("numpy.testing"), context)
|
||||
.stream().findFirst().orElse(null);
|
||||
final PsiElement testingModule = PyUtil.turnDirIntoInit(resolved);
|
||||
members.add(new PyCustomMember("testing", testingModule));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,9 +26,11 @@ import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.util.SystemInfo;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiDirectory;
|
||||
import com.jetbrains.python.psi.resolve.QualifiedNameResolver;
|
||||
import com.jetbrains.python.psi.resolve.QualifiedNameResolverImpl;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.util.QualifiedName;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveImportUtil;
|
||||
import com.jetbrains.python.sdk.PythonSdkType;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -122,15 +124,13 @@ public abstract class QtFileType extends LanguageFileType implements INativeFile
|
||||
|
||||
@Nullable
|
||||
private static String findToolInPackage(String toolName, Module module, Sdk sdk, String name) {
|
||||
QualifiedNameResolver visitor = new QualifiedNameResolverImpl(name).fromModule(module).withSdk(sdk);
|
||||
List<PsiDirectory> elements = visitor.resultsOfType(PsiDirectory.class);
|
||||
for (PsiDirectory directory : elements) {
|
||||
VirtualFile tool = directory.getVirtualFile().findChild(toolName + ".exe");
|
||||
if (tool != null) {
|
||||
return tool.getPath();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
final List<PsiElement> results = PyResolveImportUtil.resolveQualifiedName(QualifiedName.fromDottedString(name),
|
||||
PyResolveImportUtil.fromSdk(module.getProject(), sdk));
|
||||
return StreamEx.of(results).select(PsiDirectory.class)
|
||||
.map(directory -> directory.getVirtualFile().findChild(toolName + ".exe"))
|
||||
.filter(file -> file != null)
|
||||
.map(file -> file.getPath())
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
protected abstract String getToolName();
|
||||
|
||||
@@ -132,7 +132,8 @@ public class PyUserSkeletonsUtil {
|
||||
final VirtualFile directory = getUserSkeletonsDirectory();
|
||||
if (directory != null) {
|
||||
final PsiDirectory psiDirectory = PsiManager.getInstance(project).findDirectory(directory);
|
||||
PsiElement fileSkeleton = new QualifiedNameResolverImpl(qName).resolveModuleAt(psiDirectory);
|
||||
PsiElement fileSkeleton = PyResolveImportUtil.resolveModuleAt(QualifiedName.fromDottedString(qName), psiDirectory,
|
||||
PyResolveImportUtil.fromFoothold(foothold));
|
||||
if (fileSkeleton instanceof PsiDirectory) {
|
||||
fileSkeleton = PyUtil.getPackageElement((PsiDirectory)fileSkeleton, foothold);
|
||||
}
|
||||
|
||||
@@ -22,9 +22,7 @@ import com.intellij.psi.util.QualifiedName;
|
||||
import com.jetbrains.python.psi.PyClass;
|
||||
import com.jetbrains.python.psi.PyPsiFacade;
|
||||
import com.jetbrains.python.psi.PyUtil;
|
||||
import com.jetbrains.python.psi.resolve.QualifiedNameFinder;
|
||||
import com.jetbrains.python.psi.resolve.QualifiedNameResolver;
|
||||
import com.jetbrains.python.psi.resolve.QualifiedNameResolverImpl;
|
||||
import com.jetbrains.python.psi.resolve.*;
|
||||
import com.jetbrains.python.psi.stubs.PyClassNameIndex;
|
||||
import com.jetbrains.python.psi.types.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -43,15 +41,16 @@ public class PyPsiFacadeImpl extends PyPsiFacade {
|
||||
myProject = project;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public QualifiedNameResolver qualifiedNameResolver(String qNameString) {
|
||||
return new QualifiedNameResolverImpl(qNameString);
|
||||
@Override
|
||||
public List<PsiElement> resolveQualifiedName(@NotNull QualifiedName name, @NotNull PyQualifiedNameResolveContext context) {
|
||||
return PyResolveImportUtil.resolveQualifiedName(name, context);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public QualifiedNameResolver qualifiedNameResolver(QualifiedName qualifiedName) {
|
||||
return new QualifiedNameResolverImpl(qualifiedName);
|
||||
public PyQualifiedNameResolveContext createResolveContextFromFoothold(@NotNull PsiElement foothold) {
|
||||
return PyResolveImportUtil.fromFoothold(foothold);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -38,6 +38,7 @@ import com.jetbrains.python.psi.resolve.*;
|
||||
import com.jetbrains.python.psi.types.PyModuleType;
|
||||
import com.jetbrains.python.psi.types.PyType;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -265,10 +266,9 @@ public class PyImportReference extends PyReferenceImpl {
|
||||
}
|
||||
|
||||
private void fillFromQName(QualifiedName thisQName, InsertHandler<LookupElement> insertHandler) {
|
||||
QualifiedNameResolver visitor = new QualifiedNameResolverImpl(thisQName).fromElement(myCurrentFile);
|
||||
for (PsiDirectory dir : visitor.resultsOfType(PsiDirectory.class)) {
|
||||
fillFromDir(dir, insertHandler);
|
||||
}
|
||||
StreamEx.of(PyResolveImportUtil.resolveQualifiedName(thisQName, PyResolveImportUtil.fromFoothold(myCurrentFile)))
|
||||
.select(PsiDirectory.class)
|
||||
.forEach(directory -> fillFromDir(directory, insertHandler));
|
||||
}
|
||||
|
||||
private void addImportedNames(@NotNull PyImportElement[] importElements) {
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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.module.Module
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.psi.PsiDirectory
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.jetbrains.python.PyNames
|
||||
import com.jetbrains.python.console.PydevConsoleRunner
|
||||
import com.jetbrains.python.psi.PyUtil
|
||||
|
||||
data class PyQualifiedNameResolveContextImpl(private val psiManager: PsiManager, private val module: Module?,
|
||||
private val foothold: PsiElement?, private val sdk: Sdk?,
|
||||
private val relativeLevel : Int = -1,
|
||||
private val withoutRoots : Boolean = false,
|
||||
private val withoutForeign: Boolean = false,
|
||||
private val withMembers : Boolean = false,
|
||||
private val withPlainDirectories : Boolean = false) : PyQualifiedNameResolveContext {
|
||||
override fun getFoothold() = foothold
|
||||
|
||||
override fun getRelativeLevel() = relativeLevel
|
||||
|
||||
override fun getSdk() = sdk
|
||||
|
||||
override fun getModule() = module
|
||||
|
||||
override fun getProject() = psiManager.project
|
||||
|
||||
override fun getWithoutRoots() = withoutRoots
|
||||
|
||||
override fun getWithoutForeign() = withoutForeign
|
||||
|
||||
override fun getPsiManager() = psiManager
|
||||
|
||||
override fun getWithMembers() = withMembers
|
||||
|
||||
override fun getWithPlainDirectories() = withPlainDirectories
|
||||
|
||||
override fun getEffectiveSdk() = if (visitAllModules) PydevConsoleRunner.getConsoleSdk(foothold) else sdk
|
||||
|
||||
override fun isValid() = footholdFile?.isValid ?: true
|
||||
|
||||
override fun copyWithoutForeign() = copy(withoutForeign = true)
|
||||
|
||||
override fun copyWithMembers() = copy(withMembers = true)
|
||||
|
||||
override fun copyWithPlainDirectories() = copy(withPlainDirectories = true)
|
||||
|
||||
override fun copyWithRelative(relativeLevel : Int) = copy(relativeLevel = relativeLevel)
|
||||
|
||||
override fun copyWithoutRoots() = copy(withoutRoots = true)
|
||||
|
||||
override fun getContainingDirectory(): PsiDirectory? {
|
||||
val file = footholdFile ?: return null
|
||||
return if (relativeLevel > 0) ResolveImportUtil.stepBackFrom(file, relativeLevel) else file.containingDirectory
|
||||
}
|
||||
|
||||
override fun getFootholdFile() = when (foothold) {
|
||||
is PsiDirectory -> foothold.findFile(PyNames.INIT_DOT_PY)
|
||||
else -> foothold?.containingFile?.originalFile
|
||||
}
|
||||
|
||||
override fun getVisitAllModules(): Boolean {
|
||||
val file = foothold
|
||||
return file != null && (PydevConsoleRunner.isInPydevConsole(file) || PyUtil.isInScratchFile(file))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@file:JvmName("PyResolveImportUtil")
|
||||
package com.jetbrains.python.psi.resolve
|
||||
|
||||
import com.google.common.base.Preconditions
|
||||
import com.intellij.facet.FacetManager
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.extensions.Extensions
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.module.ModuleManager
|
||||
import com.intellij.openapi.module.ModuleUtilCore
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.roots.FileIndexFacade
|
||||
import com.intellij.openapi.roots.ModuleRootManager
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiDirectory
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFileSystemItem
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.psi.util.QualifiedName
|
||||
import com.jetbrains.python.codeInsight.userSkeletons.PyUserSkeletonsUtil
|
||||
import com.jetbrains.python.facet.PythonPathContributingFacet
|
||||
import com.jetbrains.python.psi.LanguageLevel
|
||||
import com.jetbrains.python.psi.PyFile
|
||||
import com.jetbrains.python.psi.PyUtil
|
||||
import com.jetbrains.python.psi.impl.PyBuiltinCache
|
||||
import com.jetbrains.python.psi.impl.PyImportResolver
|
||||
import com.jetbrains.python.sdk.PySdkUtil
|
||||
import com.jetbrains.python.sdk.PythonSdkType
|
||||
|
||||
/**
|
||||
* Python resolve utilities for qualified names.
|
||||
*
|
||||
* @author vlan
|
||||
*/
|
||||
|
||||
/**
|
||||
* Resolves a qualified [name] a list of modules / top-level elements according to the [context].
|
||||
*/
|
||||
fun resolveQualifiedName(name: QualifiedName, context: PyQualifiedNameResolveContext): List<PsiElement> {
|
||||
checkAccess()
|
||||
if (!context.isValid) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val relativeDirectory = context.containingDirectory
|
||||
val relativeResult = resolveWithRelativeLevel(name, context)
|
||||
val foundRelativeImport = relativeDirectory != null && relativeResult != null &&
|
||||
isRelativeImportResult(name, relativeDirectory, relativeResult, context)
|
||||
|
||||
val cache = findCache(context)
|
||||
val mayCache = cache != null && !context.withoutRoots && !context.withoutForeign && !foundRelativeImport
|
||||
|
||||
if (mayCache) {
|
||||
val cachedResults = cache?.get(name)
|
||||
if (cachedResults != null) {
|
||||
return listOf(listOfNotNull(relativeResult), cachedResults).flatten()
|
||||
}
|
||||
}
|
||||
|
||||
val allResults = listOf(listOfNotNull(relativeResult),
|
||||
resultsFromRoots(name, context),
|
||||
relativeResultsFromSkeletons(name, context),
|
||||
foreignResults(name, context)).flatten()
|
||||
val results = if (name.componentCount > 0) findFirstResults(allResults) else allResults
|
||||
|
||||
if (mayCache) {
|
||||
cache?.put(name, results)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a [name] to the first module member defined at the top-level.
|
||||
*/
|
||||
fun resolveTopLevelMember(name: QualifiedName, context : PyQualifiedNameResolveContext): PsiElement? {
|
||||
checkAccess()
|
||||
val memberName = name.lastComponent ?: return null
|
||||
return resolveQualifiedName(name.removeLastComponent(), context)
|
||||
.asSequence()
|
||||
.filterIsInstance(PyFile::class.java)
|
||||
.flatMap { it.multiResolveName(memberName).asSequence() }
|
||||
.map { it.element }
|
||||
.firstOrNull()
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a [name] relative to the specified [directory].
|
||||
*/
|
||||
fun resolveModuleAt(name: QualifiedName, directory: PsiDirectory?, context: PyQualifiedNameResolveContext): PsiElement? {
|
||||
checkAccess()
|
||||
if (directory == null || !directory.isValid) {
|
||||
return null
|
||||
}
|
||||
return name.components.fold(directory as PsiElement?) { seeker, component ->
|
||||
if (component == null) null
|
||||
// TODO: Switch to multi-resolve in this API
|
||||
else ResolveImportUtil.resolveChild(seeker, component, context.footholdFile, !context.withMembers,
|
||||
!context.withPlainDirectories)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [PyQualifiedNameResolveContext] from a [foothold] element.
|
||||
*/
|
||||
fun fromFoothold(foothold: PsiElement): PyQualifiedNameResolveContext {
|
||||
val module = ModuleUtilCore.findModuleForPsiElement(foothold)
|
||||
val sdk = module?.let { ModuleRootManager.getInstance(module).sdk }
|
||||
return PyQualifiedNameResolveContextImpl(foothold.manager, module, foothold, sdk)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [PyQualifiedNameResolveContext] from a [module].
|
||||
*/
|
||||
fun fromModule(module: Module): PyQualifiedNameResolveContext =
|
||||
PyQualifiedNameResolveContextImpl(PsiManager.getInstance(module.project), module, null, ModuleRootManager.getInstance(module).sdk)
|
||||
|
||||
/**
|
||||
* Creates a [PyQualifiedNameResolveContext] from an [sdk].
|
||||
*/
|
||||
fun fromSdk(project: Project, sdk: Sdk): PyQualifiedNameResolveContext =
|
||||
PyQualifiedNameResolveContextImpl(PsiManager.getInstance(project), module = null, foothold = null, sdk = sdk)
|
||||
|
||||
private fun foreignResults(name: QualifiedName, context: PyQualifiedNameResolveContext) =
|
||||
if (context.withoutForeign)
|
||||
emptyList()
|
||||
else
|
||||
Extensions.getExtensions(PyImportResolver.EP_NAME)
|
||||
.asSequence()
|
||||
.map { it.resolveImportReference(name, context, !context.withoutRoots) }
|
||||
.filterNotNull()
|
||||
.toList()
|
||||
|
||||
private fun relativeResultsFromSkeletons(name: QualifiedName, context: PyQualifiedNameResolveContext): List<PsiElement> {
|
||||
val footholdFile = context.footholdFile
|
||||
if (context.withoutRoots && footholdFile != null) {
|
||||
val virtualFile = footholdFile.virtualFile
|
||||
if (virtualFile == null || FileIndexFacade.getInstance(context.project).isInContent(virtualFile)) {
|
||||
return emptyList()
|
||||
}
|
||||
val containingDirectory = context.containingDirectory
|
||||
if (containingDirectory != null) {
|
||||
val containingName = QualifiedNameFinder.findCanonicalImportPath(containingDirectory, null)
|
||||
if (containingName != null && containingName.componentCount > 0) {
|
||||
val absoluteName = containingName.append(name)
|
||||
val sdk = PythonSdkType.getSdk(footholdFile) ?: return emptyList()
|
||||
val skeletonsVirtualFile = PySdkUtil.findSkeletonsDir(sdk) ?: return emptyList()
|
||||
val skeletonsDir = context.psiManager.findDirectory(skeletonsVirtualFile)
|
||||
return listOfNotNull(resolveModuleAt(absoluteName, skeletonsDir, context))
|
||||
}
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the results according to their import priority in sys.path.
|
||||
*/
|
||||
private fun findFirstResults(results: List<PsiElement>) =
|
||||
if (results.all(::isNamespacePackage))
|
||||
results
|
||||
else
|
||||
listOfNotNull(results.firstOrNull { !isNamespacePackage(it) })
|
||||
|
||||
private fun isNamespacePackage(element: PsiElement): Boolean {
|
||||
if (element is PsiDirectory) {
|
||||
val level = PyUtil.getLanguageLevelForVirtualFile(element.project, element.virtualFile)
|
||||
if (level.isAtLeast(LanguageLevel.PYTHON33)) {
|
||||
return PyUtil.turnDirIntoInit(element) == null
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun resolveWithRelativeLevel(name: QualifiedName, context : PyQualifiedNameResolveContext): PsiElement? {
|
||||
val footholdFile = context.footholdFile
|
||||
if (context.relativeLevel >= 0 && footholdFile != null && !PyUserSkeletonsUtil.isUnderUserSkeletonsDirectory(footholdFile)) {
|
||||
return resolveModuleAt(name, context.containingDirectory, context)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun resultsFromRoots(name: QualifiedName, context: PyQualifiedNameResolveContext): List<PsiElement> {
|
||||
if (context.withoutRoots) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val moduleResults = mutableListOf<PsiElement>()
|
||||
val sdkResults = mutableListOf<PsiElement>()
|
||||
|
||||
val visitor = RootVisitor { root, module, sdk, isModuleSource ->
|
||||
val results = if (isModuleSource) moduleResults else sdkResults
|
||||
if (!root.isValid || root == PyUserSkeletonsUtil.getUserSkeletonsDirectory()) {
|
||||
return@RootVisitor true
|
||||
}
|
||||
val result = resolveInRoot(name, root, context)
|
||||
if (result != null) {
|
||||
results.add(result)
|
||||
}
|
||||
if (isAcceptRootAsTopLevelPackage(context) && name.matchesPrefix(QualifiedName.fromDottedString(root.name))) {
|
||||
val topLevelResult = resolveInRoot(name, root.parent, context)
|
||||
if (topLevelResult != null) {
|
||||
results.add(topLevelResult)
|
||||
}
|
||||
}
|
||||
return@RootVisitor true
|
||||
}
|
||||
|
||||
val sdk = context.effectiveSdk
|
||||
val module = context.module
|
||||
val footholdFile = context.footholdFile
|
||||
|
||||
when {
|
||||
context.visitAllModules -> {
|
||||
ModuleManager.getInstance(context.project).modules.forEach {
|
||||
RootVisitorHost.visitRoots(it, true, visitor)
|
||||
}
|
||||
when {
|
||||
sdk != null ->
|
||||
RootVisitorHost.visitSdkRoots(sdk, visitor)
|
||||
footholdFile != null ->
|
||||
RootVisitorHost.visitSdkRoots(footholdFile, visitor)
|
||||
}
|
||||
}
|
||||
module != null -> {
|
||||
val otherSdk = sdk != context.sdk
|
||||
RootVisitorHost.visitRoots(module, otherSdk, visitor)
|
||||
if (otherSdk && sdk != null) {
|
||||
RootVisitorHost.visitSdkRoots(sdk, visitor)
|
||||
}
|
||||
}
|
||||
footholdFile != null -> {
|
||||
RootVisitorHost.visitRoots(footholdFile, visitor)
|
||||
}
|
||||
sdk != null -> {
|
||||
RootVisitorHost.visitSdkRoots(sdk, visitor)
|
||||
}
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
return moduleResults + sdkResults
|
||||
}
|
||||
|
||||
private fun isAcceptRootAsTopLevelPackage(context: PyQualifiedNameResolveContext): Boolean {
|
||||
context.module?.let {
|
||||
FacetManager.getInstance(it).allFacets.forEach {
|
||||
if (it is PythonPathContributingFacet && it.acceptRootAsTopLevelPackage()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun resolveInRoot(name: QualifiedName, root: VirtualFile, context: PyQualifiedNameResolveContext): PsiElement? {
|
||||
return if (root.isDirectory) resolveModuleAt(name, context.psiManager.findDirectory(root), context) else null
|
||||
}
|
||||
|
||||
private fun findCache(context: PyQualifiedNameResolveContext): PythonPathCache? {
|
||||
return when {
|
||||
context.visitAllModules -> null
|
||||
context.module != null ->
|
||||
if (context.effectiveSdk != context.sdk) null else PythonModulePathCache.getInstance(context.module)
|
||||
context.footholdFile != null -> {
|
||||
val sdk = PyBuiltinCache.findSdkForNonModuleFile(context.footholdFile)
|
||||
if (sdk != null) PythonSdkPathCache.getInstance(context.project, sdk) else null
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun isRelativeImportResult(name: QualifiedName, directory: PsiDirectory, result: PsiElement,
|
||||
context: PyQualifiedNameResolveContext): Boolean {
|
||||
if (context.relativeLevel > 0) {
|
||||
return true
|
||||
}
|
||||
else {
|
||||
val py2 = LanguageLevel.forElement(directory).isOlderThan(LanguageLevel.PYTHON30)
|
||||
return context.relativeLevel == 0 && py2 && PyUtil.isPackage(directory, false, null) &&
|
||||
result is PsiFileSystemItem && name != QualifiedNameFinder.findShortestImportableQName(result)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkAccess() {
|
||||
Preconditions.checkState(ApplicationManager.getApplication().isReadAccessAllowed, "This method requires read access")
|
||||
}
|
||||
@@ -1,521 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* 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.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.intellij.facet.Facet;
|
||||
import com.intellij.facet.FacetManager;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.extensions.Extensions;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.projectRoots.Sdk;
|
||||
import com.intellij.openapi.roots.FileIndexFacade;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.QualifiedName;
|
||||
import com.jetbrains.python.codeInsight.userSkeletons.PyUserSkeletonsUtil;
|
||||
import com.jetbrains.python.console.PydevConsoleRunner;
|
||||
import com.jetbrains.python.facet.PythonPathContributingFacet;
|
||||
import com.jetbrains.python.psi.LanguageLevel;
|
||||
import com.jetbrains.python.psi.PyFile;
|
||||
import com.jetbrains.python.psi.PyUtil;
|
||||
import com.jetbrains.python.psi.impl.PyBuiltinCache;
|
||||
import com.jetbrains.python.psi.impl.PyImportResolver;
|
||||
import com.jetbrains.python.psi.impl.PyPsiUtils;
|
||||
import com.jetbrains.python.sdk.PySdkUtil;
|
||||
import com.jetbrains.python.sdk.PythonSdkType;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static com.jetbrains.python.psi.PyUtil.as;
|
||||
import static com.jetbrains.python.psi.PyUtil.turnDirIntoInit;
|
||||
|
||||
/**
|
||||
* Resolves the specified qualified name in the specified context (module, all modules or a file) to a file or directory.
|
||||
*
|
||||
* @author yole
|
||||
*/
|
||||
public class QualifiedNameResolverImpl implements RootVisitor, QualifiedNameResolver {
|
||||
boolean myCheckForPackage = true;
|
||||
private final QualifiedNameResolveContext myContext = new QualifiedNameResolveContext();
|
||||
private final @NotNull QualifiedName myQualifiedName;
|
||||
final Set<PsiElement> mySourceResults = Sets.newLinkedHashSet();
|
||||
final Set<PsiElement> myLibResults = Sets.newLinkedHashSet();
|
||||
final Set<PsiElement> myForeignResults = Sets.newLinkedHashSet();
|
||||
private boolean myVisitAllModules = false;
|
||||
private int myRelativeLevel = -1;
|
||||
private boolean myWithoutRoots;
|
||||
private boolean myWithoutForeign;
|
||||
private boolean myWithMembers;
|
||||
|
||||
public QualifiedNameResolverImpl(@NotNull String qNameString) {
|
||||
myQualifiedName = QualifiedName.fromDottedString(qNameString);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param qName the empty name means that all found roots will be traversed.
|
||||
*/
|
||||
public QualifiedNameResolverImpl(@NotNull QualifiedName qName) {
|
||||
myQualifiedName = qName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QualifiedNameResolver withContext(QualifiedNameResolveContext context) {
|
||||
myContext.copyFrom(context);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QualifiedNameResolver fromElement(@NotNull PsiElement foothold) {
|
||||
checkAccess();
|
||||
myContext.setFromElement(foothold);
|
||||
if (PydevConsoleRunner.isInPydevConsole(foothold) || PyUtil.isInScratchFile(foothold)) {
|
||||
withAllModules();
|
||||
Sdk sdk = PydevConsoleRunner.getConsoleSdk(foothold);
|
||||
if (sdk != null) {
|
||||
myContext.setSdk(sdk);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QualifiedNameResolver fromModule(@NotNull Module module) {
|
||||
PyPsiUtils.assertValid(module);
|
||||
checkAccess();
|
||||
myContext.setFromModule(module);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QualifiedNameResolver fromSdk(@NotNull Project project, @NotNull Sdk sdk) {
|
||||
myContext.setFromSdk(project, sdk);
|
||||
return this;
|
||||
}
|
||||
|
||||
private boolean isAcceptRootAsTopLevelPackage() {
|
||||
Module module = myContext.getModule();
|
||||
if (module != null) {
|
||||
Facet[] facets = FacetManager.getInstance(module).getAllFacets();
|
||||
for (Facet facet : facets) {
|
||||
if (facet instanceof PythonPathContributingFacet && ((PythonPathContributingFacet)facet).acceptRootAsTopLevelPackage()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QualifiedNameResolver withAllModules() {
|
||||
myVisitAllModules = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that we need to look for the name in the specified SDK (instead of the SDK assigned to the module, if any).
|
||||
*
|
||||
* @param sdk the SDK in which the name should be searched.
|
||||
* @return this
|
||||
*/
|
||||
@Override
|
||||
public QualifiedNameResolver withSdk(Sdk sdk) {
|
||||
myContext.setSdk(sdk);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies whether we should attempt to resolve imports relative to the current file.
|
||||
*
|
||||
* @param relativeLevel if >= 0, we try to resolve at the specified number of levels above the current file.
|
||||
* @return this
|
||||
*/
|
||||
@Override
|
||||
public QualifiedNameResolver withRelative(int relativeLevel) {
|
||||
myRelativeLevel = relativeLevel;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that we should only try to resolve relative to the current file, not in roots.
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
@Override
|
||||
public QualifiedNameResolver withoutRoots() {
|
||||
myWithoutRoots = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QualifiedNameResolver withoutForeign() {
|
||||
myWithoutForeign = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QualifiedNameResolver withMembers() {
|
||||
myWithMembers = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that we're looking for a file in a directory hierarchy, not a module in the Python package hierarchy
|
||||
* (so we don't need to check for existence of __init__.py)
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public QualifiedNameResolver withPlainDirectories() {
|
||||
myCheckForPackage = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean visitRoot(final VirtualFile root, @Nullable Module module, @Nullable Sdk sdk, boolean isModuleSource) {
|
||||
checkAccess();
|
||||
if (!root.isValid()) {
|
||||
return true;
|
||||
}
|
||||
if (root.equals(PyUserSkeletonsUtil.getUserSkeletonsDirectory())) {
|
||||
return true;
|
||||
}
|
||||
PsiElement resolveResult = resolveInRoot(root);
|
||||
if (resolveResult != null) {
|
||||
addRoot(resolveResult, isModuleSource);
|
||||
}
|
||||
|
||||
if (isAcceptRootAsTopLevelPackage() && myQualifiedName.matchesPrefix(QualifiedName.fromDottedString(root.getName()))) {
|
||||
resolveResult = resolveInRoot(root.getParent());
|
||||
if (resolveResult != null) {
|
||||
addRoot(resolveResult, isModuleSource);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void addRoot(PsiElement resolveResult, boolean isModuleSource) {
|
||||
final Set<PsiElement> results = isModuleSource ? mySourceResults : myLibResults;
|
||||
final boolean allNamespacePackages = allNamespacePackages(results);
|
||||
if (allNamespacePackages) {
|
||||
if (!isNamespacePackage(resolveResult)) {
|
||||
results.clear();
|
||||
}
|
||||
}
|
||||
if (allNamespacePackages || results.isEmpty() || myQualifiedName.getComponentCount() == 0) {
|
||||
results.add(resolveResult);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isNamespacePackage(@NotNull PsiElement element) {
|
||||
final PsiDirectory dir = as(element, PsiDirectory.class);
|
||||
if (dir != null) {
|
||||
final LanguageLevel level = PyUtil.getLanguageLevelForVirtualFile(dir.getProject(), dir.getVirtualFile());
|
||||
if (level.isAtLeast(LanguageLevel.PYTHON33)) {
|
||||
return turnDirIntoInit(dir) == null;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean allNamespacePackages(@NotNull Collection<PsiElement> elements) {
|
||||
for (PsiElement element : elements) {
|
||||
if (!isNamespacePackage(element)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public List<PsiElement> resultsAsList() {
|
||||
checkAccess();
|
||||
if (!myContext.isValid()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final PsiFile footholdFile = myContext.getFootholdFile();
|
||||
checkValidForTests(footholdFile);
|
||||
boolean foundRelativeImport = false;
|
||||
if (myRelativeLevel >= 0 && footholdFile != null && !PyUserSkeletonsUtil.isUnderUserSkeletonsDirectory(footholdFile)) {
|
||||
PsiDirectory dir = footholdFile.getContainingDirectory();
|
||||
checkValidForTests(dir);
|
||||
if (myRelativeLevel > 0) {
|
||||
dir = ResolveImportUtil.stepBackFrom(footholdFile, myRelativeLevel);
|
||||
checkValidForTests(dir);
|
||||
}
|
||||
|
||||
PsiElement module = resolveModuleAt(dir);
|
||||
checkValidForTests(module);
|
||||
if (module != null) {
|
||||
foundRelativeImport = isRelativeImportResult(dir, module);
|
||||
addRoot(module, true);
|
||||
}
|
||||
}
|
||||
|
||||
final PythonPathCache cache = findMyCache();
|
||||
final boolean mayCache = cache != null && !myWithoutRoots && !myWithoutForeign && !foundRelativeImport;
|
||||
if (mayCache) {
|
||||
final List<PsiElement> cachedResults = cache.get(myQualifiedName);
|
||||
if (cachedResults != null) {
|
||||
cachedResults.stream().forEach(QualifiedNameResolverImpl::checkValidForTests);
|
||||
mySourceResults.addAll(cachedResults);
|
||||
return Lists.newArrayList(mySourceResults);
|
||||
}
|
||||
}
|
||||
|
||||
if (!myWithoutRoots) {
|
||||
addResultsFromRoots();
|
||||
}
|
||||
else if (footholdFile != null) {
|
||||
addRelativeImportResultsFromSkeletons(footholdFile);
|
||||
}
|
||||
|
||||
if (mySourceResults.isEmpty() || myQualifiedName.getComponentCount() == 0) {
|
||||
mySourceResults.addAll(myLibResults);
|
||||
myLibResults.clear();
|
||||
}
|
||||
|
||||
if (!myWithoutForeign) {
|
||||
if (mySourceResults.isEmpty() || myQualifiedName.getComponentCount() == 0) {
|
||||
for (PyImportResolver resolver : Extensions.getExtensions(PyImportResolver.EP_NAME)) {
|
||||
PsiElement foreign = resolver.resolveImportReference(myQualifiedName, myContext, !myWithoutRoots);
|
||||
if (foreign != null) {
|
||||
myForeignResults.add(foreign);
|
||||
}
|
||||
}
|
||||
mySourceResults.addAll(myForeignResults);
|
||||
myForeignResults.clear();
|
||||
}
|
||||
}
|
||||
|
||||
final ArrayList<PsiElement> results = Lists.newArrayList(mySourceResults);
|
||||
if (mayCache) {
|
||||
cache.put(myQualifiedName, results);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
private boolean isRelativeImportResult(@NotNull PsiDirectory rootDirectory, @NotNull PsiElement result) {
|
||||
if (myRelativeLevel > 0) {
|
||||
return true;
|
||||
}
|
||||
final boolean isPython2 = LanguageLevel.forElement(rootDirectory).isOlderThan(LanguageLevel.PYTHON30);
|
||||
if (myRelativeLevel == 0 && isPython2 && PyUtil.isPackage(rootDirectory, false, null)) {
|
||||
// Candidate for implicit relative import doesn't necessarily means that the same module cannot be imported absolutely
|
||||
final PsiFileSystemItem moduleOrPackage = as(result, PsiFileSystemItem.class);
|
||||
if (moduleOrPackage != null && !myQualifiedName.equals(QualifiedNameFinder.findShortestImportableQName(moduleOrPackage))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve relative imports from sdk root to the skeleton dir
|
||||
*/
|
||||
private void addRelativeImportResultsFromSkeletons(@NotNull final PsiFile foothold) {
|
||||
final VirtualFile vFile = foothold.getVirtualFile();
|
||||
if (vFile == null || FileIndexFacade.getInstance(foothold.getProject()).isInContent(vFile)) {
|
||||
return;
|
||||
}
|
||||
PsiDirectory containingDirectory = foothold.getContainingDirectory();
|
||||
if (myRelativeLevel > 0) {
|
||||
containingDirectory = ResolveImportUtil.stepBackFrom(foothold, myRelativeLevel);
|
||||
}
|
||||
if (containingDirectory != null) {
|
||||
final QualifiedName containingQName = QualifiedNameFinder.findCanonicalImportPath(containingDirectory, null);
|
||||
if (containingQName != null && containingQName.getComponentCount() > 0) {
|
||||
final QualifiedName absoluteQName = containingQName.append(myQualifiedName.toString());
|
||||
final QualifiedNameResolverImpl absoluteVisitor =
|
||||
(QualifiedNameResolverImpl)new QualifiedNameResolverImpl(absoluteQName).fromElement(foothold);
|
||||
|
||||
final Sdk sdk = PythonSdkType.getSdk(foothold);
|
||||
if (sdk == null) return;
|
||||
final VirtualFile skeletonsDir = PySdkUtil.findSkeletonsDir(sdk);
|
||||
if (skeletonsDir == null) return;
|
||||
final PsiDirectory directory = myContext.getPsiManager().findDirectory(skeletonsDir);
|
||||
final PsiElement psiElement = absoluteVisitor.resolveModuleAt(directory);
|
||||
if (psiElement != null) {
|
||||
addRoot(psiElement, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addResultsFromRoots() {
|
||||
if (myVisitAllModules) {
|
||||
for (Module mod : ModuleManager.getInstance(myContext.getProject()).getModules()) {
|
||||
RootVisitorHost.visitRoots(mod, true, this);
|
||||
}
|
||||
|
||||
if (myContext.getSdk() != null) {
|
||||
RootVisitorHost.visitSdkRoots(myContext.getSdk(), this);
|
||||
}
|
||||
else if (myContext.getFootholdFile() != null) {
|
||||
RootVisitorHost.visitSdkRoots(myContext.getFootholdFile(), this);
|
||||
}
|
||||
}
|
||||
else if (myContext.getModule() != null) {
|
||||
final boolean otherSdk = withOtherSdk();
|
||||
RootVisitorHost.visitRoots(myContext.getModule(), otherSdk, this);
|
||||
if (otherSdk) {
|
||||
RootVisitorHost.visitSdkRoots(myContext.getSdk(), this);
|
||||
}
|
||||
}
|
||||
else if (myContext.getFootholdFile() != null) {
|
||||
RootVisitorHost.visitSdkRoots(myContext.getFootholdFile(), this);
|
||||
}
|
||||
else if (myContext.getSdk() != null) {
|
||||
RootVisitorHost.visitSdkRoots(myContext.getSdk(), this);
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("Context is empty. Provide some context: module, file, sdk, etc");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public PsiElement firstResult() {
|
||||
checkAccess();
|
||||
final List<PsiElement> results = resultsAsList();
|
||||
return results.size() > 0 ? results.get(0) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public <T extends PsiElement> List<T> resultsOfType(Class<T> clazz) {
|
||||
checkAccess();
|
||||
List<T> result = new ArrayList<>();
|
||||
for (PsiElement element : resultsAsList()) {
|
||||
if (clazz.isInstance(element)) {
|
||||
//noinspection unchecked
|
||||
result.add((T)element);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public <T extends PsiElement> T firstResultOfType(Class<T> clazz) {
|
||||
checkAccess();
|
||||
final List<T> list = resultsOfType(clazz);
|
||||
return list.size() > 0 ? list.get(0) : null;
|
||||
}
|
||||
|
||||
private boolean withOtherSdk() {
|
||||
return myContext.getSdk() != null && myContext.getSdk() != PythonSdkType.findPythonSdk(myContext.getModule());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PythonPathCache findMyCache() {
|
||||
if (myVisitAllModules) {
|
||||
return null;
|
||||
}
|
||||
if (myContext.getModule() != null) {
|
||||
return withOtherSdk() ? null : PythonModulePathCache.getInstance(myContext.getModule());
|
||||
}
|
||||
if (myContext.getFootholdFile() != null) {
|
||||
final Sdk sdk = PyBuiltinCache.findSdkForNonModuleFile(myContext.getFootholdFile());
|
||||
if (sdk != null) {
|
||||
return PythonSdkPathCache.getInstance(myContext.getProject(), sdk);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private PsiElement resolveInRoot(VirtualFile root) {
|
||||
if (!root.isDirectory()) {
|
||||
// if we have added a file as a root, it's unlikely that we'll be able to resolve anything under it in 'files only' resolve mode
|
||||
return null;
|
||||
}
|
||||
return resolveModuleAt(myContext.getPsiManager().findDirectory(root));
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a module at given directory, unwinding qualifiers and traversing directories as needed.
|
||||
*
|
||||
* @param directory where to start from; top qualifier will be searched for here.
|
||||
*/
|
||||
@Contract("null -> null")
|
||||
@Nullable
|
||||
public PsiElement resolveModuleAt(@Nullable PsiDirectory directory) {
|
||||
// prerequisites
|
||||
checkAccess();
|
||||
PyPsiUtils.assertValid(directory);
|
||||
if (directory == null || !directory.isValid()) return null;
|
||||
|
||||
PsiElement seeker = directory;
|
||||
for (String name : myQualifiedName.getComponents()) {
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
seeker = ResolveImportUtil.resolveChild(seeker, name, myContext.getFootholdFile(), !myWithMembers, myCheckForPackage);
|
||||
}
|
||||
return seeker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Module getModule() {
|
||||
return myContext.getModule();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public <T extends PsiNamedElement> T resolveTopLevelMember(@NotNull final Class<T> aClass) {
|
||||
checkAccess();
|
||||
Preconditions.checkState(getModule() != null, "Module is not set");
|
||||
final String memberName = myQualifiedName.getLastComponent();
|
||||
if (memberName == null) {
|
||||
return null;
|
||||
}
|
||||
final PyFile file =
|
||||
new QualifiedNameResolverImpl(myQualifiedName.removeLastComponent()).fromModule(getModule()).firstResultOfType(PyFile.class);
|
||||
if (file == null) {
|
||||
return null;
|
||||
}
|
||||
checkValidForTests(file);
|
||||
for (final T element : PsiTreeUtil.getChildrenOfTypeAsList(file, aClass)) {
|
||||
checkValidForTests(element);
|
||||
if (memberName.equals(element.getName())) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void checkValidForTests(@Nullable final PsiElement element) {
|
||||
if (ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
PyPsiUtils.assertValid(element);
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkAccess() {
|
||||
Preconditions.checkState(ApplicationManager.getApplication().isReadAccessAllowed(), "This method requires read access");
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
package com.jetbrains.python.psi.resolve;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.extensions.Extensions;
|
||||
import com.intellij.openapi.fileTypes.ExtensionFileNameMatcher;
|
||||
import com.intellij.openapi.fileTypes.FileNameMatcher;
|
||||
@@ -209,18 +208,11 @@ public class ResolveImportUtil {
|
||||
}
|
||||
try {
|
||||
beingImported.add(marker);
|
||||
final QualifiedNameResolver visitor = new QualifiedNameResolverImpl(qualifiedName).fromElement(sourceFile);
|
||||
if (relativeLevel > 0) {
|
||||
// "from ...module import"
|
||||
visitor.withRelative(relativeLevel).withoutRoots();
|
||||
}
|
||||
else {
|
||||
// "from module import"
|
||||
if (!importIsAbsolute) {
|
||||
visitor.withRelative(0);
|
||||
}
|
||||
}
|
||||
return visitor.resultsAsList();
|
||||
final PyQualifiedNameResolveContext initialContext = PyResolveImportUtil.fromFoothold(sourceFile);
|
||||
final PyQualifiedNameResolveContext context = relativeLevel > 0 ?
|
||||
initialContext.copyWithRelative(relativeLevel).copyWithoutRoots() :
|
||||
importIsAbsolute ? initialContext : initialContext.copyWithRelative(0);
|
||||
return PyResolveImportUtil.resolveQualifiedName(qualifiedName, context);
|
||||
}
|
||||
finally {
|
||||
beingImported.remove(marker);
|
||||
@@ -230,8 +222,9 @@ public class ResolveImportUtil {
|
||||
@Nullable
|
||||
public static PsiElement resolveModuleInRoots(@NotNull QualifiedName moduleQualifiedName, @Nullable PsiElement foothold) {
|
||||
if (foothold == null) return null;
|
||||
QualifiedNameResolver visitor = new QualifiedNameResolverImpl(moduleQualifiedName).fromElement(foothold);
|
||||
return visitor.firstResult();
|
||||
final List<PsiElement> results = PyResolveImportUtil.resolveQualifiedName(moduleQualifiedName,
|
||||
PyResolveImportUtil.fromFoothold(foothold));
|
||||
return !results.isEmpty() ? results.get(0) : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -367,7 +360,9 @@ public class ResolveImportUtil {
|
||||
|
||||
@Nullable
|
||||
private static PsiElement resolveForeignImports(@NotNull PsiFile foothold, @NotNull String referencedName) {
|
||||
return new QualifiedNameResolverImpl(referencedName).fromElement(foothold).withoutRoots().firstResult();
|
||||
final PyQualifiedNameResolveContext context = PyResolveImportUtil.fromFoothold(foothold).copyWithoutRoots();
|
||||
final List<PsiElement> results = PyResolveImportUtil.resolveQualifiedName(QualifiedName.fromDottedString(referencedName), context);
|
||||
return !results.isEmpty() ? results.get(0) : null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -35,7 +35,7 @@ import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.PyBuiltinCache;
|
||||
import com.jetbrains.python.psi.impl.PyPsiUtils;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveContext;
|
||||
import com.jetbrains.python.psi.resolve.QualifiedNameResolverImpl;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveImportUtil;
|
||||
import com.jetbrains.python.psi.resolve.RatedResolveResult;
|
||||
import com.jetbrains.python.psi.types.functionalParser.ForwardDeclaration;
|
||||
import com.jetbrains.python.psi.types.functionalParser.FunctionalParser;
|
||||
@@ -631,7 +631,8 @@ public class PyTypeParser {
|
||||
final Token<PyElementType> token = tokens.get(0);
|
||||
final String name = token.getText().toString();
|
||||
qName = qName != null ? qName.append(name) : QualifiedName.fromComponents(name);
|
||||
PsiElement module = new QualifiedNameResolverImpl(qName).fromElement(myAnchor).firstResult();
|
||||
final List<PsiElement> modules = PyResolveImportUtil.resolveQualifiedName(qName, PyResolveImportUtil.fromFoothold(myAnchor));
|
||||
PsiElement module = !modules.isEmpty() ? modules.get(0) : null;
|
||||
if (module == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user