华为云CodeNavi(VSCode IDE版本)DSL规则编写指南
1. 规则简介
1.1 节点查询规则
节点查询规则可以筛选出特定的程序元素,在CodeNavi中我们称这些程序元素为节点。例如:方法定义节点、方法调用表达式节点、类定义节点。
1.1.1 简单示例
以下是一个节点查询规则的示例:
functionDeclaration fd1 where
and(fd1.hasBody,
fd1.name startWith "debug",
fd1.parameters.size() == 1,
fd1.parameters[0].type.name == "java.util.List");
上述规则将筛选出用户代码中所有名称以debug开头且只包含一个java.util.List类型参数的方法。
注意事项:
- 该规则的查询条件是一个包含四个子条件并由and逻辑连接词链接起来的复合条件。每个子条件都通过具体的运算符作用于节点或其属性。
- 规则中的hasBody, name, parameters为节点的属性。hasBody为布尔属性,name为字符串属性,parameters为集合属性。关于属性的种类,我们将在属性一章详细介绍。
- fd1是functionDeclaration节点的别名。在编写DSL规则时,定义别名可以显著增强规则的可读性,建议大家为每个节点都定义一个别名。
- 规则中的startWith, ==是运算符,通过运算符筛选节点。(布尔属性hasBody隐式蕴含了一个运算符,我们将在布尔属性一节对其进行详细介绍)
- 某些属性可以有自己的属性,在DSL中我们称这类属性为节点属性。它们隶属于某节点,同时自身也是一个节点。我们可以通过.关键词获取节点属性的属性。parameters[0].type.name首先获取节点属性parameters[0]的属性type。后者也是一个节点属性,我们再获取type的属性name。
1.1.2 针对节点属性的查询
如果节点的属性也是节点,我们也可以编写规则筛选节点的属性。
functionCall.arguments fa where
and(fa.argumentIndex == 1,
fa.type.name == "java.lang.String");
在以上规则中,我们筛选出方法调用的类型为String的第二个参数。
1.1.3 嵌套查询
节点的属性也可以是一个节点,针对节点属性,我们可以编写嵌套查询规则。
functionCall fc1 where
and(fc1.name == "hello",
fc1.enclosingFunction ef where
ef contain functionCall fc2 where
and(fc2.arguments[0].variable is fc1.arguments[0].variable,
fc2 isnot fc1));
在以上规则中,我们针对ef和fc2分别编写了子查询规则。
1.1.4 递归查询
我们可以通过星号 *编写递归查询规则。
functionDeclaration fd where
fd.(functionCalls.function)* targetFd where
targetFd is fd;
在以上规则中,functionCalls.function会被反复调用,直到targetFd满足特定的查询条件。
如果待递归的节点属性只有一个,括号可以省略。例如:
functionCall fc where fc.arguments* == null;
1.1.5 字符串操作
我们内置了工具函数以便对字符串属性进行更复杂的查询操作。
functionDeclaration fd where
or(fd.name + "world" == "helloworld",
"hello" + fd.name == "helloworld",
"hell" + fd.name + "orld" == "helloworld",
fd.name.capitalize() == "hello" + fd.enclosingClass.name.capitalize(),
fd.name.toUpperCase().toLowerCase() == "lower");
2. 逻辑连接词
逻辑连接词是一种作用于查询条件的关键词。DSL支持and, or和not三个关联词。
逻辑连接词需要和括号一同使用,括号中为具体的查询条件。其中,and和or可以作用于多个查询条件(即括号中可以包含多个查询条件,以逗号分隔开),not只能作用于一个查询条件。逻辑连接词是一类作用于查询条件的特殊运算符(普通的运算符作用于节点或属性),其具体含义如下所示:
- and:括号内的查询条件必须都为真
- or:括号内的查询条件至少有一个为真
- not:括号内的查询条件为假
需要注意的是,逻辑连接词作用的查询条件也可以包含逻辑连接词。例如,如下规则筛选出名称为debug或foo且参数数量为1或3的方法。
functionDeclaration fd where
and(or(fd.name == "debug", fd.name == "foo"),
or(fd.parameters.size() == 1, fd.parameters.size() == 3));
3. 运算符
查询条件通过运算符作用于具体的节点或属性从而完成节点筛选。DSL支持的运算符包括:>(<=), <(>=), !, [], ==(!=), startWith(notStartWith), endWith(notEndWith), contain(notContain), match(notMatch), is(isnot), in(notIn)。括号内的运算符为相应运算符的反运算符,在语义上等价于not连接词。
根据运算符作用的属性类别,我们可以将运算符分为通用运算符、算术运算符、字符串运算符、布尔运算符、节点运算符。
3.1 通用运算符(==, contain)
==(!=)运算符可以作用于数字属性、字符串属性、布尔属性、对象属性、节点属性。无论作用于何种属性,运算符的左侧都必须为属性,右侧为常量。
- 作用于数字属性时,右侧为整数或小数。例如:arguments.size() == 3
- 作用于字符串属性时,右侧为字符串常量。例如:name == “foo”
- 作用于布尔属性时,右侧为布尔常量true或false。例如:isPublic == true
- 作用于对象属性时,右侧为整数、小数、字符串或布尔常量。例如:value == 10
- 作用于节点属性时,右侧为空常量null。例如:initializer == null
contain(notContain)运算符可以作用于字符串属性、节点集合属性。
- 作用于字符串属性时,右侧为字符串常量,当常量为属性的子串时返回真。例如:recordDeclaration where name contain “debug”;返回名字中包含debug的类声明节点
- 作用于节点集合属性时,右侧为查询条件,当集合属性中包含满足该条件的节点时返回真。例如:functionDeclaration where parameters contain param where param.name == “i1”;返回包含名为i1参数的方法声明
3.2 算术运算符(>, <)
算术运算符作用于数字属性和对象属性,其含义显而易见,此处不做赘述。
3.3 字符串运算符(startWith, endWith, match)
字符串运算符作用于字符串属性和对象属性,运算符右侧为字符串常量。
- functionDeclartion where name startWith “debug”;筛选出名字以debug开头的方法声明
- functionDeclartion where name endWith "hello"筛选出名字以hello结尾的方法声明
- functionDeclaration where name match ".* (login). * "筛选出名字匹配正则表达式.* (login).* 的方法声明
3.4 布尔运算符(!)
! 运算符位于布尔属性的左侧,表示布尔属性值为false。例如:recordDeclaration where !isPublic等价于recordDelaration where isPublic == false。
3.5 节点运算符(contain, in, is)
节点运算符的左侧可以是节点属性或别名。
3.5.1 contain
contain用于查询语法树结构上的子节点。
例如,以下规则筛选出方法体内调用了名为foo的无参方法的方法声明。
functionDeclaration fd where
fd contain functionCall where
and(name == "foo",
arguments.size() == 0));
3.5.2 in
in的语义和contain相反。in查询语法树结构上的父节点。
例如,以下规则筛选出位于赋值表达式中的数据成员访问节点。
fieldAccess fa1 where
fa1 in assignStatement;
3.5.3 is
is的右侧可以是节点属性或别名。
例如,以下规则筛选出出现在赋值表达式左侧的变量访问节点,且赋值表达式的右侧为方法调用节点。规则中的va是节点variableAccess的别名。
variableAccess va where
va in assignStatement where
and(lhs is va,
rhs is functionCall);
4. 属性
DSL引擎通过运算符筛选出属性满足特定条件的节点。属性包括数字、字符串、布尔、对象、节点、集合六种。其中,集合属性又可分为数字集合、字符串集合、布尔集合、对象集合、节点集合五种子类型。
4.1 数字属性
数字属性的值可以为整数或小数。
4.2 字符串属性
字符串属性的值可以为字符串常量。
4.3 布尔属性
布尔属性的值可以为布尔常量true或false。布尔属性可以单独使用。单独使用时,布尔属性隐式等价于一个包含==的通用条件表达式。
例如,recordDeclaration where isPublic; 等价于 recordDeclaration where isPublic == true;
4.4 对象属性
对象属性的值可能是整数、小数、字符串或布尔常量。
4.5 节点属性
节点属性自身是一个节点,它可以有自己的属性。
4.5.1 别名
节点或属性可以有自己的别名,别名可以搭配is运算符使用。别名的命名规则如下:
- 只能包含大小写英文字母和数字;
- 数字只能出现在末尾;
- 不能和CodeNavi内置的节点或属性同名。
例如:va1、fd、Field1都是合法的别名,1fc、v2a是非法别名。 另外,节点集合属性的查询条件必须包括别名。
定义别名后,我们可以进行更加复杂的查询运算。如下代码中,我们为functionCall和ifBlock都定义了别名,通过别名比较了二者的起始行号。
显然,如果不定义别名,我们无法完成类似的查询。
functionCall fc where
fc.enclosingFunction contain ifBlock ifb where
ifb.startLine < fc.startLine;
注:为了提升代码的清晰度,建议为所有包含查询条件的节点和属性定义别名
4.6 节点集合属性
节点集合属性和contain连用时必须包含别名,如下所示:
functionDeclaration fd1 where
fd1.parameters contain param where
param.name startWith "register";
5. 关键词和常量
5.1 索引关键词([])
通过索引关键词可以获取集合中相应下标的元素。示例规则中我们通过索引关键词获取了方法第一个参数。需要注意的是,索引下标从0开始。
5.2 别名关键词(as)
在使用别名时,可以通过as关键词显式定义别名,例如:functionCall as fc1 where fc1.name == “helloWorld”;。
注:该关键词是可选项,等价于functionCall fc1 where fc1.name == “helloWorld”;。
5.3 size()关键词
通过size()可以获取属性的数量,一般用于集合属性。示例规则中我们通过size()获取了方法参数的数量。用于非集合属性时,我们可以判断某属性是否存在。例如:
fieldDeclaration where initializer.size() == 0;筛选出没有显示初始化的数据成员声明。
5.4 空常量(null)
空常量指代的是源码中的null。例如:private String field = null、str = null。在使用时,空常量往往和==(!=)一同出现。例如:
fieldDeclaration fd where fd.initializer == null;
assignStatement as1 where as1.rhs == null;
5.5 数值常量
DSL中的数值常量可以为整数或小数。
5.6 字符串常量
字符串常量支持大小写英文字母、数字、以及正则表达式特殊字符。
6 节点属性清单
- 点赞
- 收藏
- 关注作者
评论(0)