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

