91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Go語言自定義linter靜態檢查工具怎么實現

發布時間:2022-05-31 13:51:39 來源:億速云 閱讀:276 作者:iii 欄目:開發技術

今天小編給大家分享一下Go語言自定義linter靜態檢查工具怎么實現的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

    Go語言中的靜態檢查是如何實現?

    眾所周知Go語言是一門編譯型語言,編譯型語言離不開詞法分析、語法分析、語義分析、優化、編譯鏈接幾個階段,學過編譯原理的朋友對下面這個圖應該很熟悉:

    Go語言自定義linter靜態檢查工具怎么實現

    編譯器將高級語言翻譯成機器語言,會先對源代碼做詞法分析,詞法分析是將字符序列轉換為Token序列的過程,Token一般分為這幾類:關鍵字、標識符、字面量(包含數字、字符串)、特殊符號(如加號、等號),生成Token序列后,需要進行語法分析,進一步處理后,生成一棵以 表達式為結點的 語法樹,這個語法樹就是我們常說的AST,在生成語法樹的過程就可以檢測一些形式上的錯誤,比如括號缺少,語法分析完成后,就需要進行語義分析,在這里檢查編譯期所有能檢查靜態語義,后面的過程就是中間代碼生成、目標代碼生成與優化、鏈接,這里就不詳細描述了,這里主要是想引出抽象語法樹(AST),我們的靜態代碼檢查工具就是通過分析抽象語法樹(AST)根據定制的規則來做的;那么抽象語法樹長什么樣子呢?我們可以使用標準庫提供的go/ast、go/parser、go/token包來打印出AST,

    查看AST,具體AST長什么樣我們可以看下文的例子;

    制定linter規則

    假設我們現在要在我們團隊制定這樣一個代碼規范,所有函數的第一個參數類型必須是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  .  .  .  }

    方式一:標準庫實現custom linter

    通過上面的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 Context\n",
      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

    方式二:go/analysis

    看過上面代碼的朋友肯定有點抓狂了,有很多實體存在,要開發一個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 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可執行文件,加到我們的項目中就可以了。

    以上就是“Go語言自定義linter靜態檢查工具怎么實現”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    扎鲁特旗| 监利县| 东丽区| 咸丰县| 日土县| 闸北区| 偏关县| 台中县| 赤城县| 凉城县| 华亭县| 开封县| 门头沟区| 炉霍县| 政和县| 佛坪县| 贺州市| 黄山市| 教育| 阿拉善盟| 舞钢市| 叙永县| 逊克县| 潼南县| 瑞丽市| 客服| 仙游县| 分宜县| 翼城县| 奉贤区| 九江县| 乾安县| 彩票| 新田县| 祁东县| 安丘市| 宕昌县| 兴国县| 远安县| 新巴尔虎右旗| 宝坻区|