From 73fc405569f3c756fe542c9875d3183be4367cf6 Mon Sep 17 00:00:00 2001 From: wangcheng Date: Mon, 29 Jan 2024 19:05:23 +0800 Subject: [PATCH] feat: gofmt lint --- internal/linters/go/gofmt/gofmt.go | 220 ++++++++++++++++++++++++ internal/linters/go/gofmt/gofmt_test.go | 26 +++ internal/linters/go/gofmt/test.txt | 59 +++++++ 3 files changed, 305 insertions(+) create mode 100644 internal/linters/go/gofmt/gofmt.go create mode 100644 internal/linters/go/gofmt/gofmt_test.go create mode 100644 internal/linters/go/gofmt/test.txt diff --git a/internal/linters/go/gofmt/gofmt.go b/internal/linters/go/gofmt/gofmt.go new file mode 100644 index 00000000..11d4445f --- /dev/null +++ b/internal/linters/go/gofmt/gofmt.go @@ -0,0 +1,220 @@ +package gofmt + +import ( + "os/exec" + "regexp" + "strconv" + "strings" + + "github.com/google/go-github/v57/github" + "github.com/qiniu/x/log" + "github.com/qiniu/x/xlog" + "github.com/reviewbot/config" + "github.com/reviewbot/internal/linters" +) + +var lintName = "gofmt" + +func init() { + linters.RegisterCodeReviewHandler(lintName, gofmtHandler) +} + +func gofmtHandler(log *xlog.Logger, linterConfig config.Linter, agent linters.Agent, event github.PullRequestEvent) (map[string][]linters.LinterOutput, error) { + //chushihua + + executor, err := NewgofmtExecutor(linterConfig.WorkDir) + if err != nil { + log.Errorf("init gofmt executor failed: %v", err) + return nil, err + } + if isEmpty(linterConfig.Args...) { + // turn off compile errors by default + linterConfig.Args = append([]string{}, "-d", "./...") + } + output, err := executor.Run(log, linterConfig.Args...) + if err != nil { + log.Errorf("golangci-lint run failed: %v", err) + return nil, err + } + parsedOutput, err := executor.Parse(log, output) + if err != nil { + log.Errorf("golangci-lint parse output failed: %v", err) + return nil, err + } + return parsedOutput, nil +} + +func isEmpty(args ...string) bool { + for _, arg := range args { + if arg != "" { + return false + } + } + + return true +} + +type Gofmt struct { + dir string + gofmt string + execute func(dir, command string, args ...string) ([]byte, error) +} + +func NewgofmtExecutor(dir string) (linters.Linter, error) { + log.Infof("gofmt executor init") + g, err := exec.LookPath("gofmt") + if err != nil { + return nil, err + } + return &Gofmt{ + dir: dir, + gofmt: g, + execute: func(dir, command string, args ...string) ([]byte, error) { + c := exec.Command(command, args...) + c.Dir = dir + log.Printf("final command: %v \n", c) + return c.Output() + }, + }, nil +} + +func (g *Gofmt) Run(log *xlog.Logger, args ...string) ([]byte, error) { + b, err := g.execute(g.dir, g.gofmt, args...) + if err != nil { + log.Errorf("gofmt run with status: %v, mark and continue", err) + return b, err + } else { + log.Infof("gofmt succeeded") + } + return b, nil +} + +func (g *Gofmt) Parse(log *xlog.Logger, output []byte) (map[string][]linters.LinterOutput, error) { + return formatGofmtOutput(log, output) +} + +//formatGofmtOutput formats the output of gofmt +//example: +// diff internal/linters/go/golangci-lint/golangci-lint.go.orig internal/linters/go/golangci-lint/golangci-lint.go +// --- internal/linters/go/golangci-lint/golangci-lint.go.orig +// +++ internal/linters/go/golangci-lint/golangci-lint.go +// @@ -17,7 +17,7 @@ + +// var lintName = "golangci-lint" + +// - // test3333 +// +// test3333 +// func init() { +// linters.RegisterCodeReviewHandler(lintName, golangciLintHandler) +// } +// @@ -33,7 +33,7 @@ +// // turn off compile errors by default +// linterConfig.Args = append([]string{}, "-debug.no-compile-errors=true", "./...") +// } +// - //test 4444444 +// + //test 4444444 +// output, err := executor.Run(log, linterConfig.Args...) +// if err != nil { +// log.Errorf("golangci-lint run failed: %v", err) + +//output: map[file][]linters.LinterOutput +// map[internal/linters/go/golangci-lint/golangci-lint.go:[ +//{internal/linters/go/golangci-lint/golangci-lint.go 24 7 +// var lintName = "golangci-lint" + +// - // test3333 +// +// test3333 +// func init() { +// linters.RegisterCodeReviewHandler(lintName, golangciLintHandler) +// }} +// {internal/linters/go/golangci-lint/golangci-lint.go 40 7 +// // turn off compile errors by default +// linterConfig.Args = append([]string{}, "-debug.no-compile-errors=true", "./...") +// } +// - //test 4444444 +// + //test 4444444 +// output, err := executor.Run(log, linterConfig.Args...) +// if err != nil { +// log.Errorf("golangci-lint run failed: %v", err) +// }]] + +func formatGofmtOutput(log *xlog.Logger, output []byte) (map[string][]linters.LinterOutput, error) { + lines := strings.Split(string(output), "\n") + var result = make(map[string][]linters.LinterOutput) + fileErr := make(map[string][]string) + var filename string + for _, line := range lines { + if strings.HasPrefix(line, "diff") { + fields := strings.Fields(line) + if len(fields) >= 3 { + filename = fields[2] + } + fileErr[filename] = []string{} + } else if filename != "" { + fileErr[filename] = append(fileErr[filename], line) + } + } + + for filename, errmsgs := range fileErr { + var message string + var locationLine, lineNumber int64 + var nonFirstTime bool + for _, errmsg := range errmsgs { + if strings.HasPrefix(errmsg, "@@") { + //添加上一组错误 + if nonFirstTime { + addGofmtOutput(result, filename, locationLine, lineNumber, message) + } + + //参数置空 + message = "" + locationLine = 0 + lineNumber = 0 + nonFirstTime = true + + //提取行信息 + re := regexp.MustCompile(`-(\d+),(\d+)`) + match := re.FindStringSubmatch(errmsg) + if len(match) > 2 { + locationLine, _ = strconv.ParseInt(match[1], 10, 64) + lineNumber, _ = strconv.ParseInt(match[2], 10, 64) + } + + } else { + message += " \n " + errmsg + } + } + nonFirstTime = false + //尾部添加一次 + addGofmtOutput(result, filename, locationLine, lineNumber, message) + + } + + return result, nil +} + +func addGofmtOutput(result map[string][]linters.LinterOutput, filename string, locationLine, lineNumber int64, message string) { + output := &linters.LinterOutput{ + File: filename, + Line: int(locationLine + lineNumber), + Column: int(lineNumber), + Message: message, + } + if outs, ok := result[output.File]; !ok { + result[output.File] = []linters.LinterOutput{*output} + } else { + // remove duplicate + var existed bool + for _, v := range outs { + if v.File == output.File && v.Line == output.Line && v.Column == output.Column && v.Message == output.Message { + existed = true + break + } + } + + if !existed { + result[output.File] = append(result[output.File], *output) + } + } + +} diff --git a/internal/linters/go/gofmt/gofmt_test.go b/internal/linters/go/gofmt/gofmt_test.go new file mode 100644 index 00000000..4f30d181 --- /dev/null +++ b/internal/linters/go/gofmt/gofmt_test.go @@ -0,0 +1,26 @@ +package gofmt + +import ( + "fmt" + "os" + "testing" + + "github.com/qiniu/x/xlog" +) + +func TestFormatGofmt(t *testing.T) { + content, err := os.ReadFile("test.txt") + if err != nil { + fmt.Println("无法读取文件:", err) + return + } + result, _ := formatGofmtOutput(&xlog.Logger{}, content) + for key, value := range result { + fmt.Printf("filename : %s \n ", key) + for _, v := range value { + + fmt.Println("message: \n", v) + } + } + fmt.Println(result) +} diff --git a/internal/linters/go/gofmt/test.txt b/internal/linters/go/gofmt/test.txt new file mode 100644 index 00000000..699336bb --- /dev/null +++ b/internal/linters/go/gofmt/test.txt @@ -0,0 +1,59 @@ +diff hunk.go.orig hunk.go +--- hunk.go.orig ++++ hunk.go +@@ -1,12 +1,12 @@ + /* + Copyright 2024 Qiniu Cloud (qiniu.com). +- ++ + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at +- ++ + http://www.apache.org/licenses/LICENSE-2.0 +- ++ + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +diff hunk_test.go.orig hunk_test.go +--- hunk_test.go.orig ++++ hunk_test.go +@@ -1,12 +1,12 @@ + /* + Copyright 2024 Qiniu Cloud (qiniu.com). +- ++ + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at +- ++ + http://www.apache.org/licenses/LICENSE-2.0 +- ++ + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +diff internal/linters/go/golangci-lint/golangci-lint.go.orig internal/linters/go/golangci-lint/golangci-lint.go +--- internal/linters/go/golangci-lint/golangci-lint.go.orig ++++ internal/linters/go/golangci-lint/golangci-lint.go +@@ -17,7 +17,7 @@ + + var lintName = "golangci-lint" + +- // test3333 ++// test3333 + func init() { + linters.RegisterCodeReviewHandler(lintName, golangciLintHandler) + } +@@ -33,7 +33,7 @@ + // turn off compile errors by default + linterConfig.Args = append([]string{}, "-debug.no-compile-errors=true", "./...") + } +- //test 4444444 ++ //test 4444444 + output, err := executor.Run(log, linterConfig.Args...) + if err != nil { + log.Errorf("golangci-lint run failed: %v", err)