IDEA Structural Replace 使用心得

最近项目有一个重构的需求,纯靠文本替换无法完成,想到了用 IDEA Structural Replace。这次由于使用场景比较复杂,用到了 script,记录一下使用心得。

Structural Replace 介绍

Structural Replace (下文简称 SR),与普通文本替换的区别可见官方介绍:

A conventional search process does not take into account the syntax and semantics of the source code. Even if you use regular expressions, IntelliJ IDEA still treats your code as a regular text. The structural search and replace (SSR) actions let you search for a particular code pattern or grammatical construct in your code considering your code structure.

简而言之,它可以按语法结构化搜索并替换。所谓的结构搜索,是以它内部的 PSI 为单位进行搜索。PSI 指Program Structure Interface,是 IDEA 内部解析索引代码文件的结构。

简单使用

如下代码:

1
2
3
4
public void getOrDefault() {
Map<Integer, String> mp = new HashMap<>();
mp.getOrDefault(10, "666");
}

需要将所有的 getOrDefault 替换成以下模板方法:

1
2
3
4
5
6
7
public static <K, V> V getOrDefault(Map<K, V> mp, K key, V defaultVal) {
V v = mp.get(key);
if (v == null) {
return defaultVal;
}
return v;
}

使用结构化查询如下:
simple_sr.png

搜索匹配需要注意的几个点:

  • 选中 MethodCall 模板
  • Instance 添加 Type 限制
  • MethodCall 添加 变量名限制

在替换内容输入框,用上面匹配到的变量 Instance、Parameter 进行组装替换后的内容。
点击 Find -> Replace All 就全部替换成功。

使用 Groovy Script 进行替换

上述替换用普通的文本替换也可以完成,但遇到替换结果要经过计算产生,就需要使用 Groovy Script 调用 IDEA 内部的 PSI 接口进行了。

还是上面的替换模板,只是替换输出需要增加泛型的类型:

1
2
3
4
public void getOrDefault() {
Map<Integer, String> mp = new HashMap<>();
TestStructuralReplace.getOrDefault(mp, 10, "666", Integer.class, String.class);
}

对于上述需求,搜索的模板不需要更改,只需要修改替换模板。重点是用 Groovy Script 提取出 Instance 变量的类型:

使用 PSI Viewer 查看被搜索对象的 PSI 类型

对于该搜索对象,PSI 类型是 PsiReferenceExpression。

psi_viewer.png

构造 Groovy Script 脚本替换

此替换与简单替换的区别是需要在替换面板新增两个通过计算得出的参数:key_class, value_class,这两个参数是通过 groovy script 运行得出的。如何构造出脚本见下一段段落。

groovy_search.png

从上一步查出 PSI 类型后,打开 IDEA SDK 搜索对应的源码文件 PsiReferenceExpression。

构造 key_class 参数的脚本如下:

1
2
3
def strType = Instance.getReference().resolve().getType().getCanonicalText(false)
strType = strType.substring(strType.indexOf('<') + 1, strType.indexOf(','))
return strType

第一行代码输出的字符串类型的 strType 是 java.util.Map<java.lang.Integer,java.lang.String>,这一步后就可以直接通过字符串切割获得 Integer 或 String 了。

value_class 同理。至此,替换完成。

脚本构造调试心得

构造过程中如何调试脚本?最简单的方法是写完在替换框点击 preview 看对应的输出,如果太过复杂的脚本,可以通过新建插件工程,执行 PsiVisitor 遍历对应替换对象模板时断点进行实时的脚本编写与输出。

另外留意的点,Groovy Script 也适用于 Search template,不过只能输出 true or false 进行条件判断,并不像 Replace template 可以直接参与输出结果这样灵活。

参考文档