forked from incu6us/goimports-reviser
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix formatting files directly, add tests, improve code generation det…
…ection
- Loading branch information
Showing
4 changed files
with
300 additions
and
104 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
package main | ||
|
||
import ( | ||
"errors" | ||
"go/parser" | ||
"go/token" | ||
"io/fs" | ||
"io/ioutil" | ||
"os" | ||
"path/filepath" | ||
"regexp" | ||
"strings" | ||
|
||
doublestar "github.com/bmatcuk/doublestar/v4" | ||
) | ||
|
||
// listFiles takes a filename or directory as its start argument and returns | ||
// a list of absolute file paths. If a filename is given, the list contains | ||
// exactly one element, otherwise the directory is scanned recursively. | ||
// Note that if start is a file, the skip rules are not evaluated. This allows | ||
// users to force-format an otherwise skipped file. | ||
func listFiles(start string, moduleRoot string, skips []string) ([]string, error) { | ||
result := []string{} | ||
|
||
info, err := os.Stat(start) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if !info.IsDir() { | ||
return []string{start}, nil | ||
} | ||
|
||
err = filepath.WalkDir(start, func(path string, d fs.DirEntry, err error) error { | ||
relPath, err := filepath.Rel(moduleRoot, path) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if isSkipped(relPath, skips) { | ||
if d.IsDir() { | ||
return filepath.SkipDir | ||
} else { | ||
return nil | ||
} | ||
} | ||
|
||
if !d.IsDir() && strings.HasSuffix(path, ".go") { | ||
result = append(result, path) | ||
} | ||
|
||
return nil | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return result, nil | ||
} | ||
|
||
func isSkipped(relPath string, skips []string) bool { | ||
for _, skip := range skips { | ||
if match, _ := doublestar.Match(skip, relPath); match { | ||
return true | ||
} | ||
} | ||
|
||
return false | ||
} | ||
|
||
func goModRootPath(path string) (string, error) { | ||
// turn path into directory, if it's a file | ||
if info, err := os.Stat(path); err == nil && !info.IsDir() { | ||
path = filepath.Dir(path) | ||
} | ||
|
||
for { | ||
if fi, err := os.Stat(filepath.Join(path, "go.mod")); err == nil && !fi.IsDir() { | ||
return path, nil | ||
} | ||
|
||
d := filepath.Dir(path) | ||
if d == path { | ||
break | ||
} | ||
|
||
path = d | ||
} | ||
|
||
return "", errors.New("no go.mod found") | ||
} | ||
|
||
var ( | ||
// detect generated files by presence if this string in the first non-stripped line | ||
generatedRe = regexp.MustCompile("(been generated|generated by|do not edit)") | ||
) | ||
|
||
func isGeneratedFile(filename string) (bool, error) { | ||
content, err := ioutil.ReadFile(filename) | ||
if err != nil { | ||
return false, err | ||
} | ||
|
||
return isGeneratedCode(content) | ||
} | ||
|
||
func isGeneratedCode(sourceCode []byte) (bool, error) { | ||
fset := token.NewFileSet() | ||
|
||
file, err := parser.ParseFile(fset, "", sourceCode, parser.ParseComments) | ||
if err != nil { | ||
return false, err | ||
} | ||
|
||
// go through all comments until we reach the package declaration | ||
outer: | ||
for _, commentGroup := range file.Comments { | ||
for _, comment := range commentGroup.List { | ||
// found the package declaration | ||
if comment.Slash > file.Package { | ||
break outer | ||
} | ||
|
||
text := []byte(strings.ToLower(comment.Text)) | ||
if generatedRe.Match(text) { | ||
return true, nil | ||
} | ||
} | ||
} | ||
|
||
return false, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
"testing" | ||
) | ||
|
||
func TestDefaultExcludeFilterAgainstFilenames(t *testing.T) { | ||
testcases := []struct { | ||
filename string | ||
expected bool | ||
}{ | ||
{ | ||
filename: "main.go", | ||
expected: false, | ||
}, | ||
{ | ||
filename: "zz_generated.deepcopy.go", | ||
expected: true, | ||
}, | ||
{ | ||
filename: "zz_generated.go", | ||
expected: true, | ||
}, | ||
{ | ||
filename: "generated.pb.go", | ||
expected: true, | ||
}, | ||
} | ||
|
||
for _, tt := range testcases { | ||
t.Run(tt.filename, func(t *testing.T) { | ||
skipped := isSkipped(tt.filename, defaultExcludes) | ||
if skipped != tt.expected { | ||
t.Errorf("Expected %v but got %v", tt.expected, skipped) | ||
} | ||
|
||
tt.filename = "pkg/" + tt.filename | ||
|
||
skipped = isSkipped(tt.filename, defaultExcludes) | ||
if skipped != tt.expected { | ||
t.Errorf("Expected %v for %q, but got %v", tt.expected, tt.filename, skipped) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestIsGeneratedCode(t *testing.T) { | ||
testcases := []struct { | ||
comment string | ||
expected bool | ||
}{ | ||
{ | ||
comment: "", | ||
expected: false, | ||
}, | ||
{ | ||
comment: "// This file has been generated.", | ||
expected: true, | ||
}, | ||
{ | ||
comment: "// Code generated by MockGen. DO NOT EDIT.", | ||
expected: true, | ||
}, | ||
{ | ||
comment: "// Code generated by generate-imagename-constants.sh. DO NOT EDIT.", | ||
expected: true, | ||
}, | ||
{ | ||
comment: "// This file has been generated with Velero v1.5.3. Do not edit.", | ||
expected: true, | ||
}, | ||
} | ||
|
||
for i, tt := range testcases { | ||
code := fmt.Sprintf(` | ||
%s | ||
package main | ||
func main() { | ||
} | ||
`, tt.comment) | ||
t.Run(fmt.Sprintf("#%d vanilla", i+1), runGeneratedCodeTest(code, tt.expected)) | ||
|
||
code = fmt.Sprintf(` | ||
// +build foo | ||
%s | ||
package main | ||
func main() { | ||
} | ||
`, tt.comment) | ||
t.Run(fmt.Sprintf("#%d with build constraint", i+1), runGeneratedCodeTest(code, tt.expected)) | ||
|
||
code = fmt.Sprintf(` | ||
// +build foo | ||
/* | ||
I am a license header. | ||
*/ | ||
%s | ||
package main | ||
func main() { | ||
} | ||
`, tt.comment) | ||
t.Run(fmt.Sprintf("#%d with build constraint and license header", i+1), runGeneratedCodeTest(code, tt.expected)) | ||
|
||
code = fmt.Sprintf(` | ||
// +build foo | ||
/* | ||
I am a license header. | ||
*/ | ||
package main | ||
%s | ||
func main() { | ||
} | ||
`, tt.comment) | ||
t.Run(fmt.Sprintf("#%d, but too late, so ignore it", i+1), runGeneratedCodeTest(code, false)) | ||
} | ||
} | ||
|
||
func runGeneratedCodeTest(code string, expected bool) func(t *testing.T) { | ||
return func(t *testing.T) { | ||
b := []byte(strings.TrimSpace(code)) | ||
|
||
generated, err := isGeneratedCode(b) | ||
if err != nil { | ||
t.Errorf("should not have errored, but got %v", err) | ||
} | ||
|
||
if generated != expected { | ||
t.Errorf("Expected %v but got %v", expected, generated) | ||
} | ||
} | ||
} |
Oops, something went wrong.