<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
通常我們在業務專案中會藉助使用靜態程式碼檢查工具來保證程式碼質量,通過靜態程式碼檢查工具我們可以提前發現一些問題,比如變數未定義、型別不匹配、變數作用域問題、陣列下標越界、記憶體洩露等問題,工具會按照自己的規則進行問題的嚴重等級劃分,給出不同的標識和提示,靜態程式碼檢查助我們儘早的發現問題,Go語言中常用的靜態程式碼檢查工具有golang-lint、golint,這些工具中已經制定好了一些規則,雖然已經可以滿足大多數場景,但是有些時候我們會遇到針對特殊場景來做一些客製化化規則的需求,所以本文我們一起來學習一下如何自定義linter需求;
眾所周知Go語言是一門編譯型語言,編譯型語言離不開詞法分析、語法分析、語意分析、優化、編譯連結幾個階段,學過編譯原理的朋友對下面這個圖應該很熟悉:
編譯器將高階語言翻譯成機器語言,會先對原始碼做詞法分析,詞法分析是將字元序列轉換為Token序列的過程,Token一般分為這幾類:關鍵字、識別符號、字面量(包含數位、字串)、特殊符號(如加號、等號),生成Token序列後,需要進行語法分析,進一步處理後,生成一棵以 表示式為結點的 語法樹,這個語法樹就是我們常說的AST,在生成語法樹的過程就可以檢測一些形式上的錯誤,比如括號缺少,語法分析完成後,就需要進行語意分析,在這裡檢查編譯期所有能檢查靜態語意,後面的過程就是中間程式碼生成、目的碼生成與優化、連結,這裡就不詳細描述了,這裡主要是想引出抽象語法樹(AST),我們的靜態程式碼檢查工具就是通過分析抽象語法樹(AST)根據客製化的規則來做的;那麼抽象語法樹長什麼樣子呢?我們可以使用標準庫提供的go/ast、go/parser、go/token包來列印出AST,
檢視AST,具體AST長什麼樣我們可以看下文的例子;
假設我們現在要在我們團隊制定這樣一個程式碼規範,所有函數的第一個引數型別必須是Context,不符合該規範的我們要給出警告;好了,現在規則已經定好了,現在我們就來想辦法實現它;先來一個有問題的範例:
// example.go package main func add(a, b int) int { return a + b }
對應AST如下:
*ast.FuncDecl { 8 . . . Name: *ast.Ident { 9 . . . . NamePos: 3:6 10 . . . . Name: "add" 11 . . . . Obj: *ast.Object { 12 . . . . . Kind: func 13 . . . . . Name: "add" // 函數名 14 . . . . . Decl: *(obj @ 7) 15 . . . . } 16 . . . } 17 . . . Type: *ast.FuncType { 18 . . . . Func: 3:1 19 . . . . Params: *ast.FieldList { 20 . . . . . Opening: 3:9 21 . . . . . List: []*ast.Field (len = 1) { 22 . . . . . . 0: *ast.Field { 23 . . . . . . . Names: []*ast.Ident (len = 2) { 24 . . . . . . . . 0: *ast.Ident { 25 . . . . . . . . . NamePos: 3:10 26 . . . . . . . . . Name: "a" 27 . . . . . . . . . Obj: *ast.Object { 28 . . . . . . . . . . Kind: var 29 . . . . . . . . . . Name: "a" 30 . . . . . . . . . . Decl: *(obj @ 22) 31 . . . . . . . . . } 32 . . . . . . . . } 33 . . . . . . . . 1: *ast.Ident { 34 . . . . . . . . . NamePos: 3:13 35 . . . . . . . . . Name: "b" 36 . . . . . . . . . Obj: *ast.Object { 37 . . . . . . . . . . Kind: var 38 . . . . . . . . . . Name: "b" 39 . . . . . . . . . . Decl: *(obj @ 22) 40 . . . . . . . . . } 41 . . . . . . . . } 42 . . . . . . . } 43 . . . . . . . Type: *ast.Ident { 44 . . . . . . . . NamePos: 3:15 45 . . . . . . . . Name: "int" // 引數名 46 . . . . . . . } 47 . . . . . . } 48 . . . . . } 49 . . . . . Closing: 3:18 50 . . . . } 51 . . . . Results: *ast.FieldList { 52 . . . . . Opening: - 53 . . . . . List: []*ast.Field (len = 1) { 54 . . . . . . 0: *ast.Field { 55 . . . . . . . Type: *ast.Ident { 56 . . . . . . . . NamePos: 3:20 57 . . . . . . . . Name: "int" 58 . . . . . . . } 59 . . . . . . } 60 . . . . . } 61 . . . . . Closing: - 62 . . . . } 63 . . . }
通過上面的AST結構我們可以找到函數引數型別具體在哪個結構上,因為我們可以根據這個結構寫出解析程式碼如下:
package main import ( "fmt" "go/ast" "go/parser" "go/token" "log" "os" ) func main() { v := visitor{fset: token.NewFileSet()} for _, filePath := range os.Args[1:] { if filePath == "--" { // to be able to run this like "go run main.go -- input.go" continue } f, err := parser.ParseFile(v.fset, filePath, nil, 0) if err != nil { log.Fatalf("Failed to parse file %s: %s", filePath, err) } ast.Walk(&v, f) } } type visitor struct { fset *token.FileSet } func (v *visitor) Visit(node ast.Node) ast.Visitor { funcDecl, ok := node.(*ast.FuncDecl) if !ok { return v } params := funcDecl.Type.Params.List // get params // list is equal of zero that don't need to checker. if len(params) == 0 { return v } firstParamType, ok := params[0].Type.(*ast.SelectorExpr) if ok && firstParamType.Sel.Name == "Context" { return v } fmt.Printf("%s: %s function first params should be Contextn", v.fset.Position(node.Pos()), funcDecl.Name.Name) return v }
然後執行命令如下:
$ go run ./main.go -- ./example.go ./example.go:3:1: add function first params should be Context
通過輸出我們可以看到,函數add()第一個引數必須是Context;這就是一個簡單實現,因為AST的結構實在是有點複雜,就不在這裡詳細介紹每個結構體了,可以看曹大之前寫的一篇文章:golang
和 ast
看過上面程式碼的朋友肯定有點抓狂了,有很多實體存在,要開發一個linter,我們需要搞懂好多實體,好在go/analysis進行了封裝,go/analysis為linter
提供了統一的介面,它簡化了與IDE,metalinters,程式碼Review等工具的整合。如,任何go/analysislinter都可以高效的被go
vet執行,下面我們通過程式碼方式來介紹go/analysis的優勢;
新建一個專案程式碼結構如下:
. ├── firstparamcontext │ └── firstparamcontext.go ├── go.mod ├── go.sum └── testfirstparamcontext ├── example.go └── main.go
新增檢查模組程式碼,在firstparamcontext.go新增如下程式碼:
package firstparamcontext import ( "go/ast" "golang.org/x/tools/go/analysis" ) var Analyzer = &analysis.Analyzer{ Name: "firstparamcontext", Doc: "Checks that functions first param type is Context", Run: run, } func run(pass *analysis.Pass) (interface{}, error) { inspect := func(node ast.Node) bool { funcDecl, ok := node.(*ast.FuncDecl) if !ok { return true } params := funcDecl.Type.Params.List // get params // list is equal of zero that don't need to checker. if len(params) == 0 { return true } firstParamType, ok := params[0].Type.(*ast.SelectorExpr) if ok && firstParamType.Sel.Name == "Context" { return true } pass.Reportf(node.Pos(), "''%s' function first params should be Contextn", funcDecl.Name.Name) return true } for _, f := range pass.Files { ast.Inspect(f, inspect) } return nil, nil }
然後新增分析器:
package main import ( "asong.cloud/Golang_Dream/code_demo/custom_linter/firstparamcontext" "golang.org/x/tools/go/analysis/singlechecker" ) func main() { singlechecker.Main(firstparamcontext.Analyzer) }
命令列執行如下:
$ go run ./main.go -- ./example.go /Users/go/src/asong.cloud/Golang_Dream/code_demo/custom_linter/testfirstparamcontext/example.go:3:1: ''add' function first params should be Context
如果我們想新增更多的規則,使用golang.org/x/tools/go/analysis/multichecker追加即可。
我們可以把golang-cli的程式碼下載到本地,然後在pkg/golinters 下新增firstparamcontext.go,
程式碼如下:
import ( "golang.org/x/tools/go/analysis" "github.com/golangci/golangci-lint/pkg/golinters/goanalysis" "github.com/fisrtparamcontext" ) func NewfirstparamcontextCheck() *goanalysis.Linter { return goanalysis.NewLinter( "firstparamcontext", "Checks that functions first param type is Context", []*analysis.Analyzer{firstparamcontext.Analyzer}, nil, ).WithLoadMode(goanalysis.LoadModeSyntax) }
然後重新make一個golang-cli可執行檔案,加到我們的專案中就可以了;
到此這篇關於Go語言自定義linter靜態檢查工具的文章就介紹到這了,更多相關Go自定義linter內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45