Skip to content

Commit

Permalink
fix formatting files directly, add tests, improve code generation det…
Browse files Browse the repository at this point in the history
…ection
  • Loading branch information
xrstf committed May 5, 2021
1 parent e51cef8 commit 96a7c07
Show file tree
Hide file tree
Showing 4 changed files with 300 additions and 104 deletions.
38 changes: 20 additions & 18 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,25 @@ const (
defaultConfigFile = ".gimps.yaml"
)

var (
defaultExcludes = []string{
// to not break 3rd party code
"vendor/**",

// to not muck with generated files
"**/zz_generated.**",
"**/zz_generated_**",
"**/generated.pb.go",
"**/generated.proto",
"**/*_generated.go",

// for performance
".git/**",
"_build/**",
"node_modules/**",
}
)

type Config struct {
gimps.Config `yaml:",inline"`
Exclude []string `yaml:"exclude"`
Expand Down Expand Up @@ -42,24 +61,7 @@ func loadConfiguration(filename string, moduleRoot string) (*Config, error) {
}

if c.Exclude == nil || len(c.Exclude) == 0 {
// vendor is because we never want to modify vendor, the
// others are just to save time while scanning bigger
// repositories that maybe also contain non-Go stuff
c.Exclude = []string{
// to not break 3rd party code
"vendor/**",

// to not muck with generated files
"**/zz_generated.**",
"**/zz_generated_**",
"**/generated.pb.go",
"**/*_generated.go",

// for performance
".git/**",
"_build/**",
"node_modules/**",
}
c.Exclude = defaultExcludes
}

if c.DetectGeneratedFiles == nil {
Expand Down
132 changes: 132 additions & 0 deletions fs.go
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
}
147 changes: 147 additions & 0 deletions fs_test.go
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)
}
}
}

0 comments on commit 96a7c07

Please sign in to comment.