You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Idea插件中JavaRecursiveElementWalkingVisitor的visitMethodCallExpression未触发

问题描述

我基于JavaRecursiveElementWalkingVisitor实现了AnalyzeVisitor子类,传入动作执行后,日志显示已访问到PsiMethodCallExpression:commentRepository.delete(comment)(实际类型为com.intellij.psi.impl.source.tree.java.PsiMethodCallExpressionImpl)这类元素,但visitMethodCallExpression方法始终没有执行(无"methodCall visited"日志),visitForeachStatement方法也存在同样问题。我尝试修改visitElement方法,手动判断元素类型并调用对应访问方法,但依然无效,怀疑是PsiMethodCallExpression属于LeafPsiElement导致的。请问问题原因是什么?如何解决?

访问者实现代码

package mypackage

import com.intellij.psi.JavaRecursiveElementWalkingVisitor
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiForStatement
import com.intellij.psi.PsiForeachStatement
import com.intellij.psi.PsiMethodCallExpression

class AnalyzeVisitor: JavaRecursiveElementWalkingVisitor() {
    private var forVisited: Boolean = false

    override fun visitElement(element: PsiElement) {
        println("VISITED $element with class ${element::class.java}")
        super.visitElement(element)
    }

    override fun visitForStatement(statement: PsiForStatement) {
        forVisited = true
        println("forStatement visited '$statement'")
        super.visitForStatement(statement)
    }

    override fun visitForeachStatement(statement: PsiForeachStatement?) {
        forVisited = true
        println("foreachStatement visited'$statement'")
        super.visitForeachStatement(statement)
    }

    override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
        println("methodCall visited '$expression'")
        if (forVisited) {
            println("visited")
            // some logic
        }
        super.visitMethodCallExpression(expression)
    }
}

目标分析的Java服务代码

package another.service;

import org.springframework.stereotype.Service;
import ru.itmo.wp.domain.Comment;
import ru.itmo.wp.repository.CommentRepository;

import java.util.List;

@Service
public class CommentService {
    private final CommentRepository commentRepository;

    public CommentService(CommentRepository commentRepository) {
        this.commentRepository = commentRepository;
    }

    public void saveComment(Comment comment) {
        commentRepository.save(comment);
    }

    public boolean deleteCommentAndDoLogic(Comment comment) {
        commentRepository.delete(comment);
        System.out.println("Comment deleted " + comment.getText());
        return true;
    }

    public void deleteCommentsSimple(List<Comment> comments) {
        for (Comment comment: comments) {
            commentRepository.delete(comment);
        }
    }

    public void deleteCommentsInner(List<Comment> comments) {
        for (Comment comment: comments) {
            boolean t = deleteCommentAndDoLogic(comment);
        }
    }
}

尝试过的visitElement修改代码

override fun visitElement(element: PsiElement) {
        println("VISITED $element with class ${element::class.java}")
        when(element) {
            is PsiMethodCallExpression -> {
                visitMethodCallExpression(element)
            }
            is PsiForStatement -> {
                visitForStatement(element)
            }
            is PsiForeachStatement -> {
                println("FOUND FOREACH")
                visitForeachStatement(element)
            }
        }
        if (element is PsiMethodCallExpression) {
            visitMethodCallExpression(element)
        }
        super.visitElement(element)
    }

问题原因与解决方案

核心原因

你使用的JavaRecursiveElementWalkingVisitor默认逻辑是直接遍历当前元素的所有子节点,不会通过PSI元素的accept机制触发对应类型的visitXXX方法。也就是说,即使遇到PsiMethodCallExpression或PsiForeachStatement这类复合元素,它也会跳过直接调用对应visit方法的步骤,直接进入子元素遍历,导致你重写的visitMethodCallExpression等方法无法被触发。

你手动在visitElement中判断类型的尝试无效,是因为super.visitElement会优先遍历子元素,且没有正确结合accept机制触发类型匹配的方法。

解决方案

方案一:替换父类为JavaRecursiveElementVisitor

这是最直接的解决方式,JavaRecursiveElementVisitor的核心逻辑是通过PSI元素的accept方法触发对应类型的visitXXX方法,同时自动递归遍历子元素,完全匹配你的需求:

修改后的访问者代码:

package mypackage

import com.intellij.psi.JavaRecursiveElementVisitor
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiForStatement
import com.intellij.psi.PsiForeachStatement
import com.intellij.psi.PsiMethodCallExpression

class AnalyzeVisitor: JavaRecursiveElementVisitor() {
    private var forVisited: Boolean = false

    override fun visitElement(element: PsiElement) {
        println("VISITED $element with class ${element::class.java}")
        super.visitElement(element)
    }

    override fun visitForStatement(statement: PsiForStatement) {
        forVisited = true
        println("forStatement visited '$statement'")
        super.visitForStatement(statement)
    }

    override fun visitForeachStatement(statement: PsiForeachStatement) {
        forVisited = true
        println("foreachStatement visited'$statement'")
        super.visitForeachStatement(statement)
    }

    override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
        println("methodCall visited '$expression'")
        if (forVisited) {
            println("visited")
            // some logic
        }
        super.visitMethodCallExpression(expression)
    }
}

注:此处将visitForeachStatement的参数从可空类型改为非空,因为JavaRecursiveElementVisitor中的该方法参数为非空,符合PSI元素的实际存在情况。

方案二:若必须使用JavaRecursiveElementWalkingVisitor

需要手动在visitElement中先调用当前元素的accept方法,触发类型匹配的visitXXX方法,再调用父类方法遍历子元素:

修改后的visitElement方法:

override fun visitElement(element: PsiElement) {
    println("VISITED $element with class ${element::class.java}")
    // 先触发当前元素的类型匹配visit方法
    element.accept(this)
    // 再遍历子元素
    super.visitElement(element)
}

确保你的visitXXX方法中调用super.visitXXX以维持递归逻辑(你已实现这一点)。

验证效果

使用上述任一方案后,触发动作时就能看到methodCall visited和foreachStatement visited的日志输出,对应方法会被正确执行。

内容的提问来源于stack exchange,提问作者Nikita I

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 21:09:57