


Cara menyesuaikan linter (alat semakan statik) dalam bahasa Go
forsword
Go
语言中常用的静态代码检查工具有golang-lint
、golint
,这些工具中已经制定好了一些规则,虽然已经可以满足大多数场景,但是有些时候我们会遇到针对特殊场景来做一些定制化规则的需求,所以本文我们一起来学习一下如何自定义linter需求;
Go语言中的静态检查是如何实现?
众所周知Go
golang-lint
, golint
, beberapa peraturan telah dirumuskan dalam ini alat, walaupun mereka sudah boleh memenuhi kebanyakan senario, Tetapi kadangkala kami menghadapi keperluan untuk membuat beberapa peraturan tersuai untuk senario khas, jadi dalam artikel ini kami akan belajar cara menyesuaikan keperluan linter 🎜🎜🎜🎜Cara melaksanakan semakan statik dalam Go; bahasa? 🎜🎜Kita semua tahuGo
bahasa ialah bahasa penyusun Bahasa tersusun dan bahasa tersusun tidak dapat dipisahkan daripada peringkat analisis leksikal, analisis sintaksis, analisis semantik, pengoptimuman, dan penyusunan dan penghubung kait
Penyusun menterjemahkan bahasa peringkat tinggi ke dalam bahasa mesin, pertama sekali, analisis leksikal adalah proses menukar jujukan aksara kepada Token secara amnya dibahagikan kepada kategori ini : kata kunci , pengecam, literal (termasuk nombor, rentetan), simbol khas (seperti tanda tambah, tanda sama), jana Token
Selepas jujukan, analisis sintaks perlu dilakukan. Selepas pemprosesan selanjutnya, pokok sintaks dengan ungkapan sebagai nod dijana. Pepohon sintaks ini adalah perkara yang sering kita lakukan. sebut AST
, dalam proses menjana sintaks pepohon Anda boleh mengesan beberapa ralat formal, seperti kurungan yang hilang Selepas analisis sintaks selesai, analisis semantik diperlukan Di sini, semua semantik statik yang boleh disemak semasa tempoh penyusunan proses berikut adalah penjanaan kod perantaraan penjanaan dan pengoptimuman kod, dan memautkan , saya tidak akan menerangkannya secara terperinci di sini. Tujuan utama di sini ialah untuk memperkenalkan pokok sintaks abstrak (AST). ) mengikut peraturan tersuai; maka pokok sintaks abstrak adalah panjang Bagaimana rupanya? Kita boleh menggunakan go/ast
、go/parser
、 go/token
pakej untuk mencetakAST code>, specificKita boleh lihat contoh di bawah untuk apa yang AST
kelihatan seperti ;
// example.go
package main
func add(a, b int) int {
return a + b
}
Salin selepas log masuk*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 . . . }
Salin selepas log masuk
// example.go package main func add(a, b int) int { return a + b }
*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 . . . }
Context
,不符合该规范的我们要给出警告;好了,现在规则已经定好了,现在我们就来想办法实现它;先来一个有问题的示例:
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 Context\n", v.fset.Position(node.Pos()), funcDecl.Name.Name) return v }
对应AST
如下:
$ go run ./main.go -- ./example.go ./example.go:3:1: add function first params should be Context
方式一:标准库实现custom linter
通过上面的AST
结构我们可以找到函数参数类型具体在哪个结构上,因为我们可以根据这个结构写出解析代码如下:
. ├── firstparamcontext │ └── firstparamcontext.go ├── go.mod ├── go.sum └── testfirstparamcontext ├── example.go └── main.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 Context\n", funcDecl.Name.Name) return true } for _, f := range pass.Files { ast.Inspect(f, inspect) } return nil, nil }
通过输出我们可以看到,函数add()
第一个参数必须是Context;这就是一个简单实现,因为AST
Kaedah 1: Perpustakaan standard melaksanakan linter tersuai
AST
struktur Kita boleh mengetahui struktur mana jenis parameter fungsi dihidupkan, kerana kita boleh menulis kod parsing berdasarkan struktur ini seperti berikut : 🎜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
Parameter pertama add()
mestilah Konteks; ini ialah pelaksanaan mudah, kerana AST
Struktur ialah memang agak rumit, jadi saya tidak akan memperkenalkan setiap satu secara terperinci di sini Untuk struktur, anda boleh membaca artikel yang ditulis oleh Cao Da sebelum ini: golang dan ast🎜方式二:go/analysis
看过上面代码的朋友肯定有点抓狂了,有很多实体存在,要开发一个linter
,我们需要搞懂好多实体,好在go/analysis
进行了封装,go/analysis
为linter
提供了统一的接口,它简化了与IDE,metalinters,代码Review等工具的集成。如,任何go/analysis
linter都可以高效的被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 Context\n", 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
我们可以把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
可执行文件,加到我们的项目中就可以了;
总结
golang-cli
仓库中pkg/golinters
目录下存放了很多静态检查代码,学会一个知识点的最快办法就是抄代码,先学会怎么使用的,慢慢再把它变成我们自己的;本文没有对AST
标准库做过多的介绍,因为这部分文字描述比较难以理解,最好的办法还是自己去看官方文档、加上实践才能更快的理解。
本文所有代码已经上传:https://github.com/asong2020/Golang_Dream/tree/master/code_demo/custom_linter
Atas ialah kandungan terperinci Cara menyesuaikan linter (alat semakan statik) dalam bahasa Go. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Alat AI Hot

Undresser.AI Undress
Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover
Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool
Gambar buka pakaian secara percuma

Clothoff.io
Penyingkiran pakaian AI

Video Face Swap
Tukar muka dalam mana-mana video dengan mudah menggunakan alat tukar muka AI percuma kami!

Artikel Panas

Alat panas

Notepad++7.3.1
Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina
Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1
Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6
Alat pembangunan web visual

SublimeText3 versi Mac
Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Topik panas



Perpustakaan yang digunakan untuk operasi nombor terapung dalam bahasa Go memperkenalkan cara memastikan ketepatannya ...

Masalah Threading Giliran di GO Crawler Colly meneroka masalah menggunakan Perpustakaan Colly Crawler dalam bahasa Go, pemaju sering menghadapi masalah dengan benang dan permintaan beratur. � ...

Perbezaan antara percetakan rentetan dalam bahasa Go: perbezaan kesan menggunakan fungsi println dan rentetan () sedang ...

Masalah menggunakan redisstream untuk melaksanakan beratur mesej dalam bahasa Go menggunakan bahasa Go dan redis ...

Apa yang perlu saya lakukan jika label struktur tersuai di Goland tidak dipaparkan? Apabila menggunakan Goland untuk Pembangunan Bahasa GO, banyak pemaju akan menghadapi tag struktur tersuai ...

Pergi bahasa berfungsi dengan baik dalam membina sistem yang cekap dan berskala. Kelebihannya termasuk: 1. Prestasi Tinggi: Disusun ke dalam Kod Mesin, Kelajuan Berjalan Cepat; 2. Pengaturcaraan serentak: Memudahkan multitasking melalui goroutine dan saluran; 3. Kesederhanaan: sintaks ringkas, mengurangkan kos pembelajaran dan penyelenggaraan; 4. Cross-Platform: Menyokong kompilasi silang platform, penggunaan mudah.

Perpustakaan mana yang dibangunkan oleh syarikat besar atau projek sumber terbuka yang terkenal? Semasa pengaturcaraan di GO, pemaju sering menghadapi beberapa keperluan biasa, ...

Penghapusan automatik Golang Generik Jenis Kekangan Jenis dalam Pengguna VSCode mungkin menghadapi masalah yang aneh ketika menulis kod Golang menggunakan vscode. Bila ...
