华为云CodeNavi(VSCode IDE版本)DSL规则编写指南

举报
yd_296685231 发表于 2024/06/26 18:20:45 2024/06/26
【摘要】 本文档介绍如何编写CodeNavi的自定义规则

1. 规则简介

1.1 节点查询规则

节点查询规则可以筛选出特定的程序元素,在Kirin中我们称这些程序元素为节点。例如:方法定义节点、方法调用表达式节点、类定义节点。

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, ornot三个关联词。

逻辑连接词需要和括号一同使用,括号中为具体的查询条件。其中,andor可以作用于多个查询条件(即括号中可以包含多个查询条件,以逗号分隔开),not只能作用于一个查询条件。逻辑连接词是一类作用于查询条件的特殊运算符(普通的运算符作用于节点或属性),其具体含义如下所示:

  • and:括号内的查询条件必须都为真
  • or:括号内的查询条件至少有一个为真
  • not:括号内的查询条件为假

需要注意的是,逻辑连接词作用的查询条件也可以包含逻辑连接词。例如,如下规则筛选出名称为debugfoo且参数数量为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”
  • 作用于布尔属性时,右侧为布尔常量truefalse。例如: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 布尔属性

布尔属性的值可以为布尔常量truefalse。布尔属性可以单独使用。单独使用时,布尔属性隐式等价于一个包含==的通用条件表达式。

例如,recordDeclaration where isPublic; 等价于 recordDeclaration where isPublic == true;

4.4 对象属性

对象属性的值可能是整数、小数、字符串或布尔常量。

4.5 节点属性

节点属性自身是一个节点,它可以有自己的属性。

4.5.1 别名

节点或属性可以有自己的别名,别名可以搭配is运算符使用。别名的命名规则如下:

  • 只能包含大小写英文字母和数字;
  • 数字只能出现在末尾;
  • 不能和Kirin内置的节点或属性同名。

例如: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 节点属性清单

基础节点
类型节点
通用属性
修饰符属性

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。