kotlin插件
上周,我们使用ANTLR生成了一个库,可以分析Kotlin代码 。 现在该使用生成的API来检查特定模式了。
API概述
让我们开始看看生成的API:
-
KotlinLexer
:执行词法分析 。 -
KotlinParser
:包装代表所有Kotlin令牌的类,并处理解析错误。 -
KotlinParserVisitor
:用于在Kotlin代码上实现Visitor模式的合同。KotlinParserBaseVisitor
是它的空实现,以简化子类的创建。 -
KotlinParserListener
:访问Kotlin代码时与回调相关的代码的合同,其中KotlinParserBaseListener
为空的实现。
![解析器词法分析器类图](https://i-blog.csdnimg.cn/blog_migrate/45edf3e6daf67f54109d561bd77c2da1.png)
类图并不是简化代码编写的最佳方法。 以下代码段是非常粗糙的分析实现。 我将使用Kotlin,但可以使用与Java互操作的任何JVM语言:
valstream=CharStreams.fromString("fun main(args : Array<String>) {}") (1)
vallexer=KotlinLexer(stream) (2)
valtokens=CommonTokenStream(lexer) (3)
valparser=KotlinParser(tokens) (4)
valcontext=parser.kotlinFile() (5)
ParseTreeWalker().apply{ (6)
walk(object: KotlinParserBaseListener(){ (7)
overridefunenterFunctionDeclaration(ctx:KotlinParser.FunctionDeclarationContext){ (8)
println(ctx.SimpleName().text) (9)
}
},context)
}
解释如下:
- 创建一个
CharStream
以便在下一行输入词法分析器。 所述CharStreams
提供了大量的静态的fromXXX()
方法中,每个接受一个不同的类型(String
,InputStream
等) - 使用流实例化词法分析器
- 在词法分析器上实例化令牌流。 该类通过词法分析器提供流功能。
- 使用令牌流实例化解析器
- 在代码中定义入口点。 在这种情况下,它是一个Kotlin文件-可能会用于该插件。
- 创建将依次访问每个节点的整体walker
- 通过调用
walk
并传递所需的行为作为对象来启动访问过程 - 覆盖所需的功能。 此处,每次输入功能节点时都会调用它
- 进行所需的操作, 例如打印功能名称
显然,第1至7行只是将所有组件连接在一起的样板。 需要实现的行为应替换第8行和第9行。
第一次简单检查
在Kotlin中,如果函数返回Unit
-不返回任何值,则显式声明其返回类型是可选的。 检查是否有这种明确的回报将是一个很好的规则。 以下代码段都是有效的Kotlin代码,它们是等效的-一个具有明确的返回类型,另一个不具有:
funhello1():Unit{
println("Hello")
}
funhello2(){
println("Hello")
}
让我们用grun
以图形方式显示解析树( grun
是在解释以前的帖子 )。 它产生以下内容:
可以看出,具有显式返回类型的代码片段在functionDeclaration下具有类型分支。 KotlinParser ANTLR语法文件中的代码片段确认了这一点:
functionDeclaration
: modifiers 'fun' typeParameters?
(type '.' | annotations)?
SimpleName
typeParameters? valueParameters (':' type)?
typeConstraints
functionBody?
SEMI*
;
该规则应检查是否存在这样的返回类型,否则它不应为Unit
。 让我们以所需的效果更新上面的代码:
ParseTreeWalker().apply{
walk(object: KotlinParserBaseListener(){
overridefunenterFunctionDeclaration(ctx:KotlinParser.FunctionDeclarationContext){
if(ctx.type().isNotEmpty()){ (1)
valtypeContext=ctx.type(0) (2)
with(typeContext.typeDescriptor().userType().simpleUserType()){ (3)
valtypeName=this[0].SimpleName()
if(typeName.symbol.text=="Unit"){ (4)
println("Found Unit as explicit return type "+ (5)
"in function ${ctx.SimpleName()} at line ${typeName.symbol.line}")
}
}
}
}
},context)
}
解释如下:
- 检查是否有显式的返回类型,无论它是什么
- 足够奇怪的是,语法允许使用多值返回类型。 就拿第一个。
- 遵循解析树,直到最终的类型名称-有关路径的图形表示,请参考上面的解析树屏幕快照。
- 检查返回类型是否为
Unit
- 在控制台中打印消息。 在下一步中,我们将在那里调用SonarQube API。
正确运行上面的代码将产生以下输出:
Found Unit as explicit return type in function hello1 at line 1
更高级的检查
在Kotlin中,以下片段都是等效的:
funhello1(name:String):String{
return"Hello $name"
}
funhello2(name:String):String="Hello $name"
funhello3(name:String)="Hello $name"
请注意,在最后一种情况下,返回类型可以由编译器推断,而由开发人员省略。 这将是一个很好的检查:对于表达式主体,应省略返回类型。 可以使用与上述相同的技术:
- 使用
grun
显示摘录中的分析树: - 检查差异。 明显:
- 如上所述,没有显式返回类型的
functionDeclaration
错过functionDeclaration
树中的type
节点 - 具有表达式主体的
functionBody
具有一个functionBody
其第一个子代为=
,第二个子代为expression
- 如上所述,没有显式返回类型的
- 请参考初始语法,以确保涵盖所有情况。
functionBody : block | '=' expression ;
- 码!
ParseTreeWalker().apply{ walk(object: KotlinParserBaseListener(){ overridefunenterFunctionDeclaration(ctx:KotlinParser.FunctionDeclarationContext){ valbodyChildren=ctx.functionBody().children if(bodyChildren.size>1 &&bodyChildren[0]isTerminalNode&&bodyChildren[0].text=="=" &&ctx.type().isNotEmpty()){ valfirstChild=bodyChildren[0]asTerminalNode println("Found explicit return type for expression body "+ "in function ${ctx.SimpleName()} at line ${firstChild.symbol.line}") } } },context) }
该代码非常不言自明,并产生以下内容:
Found explicit return type for expression body in function hello2 at line 5
kotlin插件