Skip to content

Commit

Permalink
feat: gofmt lint
Browse files Browse the repository at this point in the history
  • Loading branch information
wangcheng committed Jan 31, 2024
1 parent 121f3c1 commit 6333df2
Show file tree
Hide file tree
Showing 4 changed files with 304 additions and 0 deletions.
219 changes: 219 additions & 0 deletions internal/linters/go/gofmt/gofmt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
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) {
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("gofmt run failed: %v", err)
return nil, err
}
parsedOutput, err := executor.Parse(log, output)
if err != nil {
log.Errorf("gofmt 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, "@@") {
//Add the previous set of error messages
if nonFirstTime {
message += "```"
addGofmtOutput(result, filename, locationLine, lineNumber, message)
}

//Parameter Reset
message = " Is your code not properly formatted?\n```"
locationLine = 0
lineNumber = 0
nonFirstTime = true

//Extract row information
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 if strings.HasPrefix(errmsg, "+") || strings.HasPrefix(errmsg, "-") {
message += " \n " + errmsg
}
}
nonFirstTime = false
//Add once to the tail
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 - 1),
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)
}
}

}
25 changes: 25 additions & 0 deletions internal/linters/go/gofmt/gofmt_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package gofmt

import (
"fmt"
"os"
"testing"

"github.com/qiniu/x/xlog"
)

func TestFormatGofmt(t *testing.T) {
content, err := os.ReadFile("../../../../testdata/gofmt_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)
}
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (

// linters import
_ "github.com/reviewbot/internal/linters/git-flow/rebase-suggestion"
_ "github.com/reviewbot/internal/linters/go/gofmt"
_ "github.com/reviewbot/internal/linters/go/staticcheck"
)

Expand Down
59 changes: 59 additions & 0 deletions testdata/gofmt_test.txt
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit 6333df2

Please sign in to comment.