独立 IntelliJ 解析器中的符号解析

Symbols resolution in standalone IntelliJ parser

我正在尝试将 IntelliJ SDK 用作独立的 java 解析器,它在大多数情况下工作正常,但无法解析 return 类型的通用方法。

当我在 IntelliJ 的下一个示例中为 verify(mock).simpleMethod() 调试 resolveMethod 时:

public class ResolutionTest {

    private interface IMethods {
        String simpleMethod();
    }

    private IMethods mock;

    public static <T> T verify(T m) {
        return m;
    }

    public void test() {
        verify(mock).simpleMethod();
    }

}

我看到 return 类型的 verify(mock) 作为 IMethodssimpleMethod 也正确解析。但是在我的解析器中 return 类型的 verify(mock)T 并且 simpleMethod 解析因此失败。我想我没有注册某些服务或扩展程序,但我不知道是哪一个。

我的解析器:

import com.intellij.codeInsight.ContainerProvider;
import com.intellij.codeInsight.runner.JavaMainMethodProvider;
import com.intellij.core.CoreApplicationEnvironment;
import com.intellij.core.CoreJavaFileManager;
import com.intellij.core.JavaCoreApplicationEnvironment;
import com.intellij.core.JavaCoreProjectEnvironment;
import com.intellij.mock.MockProject;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.extensions.ExtensionsArea;
import com.intellij.openapi.fileTypes.FileTypeExtensionPoint;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.augment.PsiAugmentProvider;
import com.intellij.psi.augment.TypeAnnotationModifier;
import com.intellij.psi.compiled.ClassFileDecompilers;
import com.intellij.psi.impl.JavaClassSupersImpl;
import com.intellij.psi.impl.PsiElementFinderImpl;
import com.intellij.psi.impl.PsiNameHelperImpl;
import com.intellij.psi.impl.PsiTreeChangePreprocessor;
import com.intellij.psi.impl.file.impl.JavaFileManager;
import com.intellij.psi.meta.MetaDataContributor;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.stubs.BinaryFileStubBuilders;
import com.intellij.psi.util.JavaClassSupers;

import java.io.File;

public class Main {

    static class Analyzer extends PsiElementVisitor {
        static final Disposable disposable = () -> {
        };

        private static class ProjectEnvironment extends JavaCoreProjectEnvironment {
            public ProjectEnvironment(Disposable parentDisposable, CoreApplicationEnvironment applicationEnvironment) {
                super(parentDisposable, applicationEnvironment);
            }

            @Override
            protected void registerJavaPsiFacade() {
                JavaFileManager javaFileManager = getProject().getComponent(JavaFileManager.class);
                CoreJavaFileManager coreJavaFileManager = (CoreJavaFileManager) javaFileManager;
                ServiceManager.getService(getProject(), CoreJavaFileManager.class);
                getProject().registerService(CoreJavaFileManager.class, coreJavaFileManager);
                getProject().registerService(PsiNameHelper.class, PsiNameHelperImpl.getInstance());
                PsiElementFinder finder = new PsiElementFinderImpl(getProject(), coreJavaFileManager);
                ExtensionsArea area = Extensions.getArea(getProject());
                area.getExtensionPoint(PsiElementFinder.EP_NAME).registerExtension(finder);
                super.registerJavaPsiFacade();
            }

            @Override
            protected void preregisterServices() {
                super.preregisterServices();
                ExtensionsArea area = Extensions.getArea(getProject());
                CoreApplicationEnvironment.registerExtensionPoint(area, PsiTreeChangePreprocessor.EP_NAME, PsiTreeChangePreprocessor.class);
                CoreApplicationEnvironment.registerExtensionPoint(area, PsiElementFinder.EP_NAME, PsiElementFinder.class);
            }
        }

        private static class ApplicationEnvironment extends JavaCoreApplicationEnvironment {

            public ApplicationEnvironment(Disposable parentDisposable) {
                super(parentDisposable);
                myApplication.registerService(JavaClassSupers.class, new JavaClassSupersImpl());
            }
        }

        final ApplicationEnvironment applicationEnvironment;
        final ProjectEnvironment projectEnvironment;

        public Analyzer() {
            ExtensionsArea rootArea = Extensions.getRootArea();
            CoreApplicationEnvironment.registerExtensionPoint(rootArea, BinaryFileStubBuilders.EP_NAME, FileTypeExtensionPoint.class);
            CoreApplicationEnvironment.registerExtensionPoint(rootArea, FileContextProvider.EP_NAME, FileContextProvider.class);
            CoreApplicationEnvironment.registerExtensionPoint(rootArea, MetaDataContributor.EP_NAME, MetaDataContributor.class);
            CoreApplicationEnvironment.registerExtensionPoint(rootArea, PsiAugmentProvider.EP_NAME, PsiAugmentProvider.class);
            CoreApplicationEnvironment.registerExtensionPoint(rootArea, JavaMainMethodProvider.EP_NAME, JavaMainMethodProvider.class);
            CoreApplicationEnvironment.registerExtensionPoint(rootArea, ContainerProvider.EP_NAME, ContainerProvider.class);
            CoreApplicationEnvironment.registerExtensionPoint(rootArea, ClassFileDecompilers.EP_NAME, ClassFileDecompilers.Decompiler.class);
            CoreApplicationEnvironment.registerExtensionPoint(rootArea, TypeAnnotationModifier.EP_NAME, TypeAnnotationModifier.class);
            applicationEnvironment = new ApplicationEnvironment(disposable);
            projectEnvironment = new ProjectEnvironment(disposable, applicationEnvironment);
        }

        public void add(final String[] args) throws Exception {
            for (String arg : args) {
                final VirtualFile root = applicationEnvironment.getLocalFileSystem().findFileByIoFile(new File(arg));
                projectEnvironment.addSourcesToClasspath(root);
            }
        }


        public void run() {
            MockProject project = projectEnvironment.getProject();
            PsiClass cls = project.getComponent(JavaFileManager.class)
                    .findClass("ResolutionTest", GlobalSearchScope.projectScope(project));
            if (cls != null) {
                PsiMethod[] methods = cls.findMethodsByName("test", false);
                if (methods.length == 1) {
                    PsiMethod method = methods[0];
                    for (PsiStatement s : method.getBody().getStatements()) {
                        System.out.println(s.getNode().getText());
                        process(s);
                    }
                }
            }
        }

        private void process(PsiMethodCallExpression expression) {
            PsiExpression qualifierExpression = expression.getMethodExpression().getQualifierExpression();
            if (qualifierExpression instanceof PsiMethodCallExpression) {
                process((PsiMethodCallExpression) qualifierExpression);
            } else if (qualifierExpression instanceof PsiReference) {
                System.out.println("Resolving reference " + qualifierExpression.getText());
                PsiElement targetElement = ((PsiReference) qualifierExpression).resolve();
                if (targetElement == null) {
                    System.out.println("Resolution failed");
                } else if (targetElement instanceof PsiClass) {
                    System.out.println("Class " + ((PsiClass) targetElement).getName());
                } else if (targetElement instanceof PsiVariable) {
                    System.out.println("Variable " + ((PsiVariable) targetElement).getTypeElement().getText());
                }
            }

            System.out.println("Resolving method " + expression.getMethodExpression().getText());
            PsiMethod method = expression.resolveMethod();
            if (method == null) {
                System.out.println("Resolution failed");
            } else {
                PsiClass clazz = method.getContainingClass();
                System.out.println(clazz.getName() + "." + method.getName());
            }
        }

        private void process(PsiExpression e) {
            if (e instanceof PsiMethodCallExpression) {
                process((PsiMethodCallExpression) e);
            }
        }

        private void process(PsiStatement s) {
            if (s instanceof PsiExpressionStatement) {
                process(((PsiExpressionStatement) s).getExpression());
            }
        }
    }

    public static void main(String[] args) {
        try {
            Analyzer analyzer = new Analyzer();
            analyzer.add(args);
            analyzer.run();
        } catch (Exception e) {
            e.printStackTrace(System.out);
        }
    }
}

并输出:

verify(mock).simpleMethod();
Resolving method verify
ResolutionTest.verify
Resolving method verify(mock).simpleMethod
Resolution failed

为了使这个示例工作,我必须通过 projectEnvironment.addJarToClassPath(file); 添加 rt.jar - 不幸的是,我仍然在 mockito 中遇到 2 个方法解析失败,我无法创建重现问题的小样本。关于 rt.jar 的信息可能对某些人有用,因此我将其添加为答案。

有问题的功能:

@Test
public void any_should_be_actual_alias_to_anyObject() {
    mock.simpleMethod((Object) null);

    verify(mock).simpleMethod(any());
    verify(mock).simpleMethod(anyObject());
}

我目前对一个问题的理解:any() return 是通用的,simpleMethod 有多个重载,解析器无法选择合适的一个,但想法本身能够 select 合适的变体。

P.S。将 java 语言级别设置为 6 后(如在 mockito 源中)——不再有失败。