使用golang的AST编写定制化lint

2024-09-03 17:20

本文主要是介绍使用golang的AST编写定制化lint,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

什么是lint

(来自wiki)在计算机科学中,lint是一种工具程序的名称,它用来标记源代码中,某些可疑的、不具结构性(可能造成bug)的段落。它是一种静态程序分析工具,最早适用于C语言,在UNIX平台上开发出来。后来它成为通用术语,可用于描述在任何一种计算机程序语言中,用来标记源代码中有疑义段落的工具。

什么是AST

(来自wiki)在计算机科学中,抽象语法树Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。之所以说语法是“抽象”的,是因为这里的语法并不会表示出真实语法中出现的每个细节。比如,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现;而类似于 if-condition-then 这样的条件跳转语句,可以使用带有三个分支的节点来表示。

(来自网络)在Go语言中,AST是通过Go语言的内置包go/ast来实现的。该包提供了一系列类型和函数,可以用于生成和操作AST。

使用 ast 包提供的一些函数,我们可以非常方便地将如下的规则字符串:

orders > 10000 && driving_years > 5

解析成一棵这样的二叉树:

规则二叉树

其中,ast.BinaryExpr 代表一个二元表达式,它由 X 和 Y 以及符号 OP 三部分组成。最上面的一个 BinaryExpr 表示规则的左半部分和右半部分相与。

很明显,左半部分就是:orders > 10000,而右半部分则是:driving_years > 5。神奇的是,左半部分和右半部分恰好又都是一个二元表达式。

左半部分的 orders > 10000 其实也是最小的叶子节点,它可以算出来一个 bool 值。把它拆开来之后,又可以分成 X、Y、OP。X 是 orders,OP 是 “>",Y 则是 “10000”。其中 X 表示一个标识符,是 ast.Ident 类型,Y 表示一个基本类型的字面量,例如 int 型、字符串型……是 ast.BasicLit 类型。

右半部分的 driving_years > 18 也可以照此拆分。

然后,从 json 中取出这个司机的 orders 字段的值为 100000,它比 10000 大,所以左半部分算出来为 true。同理,右半部分算出来也为 true。最后,再算最外层的 “&&",结果仍然为 true。

至此,直接根据规则字符串,我们就可以算出来结果。

如果写成程序的话,就是一个 dfs 的遍历过程。如果不是叶子结点,那就是二元表达式结点,那就一定有 X、Y、OP 部分。递归地遍历 X,如果 X 是叶子结点,那就结束递归,并计算出 X 的值……

我们可以使用AST做什么

AST一般可以被用来做linter。现在常用的golangci-lint已经集成了一些linter了,但是我们有可能会有一些自己定制化的代码静态检查规则,就可以通过自己解析AST来实现。

之前在对接现网问题和做code review的时候,发现有可能有些编码习惯会导致一些问题,其中一个问题就是遇到某个接口报错时,返回出来的异常里面却没有包含异常的详情,导致问题无法定位,大致可能是这样的代码:

err := someFunc()
if err != nil {return fmt.Errorf("call someFunc failed")
}

上面这段代码里面,调用someFunc这个接口出错了,但是最后返回的异常里面却没有包含具体的错误,最终可能会导致问题无法被定位。

实际案例就是之前定位线上告警邮件通知时,所有的参数错误都被包装成一个“参数错误”的异常被跑出来,但是具体的参数错误就没有被包含了,所以当时通过日志是完全没法定位具体是哪个参数报错了。所以正确的代码应该是这样

err := someFunc()
if err != nil {return fmt.Errorf("call someFunc failed, %s", err) // 或者是 return err之类的,总之return的内容里面一定得包含err才行
}

所以基于上面的内容,我们可以使用AST做的就是静态检查是否有类似的错误,这种错误就可以被抽象为:

在err != nil的if分支内,是否有return语句未包含err相关信息

我们可以把这个规则命名为NotReturnErr检查。

但是这个规则也不完善,因为有可能有这种情况:

err := someFunc()
if err != nil {errMsg := fmt.Sprintf("call someFunc failed, %s", err)return fmt.Errorf("%s", errMsg)
}

这种如果要通过规则去检查可能就会复杂一点,目前选择扫描出来之后通过人工去筛查一下是否有这种误判的情况了。

如何实现NotReturnErr检查

基本概念

先具体介绍一些这里可能会用到的AST的具体概念:

ast.Node

在Go语言的抽象语法树(AST)中,Node是所有AST节点类型的接口。所有的AST节点,无论是表达式、声明、语句,还是其他类型的节点,都实现了Node接口。这个接口定义如下:

 
type Node interface {// Pos方法返回节点的第一个字符的位置。Pos() token.Pos// End方法返回节点的最后一个字符的下一个位置。End() token.Pos
}

ast.IfStmt

ast.Node的实现之一,If Statement的缩写,代表一个if语句,其定义如下:

type IfStmt struct {If   token.Pos // "if"的位置Init Stmt      // 初始化语句;可能为空Cond Expr      // 条件表达式Body *BlockStmt // "then"部分Else Stmt      // "else"部分;可能为空
}

ast.BinaryExpr

在Go语言的抽象语法树(AST)中,BinaryExpr代表一个二元表达式。二元表达式是一个包含两个操作数和一个操作符的表达式。例如,a + bc * de == f都是二元表达式。
在Go语言的AST中,BinaryExpr是一个结构体,其定义如下:

type BinaryExpr struct {X     Expr // 左操作数OpPos token.Pos // 操作符的位置Op    token.Token // 操作符Y     Expr // 右操作数
}

ast.Ident

在Go语言的抽象语法树(AST)中,ast.Ident代表一个标识符。标识符在编程中广泛使用,它可以是变量名、函数名、类型名等。

在Go语言的AST中,ast.Ident是一个结构体,其定义如下:

type Ident struct {NamePos token.Pos // 标识符的位置Name    string    // 标识符的名字Obj     *Object   // 对应的对象;可能为空
}

ast.ReturnStmt

在Go语言的抽象语法树(AST)中,ReturnStmt代表一个return语句。return语句用于从函数中返回,并且可以携带返回值。

在Go语言的AST中,ReturnStmt是一个结构体,其定义如下:

type ReturnStmt struct {Return  token.Pos // "return"的位置Results []Expr    // 返回值列表;可能为空
}

ast.CallExpr

在Go语言的抽象语法树(AST)中,CallExpr代表一个函数调用表达式。函数调用表达式是一种特殊的表达式,它表示对一个函数的调用。

在Go语言的AST中,CallExpr是一个结构体,其定义如下:

type CallExpr struct {Fun      Expr      // 被调用的函数Lparen   token.Pos // 左括号的位置Args     []Expr    // 函数调用的参数列表Ellipsis token.Pos // 省略号的位置(如果存在)Rparen   token.Pos // 右括号的位置
}

ast.SelectorExpr

在Go语言的抽象语法树(AST)中,SelectorExpr代表一个选择器表达式。选择器表达式用于访问结构体的字段或者调用包的函数或变量。

在Go语言的AST中,SelectorExpr是一个结构体,其定义如下:

type SelectorExpr struct {X   Expr   // 表达式Sel *Ident // 选择器
}

实现逻辑

实现逻辑用一句话概括就是,遍历目录下的所有go文件(除vendor以外),然后对所有遍历的文件生成AST语法树,找到同时满足以下条件的AST节点:

  1. 所处函数为返回值有error的函数
  2. 有if语句,且if语句包含err != nil的判断
  3. if语句的body里有return语句

判断此节点是否返回error相关内容,例如return err或者return fmt.Errorf("%s", err.Error()),如果没有的话就打印记录。

代码仓

运行效果

然后就可以根据具体的情况来看是否需要对当前代码进行修改,我们以这个为例:

打开具体文件发现内容如下所示:

这种如果不是逻辑上就需要忽略这个err的话,那么这里可能就会有问题,这种就是需要排查是否需要修改的。

ChangeLog

下一步计划

  • 优化检测逻辑,当前会检测到一些error派生的类生成的新error,这种暂时没法识别成error
  • 计划将此lint和其他lint看能否集成到gitlab提交代码流程内,

这篇关于使用golang的AST编写定制化lint的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1133565

相关文章

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

pdfmake生成pdf的使用

实际项目中有时会有根据填写的表单数据或者其他格式的数据,将数据自动填充到pdf文件中根据固定模板生成pdf文件的需求 文章目录 利用pdfmake生成pdf文件1.下载安装pdfmake第三方包2.封装生成pdf文件的共用配置3.生成pdf文件的文件模板内容4.调用方法生成pdf 利用pdfmake生成pdf文件 1.下载安装pdfmake第三方包 npm i pdfma

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

git使用的说明总结

Git使用说明 下载安装(下载地址) macOS: Git - Downloading macOS Windows: Git - Downloading Windows Linux/Unix: Git (git-scm.com) 创建新仓库 本地创建新仓库:创建新文件夹,进入文件夹目录,执行指令 git init ,用以创建新的git 克隆仓库 执行指令用以创建一个本地仓库的

【北交大信息所AI-Max2】使用方法

BJTU信息所集群AI_MAX2使用方法 使用的前提是预约到相应的算力卡,拥有登录权限的账号密码,一般为导师组共用一个。 有浏览器、ssh工具就可以。 1.新建集群Terminal 浏览器登陆10.126.62.75 (如果是1集群把75改成66) 交互式开发 执行器选Terminal 密码随便设一个(需记住) 工作空间:私有数据、全部文件 加速器选GeForce_RTX_2080_Ti

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念