diff options
Diffstat (limited to 'vendor/github.com/mgechev')
79 files changed, 7791 insertions, 0 deletions
diff --git a/vendor/github.com/mgechev/dots/.travis.yml b/vendor/github.com/mgechev/dots/.travis.yml new file mode 100644 index 0000000000..f4a4a7363c --- /dev/null +++ b/vendor/github.com/mgechev/dots/.travis.yml @@ -0,0 +1,2 @@ +language: go +go: master diff --git a/vendor/github.com/mgechev/dots/LICENSE b/vendor/github.com/mgechev/dots/LICENSE new file mode 100644 index 0000000000..c617c7e012 --- /dev/null +++ b/vendor/github.com/mgechev/dots/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Minko Gechev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/mgechev/dots/README.md b/vendor/github.com/mgechev/dots/README.md new file mode 100644 index 0000000000..1203aef5f7 --- /dev/null +++ b/vendor/github.com/mgechev/dots/README.md @@ -0,0 +1,100 @@ +[![Build Status](https://travis-ci.org/mgechev/dots.svg?branch=master)](https://travis-ci.org/mgechev/dots) + +# Dots + +Implements the wildcard file matching in Go used by golint, go test etc. + +## Usage + +```go +import "github.com/mgechev/dots" + +func main() { + result, err := dots.Resolve([]string{"./fixtures/..."}, []string{"./fixtures/foo"}) + for _, f := range result { + fmt.Println(f); + } +} +``` + +If we suppose that we have the following directory structure: + +```text +├── README.md +├── fixtures +│ ├── bar +│ │ ├── bar1.go +│ │ └── bar2.go +│ ├── baz +│ │ ├── baz1.go +│ │ ├── baz2.go +│ │ └── baz3.go +│ └── foo +│ ├── foo1.go +│ ├── foo2.go +│ └── foo3.go +└── main.go +``` + +The result will be: + +```text +fixtures/bar/bar1.go +fixtures/bar/bar2.go +fixtures/baz/baz1.go +fixtures/baz/baz2.go +fixtures/baz/baz3.go +``` + +`dots` supports wildcard in both - the first and the last argument of `Resolve`, which means that you can ignore files based on a wildcard: + +```go +dots.Resolve([]string{"github.com/mgechev/dots"}, []string{"./..."}) // empty list +dots.Resolve([]string{"./fixtures/bar/..."}, []string{"./fixture/foo/...", "./fixtures/baz/..."}) // bar1.go, bar2.go +``` + +## Preserve package structure + +`dots` allow you to receive a slice of slices where each nested slice represents an individual package: + +```go +dots.ResolvePackages([]string{"github.com/mgechev/dots/..."}, []string{}) +``` + +So we will get the result: + +```text +[ + [ + "$GOROOT/src/github.com/mgechev/dots/fixtures/dummy/bar/bar1.go", + "$GOROOT/src/github.com/mgechev/dots/fixtures/dummy/bar/bar2.go" + ], + [ + "$GOROOT/src/github.com/mgechev/dots/fixtures/dummy/baz/baz1.go", + "$GOROOT/src/github.com/mgechev/dots/fixtures/dummy/baz/baz2.go", + "$GOROOT/src/github.com/mgechev/dots/fixtures/dummy/baz/baz3.go" + ], + [ + "$GOROOT/src/github.com/mgechev/dots/fixtures/dummy/foo/foo1.go", + "$GOROOT/src/github.com/mgechev/dots/fixtures/dummy/foo/foo2.go", + "$GOROOT/src/github.com/mgechev/dots/fixtures/dummy/foo/foo3.go" + ], + [ + "$GOROOT/src/github.com/mgechev/dots/fixtures/pkg/baz/baz1.go", + "$GOROOT/src/github.com/mgechev/dots/fixtures/pkg/baz/baz2.go" + ], + [ + "$GOROOT/src/github.com/mgechev/dots/fixtures/pkg/foo/foo1.go", + "$GOROOT/src/github.com/mgechev/dots/fixtures/pkg/foo/foo2.go" + ], + [ + "$GOROOT/src/github.com/mgechev/dots/fixtures/pkg/foo/bar/bar1.go" + ] +] +``` + +This method is especially useful, when you want to perform type checking over given package from the result. + +## License + +MIT diff --git a/vendor/github.com/mgechev/dots/resolve.go b/vendor/github.com/mgechev/dots/resolve.go new file mode 100644 index 0000000000..309ba18ad2 --- /dev/null +++ b/vendor/github.com/mgechev/dots/resolve.go @@ -0,0 +1,456 @@ +package dots + +import ( + "go/build" + "log" + "os" + "path" + "path/filepath" + "regexp" + "runtime" + "strings" +) + +var ( + buildContext = build.Default + goroot = filepath.Clean(runtime.GOROOT()) + gorootSrc = filepath.Join(goroot, "src") +) + +func flatten(arr [][]string) []string { + var res []string + for _, e := range arr { + res = append(res, e...) + } + return res +} + +// Resolve accepts a slice of paths with optional "..." placeholder and a slice with paths to be skipped. +// The final result is the set of all files from the selected directories subtracted with +// the files in the skip slice. +func Resolve(includePatterns, skipPatterns []string) ([]string, error) { + skip, err := resolvePatterns(skipPatterns) + filter := newPathFilter(flatten(skip)) + if err != nil { + return nil, err + } + + pathSet := map[string]bool{} + includePackages, err := resolvePatterns(includePatterns) + include := flatten(includePackages) + if err != nil { + return nil, err + } + + var result []string + for _, i := range include { + if _, ok := pathSet[i]; !ok && !filter(i) { + pathSet[i] = true + result = append(result, i) + } + } + return result, err +} + +// ResolvePackages accepts a slice of paths with optional "..." placeholder and a slice with paths to be skipped. +// The final result is the set of all files from the selected directories subtracted with +// the files in the skip slice. The difference between `Resolve` and `ResolvePackages` +// is that `ResolvePackages` preserves the package structure in the nested slices. +func ResolvePackages(includePatterns, skipPatterns []string) ([][]string, error) { + skip, err := resolvePatterns(skipPatterns) + filter := newPathFilter(flatten(skip)) + if err != nil { + return nil, err + } + + pathSet := map[string]bool{} + include, err := resolvePatterns(includePatterns) + if err != nil { + return nil, err + } + + var result [][]string + for _, p := range include { + var packageFiles []string + for _, f := range p { + if _, ok := pathSet[f]; !ok && !filter(f) { + pathSet[f] = true + packageFiles = append(packageFiles, f) + } + } + result = append(result, packageFiles) + } + return result, err +} + +func isDir(filename string) bool { + fi, err := os.Stat(filename) + return err == nil && fi.IsDir() +} + +func exists(filename string) bool { + _, err := os.Stat(filename) + return err == nil +} + +func resolveDir(dirname string) ([]string, error) { + pkg, err := build.ImportDir(dirname, 0) + return resolveImportedPackage(pkg, err) +} + +func resolvePackage(pkgname string) ([]string, error) { + pkg, err := build.Import(pkgname, ".", 0) + return resolveImportedPackage(pkg, err) +} + +func resolveImportedPackage(pkg *build.Package, err error) ([]string, error) { + if err != nil { + if _, nogo := err.(*build.NoGoError); nogo { + // Don't complain if the failure is due to no Go source files. + return nil, nil + } + return nil, err + } + + var files []string + files = append(files, pkg.GoFiles...) + files = append(files, pkg.CgoFiles...) + files = append(files, pkg.TestGoFiles...) + if pkg.Dir != "." { + for i, f := range files { + files[i] = filepath.Join(pkg.Dir, f) + } + } + return files, nil +} + +func resolvePatterns(patterns []string) ([][]string, error) { + var files [][]string + for _, pattern := range patterns { + f, err := resolvePattern(pattern) + if err != nil { + return nil, err + } + files = append(files, f...) + } + return files, nil +} + +func resolvePattern(pattern string) ([][]string, error) { + // dirsRun, filesRun, and pkgsRun indicate whether golint is applied to + // directory, file or package targets. The distinction affects which + // checks are run. It is no valid to mix target types. + var dirsRun, filesRun, pkgsRun int + var matches []string + + if strings.HasSuffix(pattern, "/...") && isDir(pattern[:len(pattern)-len("/...")]) { + dirsRun = 1 + for _, dirname := range matchPackagesInFS(pattern) { + matches = append(matches, dirname) + } + } else if isDir(pattern) { + dirsRun = 1 + matches = append(matches, pattern) + } else if exists(pattern) { + filesRun = 1 + matches = append(matches, pattern) + } else { + pkgsRun = 1 + matches = append(matches, pattern) + } + + result := [][]string{} + switch { + case dirsRun == 1: + for _, dir := range matches { + res, err := resolveDir(dir) + if err != nil { + return nil, err + } + result = append(result, res) + } + case filesRun == 1: + return [][]string{matches}, nil + case pkgsRun == 1: + for _, pkg := range importPaths(matches) { + res, err := resolvePackage(pkg) + if err != nil { + return nil, err + } + result = append(result, res) + } + } + return result, nil +} + +func newPathFilter(skip []string) func(string) bool { + filter := map[string]bool{} + for _, name := range skip { + filter[name] = true + } + + return func(path string) bool { + base := filepath.Base(path) + if filter[base] || filter[path] { + return true + } + return base != "." && base != ".." && strings.ContainsAny(base[0:1], "_.") + } +} + +// importPathsNoDotExpansion returns the import paths to use for the given +// command line, but it does no ... expansion. +func importPathsNoDotExpansion(args []string) []string { + if len(args) == 0 { + return []string{"."} + } + var out []string + for _, a := range args { + // Arguments are supposed to be import paths, but + // as a courtesy to Windows developers, rewrite \ to / + // in command-line arguments. Handles .\... and so on. + if filepath.Separator == '\\' { + a = strings.Replace(a, `\`, `/`, -1) + } + + // Put argument in canonical form, but preserve leading ./. + if strings.HasPrefix(a, "./") { + a = "./" + path.Clean(a) + if a == "./." { + a = "." + } + } else { + a = path.Clean(a) + } + if a == "all" || a == "std" { + out = append(out, matchPackages(a)...) + continue + } + out = append(out, a) + } + return out +} + +// importPaths returns the import paths to use for the given command line. +func importPaths(args []string) []string { + args = importPathsNoDotExpansion(args) + var out []string + for _, a := range args { + if strings.Contains(a, "...") { + if build.IsLocalImport(a) { + out = append(out, matchPackagesInFS(a)...) + } else { + out = append(out, matchPackages(a)...) + } + continue + } + out = append(out, a) + } + return out +} + +// matchPattern(pattern)(name) reports whether +// name matches pattern. Pattern is a limited glob +// pattern in which '...' means 'any string' and there +// is no other special syntax. +func matchPattern(pattern string) func(name string) bool { + re := regexp.QuoteMeta(pattern) + re = strings.Replace(re, `\.\.\.`, `.*`, -1) + // Special case: foo/... matches foo too. + if strings.HasSuffix(re, `/.*`) { + re = re[:len(re)-len(`/.*`)] + `(/.*)?` + } + reg := regexp.MustCompile(`^` + re + `$`) + return func(name string) bool { + return reg.MatchString(name) + } +} + +// hasPathPrefix reports whether the path s begins with the +// elements in prefix. +func hasPathPrefix(s, prefix string) bool { + switch { + default: + return false + case len(s) == len(prefix): + return s == prefix + case len(s) > len(prefix): + if prefix != "" && prefix[len(prefix)-1] == '/' { + return strings.HasPrefix(s, prefix) + } + return s[len(prefix)] == '/' && s[:len(prefix)] == prefix + } +} + +// treeCanMatchPattern(pattern)(name) reports whether +// name or children of name can possibly match pattern. +// Pattern is the same limited glob accepted by matchPattern. +func treeCanMatchPattern(pattern string) func(name string) bool { + wildCard := false + if i := strings.Index(pattern, "..."); i >= 0 { + wildCard = true + pattern = pattern[:i] + } + return func(name string) bool { + return len(name) <= len(pattern) && hasPathPrefix(pattern, name) || + wildCard && strings.HasPrefix(name, pattern) + } +} + +func matchPackages(pattern string) []string { + match := func(string) bool { return true } + treeCanMatch := func(string) bool { return true } + if pattern != "all" && pattern != "std" { + match = matchPattern(pattern) + treeCanMatch = treeCanMatchPattern(pattern) + } + + have := map[string]bool{ + "builtin": true, // ignore pseudo-package that exists only for documentation + } + if !buildContext.CgoEnabled { + have["runtime/cgo"] = true // ignore during walk + } + var pkgs []string + + // Commands + cmd := filepath.Join(goroot, "src/cmd") + string(filepath.Separator) + filepath.Walk(cmd, func(path string, fi os.FileInfo, err error) error { + if err != nil || !fi.IsDir() || path == cmd { + return nil + } + name := path[len(cmd):] + if !treeCanMatch(name) { + return filepath.SkipDir + } + // Commands are all in cmd/, not in subdirectories. + if strings.Contains(name, string(filepath.Separator)) { + return filepath.SkipDir + } + + // We use, e.g., cmd/gofmt as the pseudo import path for gofmt. + name = "cmd/" + name + if have[name] { + return nil + } + have[name] = true + if !match(name) { + return nil + } + _, err = buildContext.ImportDir(path, 0) + if err != nil { + if _, noGo := err.(*build.NoGoError); !noGo { + log.Print(err) + } + return nil + } + pkgs = append(pkgs, name) + return nil + }) + + for _, src := range buildContext.SrcDirs() { + if (pattern == "std" || pattern == "cmd") && src != gorootSrc { + continue + } + src = filepath.Clean(src) + string(filepath.Separator) + root := src + if pattern == "cmd" { + root += "cmd" + string(filepath.Separator) + } + filepath.Walk(root, func(path string, fi os.FileInfo, err error) error { + if err != nil || !fi.IsDir() || path == src { + return nil + } + + // Avoid .foo, _foo, and testdata directory trees. + _, elem := filepath.Split(path) + if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" { + return filepath.SkipDir + } + + name := filepath.ToSlash(path[len(src):]) + if pattern == "std" && (strings.Contains(name, ".") || name == "cmd") { + // The name "std" is only the standard library. + // If the name is cmd, it's the root of the command tree. + return filepath.SkipDir + } + if !treeCanMatch(name) { + return filepath.SkipDir + } + if have[name] { + return nil + } + have[name] = true + if !match(name) { + return nil + } + _, err = buildContext.ImportDir(path, 0) + if err != nil { + if _, noGo := err.(*build.NoGoError); noGo { + return nil + } + } + pkgs = append(pkgs, name) + return nil + }) + } + return pkgs +} + +func matchPackagesInFS(pattern string) []string { + // Find directory to begin the scan. + // Could be smarter but this one optimization + // is enough for now, since ... is usually at the + // end of a path. + i := strings.Index(pattern, "...") + dir, _ := path.Split(pattern[:i]) + + // pattern begins with ./ or ../. + // path.Clean will discard the ./ but not the ../. + // We need to preserve the ./ for pattern matching + // and in the returned import paths. + prefix := "" + if strings.HasPrefix(pattern, "./") { + prefix = "./" + } + match := matchPattern(pattern) + + var pkgs []string + filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error { + if err != nil || !fi.IsDir() { + return nil + } + if path == dir { + // filepath.Walk starts at dir and recurses. For the recursive case, + // the path is the result of filepath.Join, which calls filepath.Clean. + // The initial case is not Cleaned, though, so we do this explicitly. + // + // This converts a path like "./io/" to "io". Without this step, running + // "cd $GOROOT/src/pkg; go list ./io/..." would incorrectly skip the io + // package, because prepending the prefix "./" to the unclean path would + // result in "././io", and match("././io") returns false. + path = filepath.Clean(path) + } + + // Avoid .foo, _foo, and testdata directory trees, but do not avoid "." or "..". + _, elem := filepath.Split(path) + dot := strings.HasPrefix(elem, ".") && elem != "." && elem != ".." + if dot || strings.HasPrefix(elem, "_") || elem == "testdata" { + return filepath.SkipDir + } + + name := prefix + filepath.ToSlash(path) + if !match(name) { + return nil + } + if _, err = build.ImportDir(path, 0); err != nil { + if _, noGo := err.(*build.NoGoError); !noGo { + log.Print(err) + } + return nil + } + pkgs = append(pkgs, name) + return nil + }) + return pkgs +} diff --git a/vendor/github.com/mgechev/revive/LICENSE b/vendor/github.com/mgechev/revive/LICENSE new file mode 100644 index 0000000000..c617c7e012 --- /dev/null +++ b/vendor/github.com/mgechev/revive/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Minko Gechev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/mgechev/revive/formatter/checkstyle.go b/vendor/github.com/mgechev/revive/formatter/checkstyle.go new file mode 100644 index 0000000000..bd20da888c --- /dev/null +++ b/vendor/github.com/mgechev/revive/formatter/checkstyle.go @@ -0,0 +1,76 @@ +package formatter + +import ( + "bytes" + "encoding/xml" + "github.com/mgechev/revive/lint" + plainTemplate "text/template" +) + +// Checkstyle is an implementation of the Formatter interface +// which formats the errors to Checkstyle-like format. +type Checkstyle struct { + Metadata lint.FormatterMetadata +} + +// Name returns the name of the formatter +func (f *Checkstyle) Name() string { + return "checkstyle" +} + +type issue struct { + Line int + Col int + What string + Confidence float64 + Severity lint.Severity + RuleName string +} + +// Format formats the failures gotten from the lint. +func (f *Checkstyle) Format(failures <-chan lint.Failure, config lint.Config) (string, error) { + var issues = map[string][]issue{} + for failure := range failures { + buf := new(bytes.Buffer) + xml.Escape(buf, []byte(failure.Failure)) + what := buf.String() + iss := issue{ + Line: failure.Position.Start.Line, + Col: failure.Position.Start.Column, + What: what, + Confidence: failure.Confidence, + Severity: severity(config, failure), + RuleName: failure.RuleName, + } + fn := failure.GetFilename() + if issues[fn] == nil { + issues[fn] = make([]issue, 0) + } + issues[fn] = append(issues[fn], iss) + } + + t, err := plainTemplate.New("revive").Parse(checkstyleTemplate) + if err != nil { + return "", err + } + + buf := new(bytes.Buffer) + + err = t.Execute(buf, issues) + if err != nil { + return "", err + } + + return buf.String(), nil +} + +const checkstyleTemplate = `<?xml version='1.0' encoding='UTF-8'?> +<checkstyle version="5.0"> +{{- range $k, $v := . }} + <file name="{{ $k }}"> + {{- range $i, $issue := $v }} + <error line="{{ $issue.Line }}" column="{{ $issue.Col }}" message="{{ $issue.What }} (confidence {{ $issue.Confidence}})" severity="{{ $issue.Severity }}" source="revive/{{ $issue.RuleName }}"/> + {{- end }} + </file> +{{- end }} +</checkstyle>` diff --git a/vendor/github.com/mgechev/revive/formatter/default.go b/vendor/github.com/mgechev/revive/formatter/default.go new file mode 100644 index 0000000000..145e6d548e --- /dev/null +++ b/vendor/github.com/mgechev/revive/formatter/default.go @@ -0,0 +1,26 @@ +package formatter + +import ( + "fmt" + + "github.com/mgechev/revive/lint" +) + +// Default is an implementation of the Formatter interface +// which formats the errors to text. +type Default struct { + Metadata lint.FormatterMetadata +} + +// Name returns the name of the formatter +func (f *Default) Name() string { + return "default" +} + +// Format formats the failures gotten from the lint. +func (f *Default) Format(failures <-chan lint.Failure, _ lint.Config) (string, error) { + for failure := range failures { + fmt.Printf("%v: %s\n", failure.Position.Start, failure.Failure) + } + return "", nil +} diff --git a/vendor/github.com/mgechev/revive/formatter/friendly.go b/vendor/github.com/mgechev/revive/formatter/friendly.go new file mode 100644 index 0000000000..a543eebe00 --- /dev/null +++ b/vendor/github.com/mgechev/revive/formatter/friendly.go @@ -0,0 +1,146 @@ +package formatter + +import ( + "bytes" + "fmt" + "sort" + + "github.com/fatih/color" + "github.com/mgechev/revive/lint" + "github.com/olekukonko/tablewriter" +) + +var ( + errorEmoji = color.RedString("✘") + warningEmoji = color.YellowString("⚠") +) + +var newLines = map[rune]bool{ + 0x000A: true, + 0x000B: true, + 0x000C: true, + 0x000D: true, + 0x0085: true, + 0x2028: true, + 0x2029: true, +} + +// Friendly is an implementation of the Formatter interface +// which formats the errors to JSON. +type Friendly struct { + Metadata lint.FormatterMetadata +} + +// Name returns the name of the formatter +func (f *Friendly) Name() string { + return "friendly" +} + +// Format formats the failures gotten from the lint. +func (f *Friendly) Format(failures <-chan lint.Failure, config lint.Config) (string, error) { + errorMap := map[string]int{} + warningMap := map[string]int{} + totalErrors := 0 + totalWarnings := 0 + for failure := range failures { + sev := severity(config, failure) + f.printFriendlyFailure(failure, sev) + if sev == lint.SeverityWarning { + warningMap[failure.RuleName] = warningMap[failure.RuleName] + 1 + totalWarnings++ + } + if sev == lint.SeverityError { + errorMap[failure.RuleName] = errorMap[failure.RuleName] + 1 + totalErrors++ + } + } + f.printSummary(totalErrors, totalWarnings) + f.printStatistics(color.RedString("Errors:"), errorMap) + f.printStatistics(color.YellowString("Warnings:"), warningMap) + return "", nil +} + +func (f *Friendly) printFriendlyFailure(failure lint.Failure, severity lint.Severity) { + f.printHeaderRow(failure, severity) + f.printFilePosition(failure) + fmt.Println() + fmt.Println() +} + +func (f *Friendly) printHeaderRow(failure lint.Failure, severity lint.Severity) { + emoji := warningEmoji + if severity == lint.SeverityError { + emoji = errorEmoji + } + fmt.Print(f.table([][]string{{emoji, "https://revive.run/r#" + failure.RuleName, color.GreenString(failure.Failure)}})) +} + +func (f *Friendly) printFilePosition(failure lint.Failure) { + fmt.Printf(" %s:%d:%d", failure.GetFilename(), failure.Position.Start.Line, failure.Position.Start.Column) +} + +type statEntry struct { + name string + failures int +} + +func (f *Friendly) printSummary(errors, warnings int) { + emoji := warningEmoji + if errors > 0 { + emoji = errorEmoji + } + problemsLabel := "problems" + if errors+warnings == 1 { + problemsLabel = "problem" + } + warningsLabel := "warnings" + if warnings == 1 { + warningsLabel = "warning" + } + errorsLabel := "errors" + if errors == 1 { + errorsLabel = "error" + } + str := fmt.Sprintf("%d %s (%d %s, %d %s)", errors+warnings, problemsLabel, errors, errorsLabel, warnings, warningsLabel) + if errors > 0 { + fmt.Printf("%s %s\n", emoji, color.RedString(str)) + fmt.Println() + return + } + if warnings > 0 { + fmt.Printf("%s %s\n", emoji, color.YellowString(str)) + fmt.Println() + return + } +} + +func (f *Friendly) printStatistics(header string, stats map[string]int) { + if len(stats) == 0 { + return + } + var data []statEntry + for name, total := range stats { + data = append(data, statEntry{name, total}) + } + sort.Slice(data, func(i, j int) bool { + return data[i].failures > data[j].failures + }) + formatted := [][]string{} + for _, entry := range data { + formatted = append(formatted, []string{color.GreenString(fmt.Sprintf("%d", entry.failures)), entry.name}) + } + fmt.Println(header) + fmt.Println(f.table(formatted)) +} + +func (f *Friendly) table(rows [][]string) string { + buf := new(bytes.Buffer) + table := tablewriter.NewWriter(buf) + table.SetBorder(false) + table.SetColumnSeparator("") + table.SetRowSeparator("") + table.SetAutoWrapText(false) + table.AppendBulk(rows) + table.Render() + return buf.String() +} diff --git a/vendor/github.com/mgechev/revive/formatter/json.go b/vendor/github.com/mgechev/revive/formatter/json.go new file mode 100644 index 0000000000..9c939face0 --- /dev/null +++ b/vendor/github.com/mgechev/revive/formatter/json.go @@ -0,0 +1,40 @@ +package formatter + +import ( + "encoding/json" + + "github.com/mgechev/revive/lint" +) + +// JSON is an implementation of the Formatter interface +// which formats the errors to JSON. +type JSON struct { + Metadata lint.FormatterMetadata +} + +// Name returns the name of the formatter +func (f *JSON) Name() string { + return "json" +} + +// jsonObject defines a JSON object of an failure +type jsonObject struct { + Severity lint.Severity + lint.Failure `json:",inline"` +} + +// Format formats the failures gotten from the lint. +func (f *JSON) Format(failures <-chan lint.Failure, config lint.Config) (string, error) { + var slice []jsonObject + for failure := range failures { + obj := jsonObject{} + obj.Severity = severity(config, failure) + obj.Failure = failure + slice = append(slice, obj) + } + result, err := json.Marshal(slice) + if err != nil { + return "", err + } + return string(result), err +} diff --git a/vendor/github.com/mgechev/revive/formatter/ndjson.go b/vendor/github.com/mgechev/revive/formatter/ndjson.go new file mode 100644 index 0000000000..aa2b1d6368 --- /dev/null +++ b/vendor/github.com/mgechev/revive/formatter/ndjson.go @@ -0,0 +1,34 @@ +package formatter + +import ( + "encoding/json" + "os" + + "github.com/mgechev/revive/lint" +) + +// NDJSON is an implementation of the Formatter interface +// which formats the errors to NDJSON stream. +type NDJSON struct { + Metadata lint.FormatterMetadata +} + +// Name returns the name of the formatter +func (f *NDJSON) Name() string { + return "ndjson" +} + +// Format formats the failures gotten from the lint. +func (f *NDJSON) Format(failures <-chan lint.Failure, config lint.Config) (string, error) { + enc := json.NewEncoder(os.Stdout) + for failure := range failures { + obj := jsonObject{} + obj.Severity = severity(config, failure) + obj.Failure = failure + err := enc.Encode(obj) + if err != nil { + return "", err + } + } + return "", nil +} diff --git a/vendor/github.com/mgechev/revive/formatter/plain.go b/vendor/github.com/mgechev/revive/formatter/plain.go new file mode 100644 index 0000000000..a854d25629 --- /dev/null +++ b/vendor/github.com/mgechev/revive/formatter/plain.go @@ -0,0 +1,26 @@ +package formatter + +import ( + "fmt" + + "github.com/mgechev/revive/lint" +) + +// Plain is an implementation of the Formatter interface +// which formats the errors to JSON. +type Plain struct { + Metadata lint.FormatterMetadata +} + +// Name returns the name of the formatter +func (f *Plain) Name() string { + return "plain" +} + +// Format formats the failures gotten from the lint. +func (f *Plain) Format(failures <-chan lint.Failure, _ lint.Config) (string, error) { + for failure := range failures { + fmt.Printf("%v: %s %s\n", failure.Position.Start, failure.Failure, "https://revive.run/r#"+failure.RuleName) + } + return "", nil +} diff --git a/vendor/github.com/mgechev/revive/formatter/severity.go b/vendor/github.com/mgechev/revive/formatter/severity.go new file mode 100644 index 0000000000..a43bf31923 --- /dev/null +++ b/vendor/github.com/mgechev/revive/formatter/severity.go @@ -0,0 +1,13 @@ +package formatter + +import "github.com/mgechev/revive/lint" + +func severity(config lint.Config, failure lint.Failure) lint.Severity { + if config, ok := config.Rules[failure.RuleName]; ok && config.Severity == lint.SeverityError { + return lint.SeverityError + } + if config, ok := config.Directives[failure.RuleName]; ok && config.Severity == lint.SeverityError { + return lint.SeverityError + } + return lint.SeverityWarning +} diff --git a/vendor/github.com/mgechev/revive/formatter/stylish.go b/vendor/github.com/mgechev/revive/formatter/stylish.go new file mode 100644 index 0000000000..cd81fdae7e --- /dev/null +++ b/vendor/github.com/mgechev/revive/formatter/stylish.go @@ -0,0 +1,89 @@ +package formatter + +import ( + "bytes" + "fmt" + + "github.com/fatih/color" + "github.com/mgechev/revive/lint" + "github.com/olekukonko/tablewriter" +) + +// Stylish is an implementation of the Formatter interface +// which formats the errors to JSON. +type Stylish struct { + Metadata lint.FormatterMetadata +} + +// Name returns the name of the formatter +func (f *Stylish) Name() string { + return "stylish" +} + +func formatFailure(failure lint.Failure, severity lint.Severity) []string { + fString := color.CyanString(failure.Failure) + fName := color.RedString("https://revive.run/r#" + failure.RuleName) + lineColumn := failure.Position + pos := fmt.Sprintf("(%d, %d)", lineColumn.Start.Line, lineColumn.Start.Column) + if severity == lint.SeverityWarning { + fName = color.YellowString("https://revive.run/r#" + failure.RuleName) + } + return []string{failure.GetFilename(), pos, fName, fString} +} + +// Format formats the failures gotten from the lint. +func (f *Stylish) Format(failures <-chan lint.Failure, config lint.Config) (string, error) { + var result [][]string + var totalErrors = 0 + var total = 0 + + for f := range failures { + total++ + currentType := severity(config, f) + if currentType == lint.SeverityError { + totalErrors++ + } + result = append(result, formatFailure(f, lint.Severity(currentType))) + } + ps := "problems" + if total == 1 { + ps = "problem" + } + + fileReport := make(map[string][][]string) + + for _, row := range result { + if _, ok := fileReport[row[0]]; !ok { + fileReport[row[0]] = [][]string{} + } + + fileReport[row[0]] = append(fileReport[row[0]], []string{row[1], row[2], row[3]}) + } + + output := "" + for filename, val := range fileReport { + buf := new(bytes.Buffer) + table := tablewriter.NewWriter(buf) + table.SetBorder(false) + table.SetColumnSeparator("") + table.SetRowSeparator("") + table.SetAutoWrapText(false) + table.AppendBulk(val) + table.Render() + c := color.New(color.Underline) + output += c.SprintfFunc()(filename + "\n") + output += buf.String() + "\n" + } + + suffix := fmt.Sprintf(" %d %s (%d errors) (%d warnings)", total, ps, totalErrors, total-totalErrors) + + if total > 0 && totalErrors > 0 { + suffix = color.RedString("\n ✖" + suffix) + } else if total > 0 && totalErrors == 0 { + suffix = color.YellowString("\n ✖" + suffix) + } else { + suffix, output = "", "" + } + + return output + suffix, nil +} diff --git a/vendor/github.com/mgechev/revive/formatter/unix.go b/vendor/github.com/mgechev/revive/formatter/unix.go new file mode 100644 index 0000000000..b9ae62d38d --- /dev/null +++ b/vendor/github.com/mgechev/revive/formatter/unix.go @@ -0,0 +1,27 @@ +package formatter + +import ( + "fmt" + + "github.com/mgechev/revive/lint" +) + +// Unix is an implementation of the Formatter interface +// which formats the errors to a simple line based error format +// main.go:24:9: [errorf] should replace errors.New(fmt.Sprintf(...)) with fmt.Errorf(...) +type Unix struct { + Metadata lint.FormatterMetadata +} + +// Name returns the name of the formatter +func (f *Unix) Name() string { + return "unix" +} + +// Format formats the failures gotten from the lint. +func (f *Unix) Format(failures <-chan lint.Failure, _ lint.Config) (string, error) { + for failure := range failures { + fmt.Printf("%v: [%s] %s\n", failure.Position.Start, failure.RuleName, failure.Failure) + } + return "", nil +} diff --git a/vendor/github.com/mgechev/revive/lint/config.go b/vendor/github.com/mgechev/revive/lint/config.go new file mode 100644 index 0000000000..fe65ace522 --- /dev/null +++ b/vendor/github.com/mgechev/revive/lint/config.go @@ -0,0 +1,32 @@ +package lint + +// Arguments is type used for the arguments of a rule. +type Arguments = []interface{} + +// RuleConfig is type used for the rule configuration. +type RuleConfig struct { + Arguments Arguments + Severity Severity +} + +// RulesConfig defines the config for all rules. +type RulesConfig = map[string]RuleConfig + +// DirectiveConfig is type used for the linter directive configuration. +type DirectiveConfig struct { + Severity Severity +} + +// DirectivesConfig defines the config for all directives. +type DirectivesConfig = map[string]DirectiveConfig + +// Config defines the config of the linter. +type Config struct { + IgnoreGeneratedHeader bool `toml:"ignoreGeneratedHeader"` + Confidence float64 + Severity Severity + Rules RulesConfig `toml:"rule"` + ErrorCode int `toml:"errorCode"` + WarningCode int `toml:"warningCode"` + Directives DirectivesConfig `toml:"directive"` +} diff --git a/vendor/github.com/mgechev/revive/lint/failure.go b/vendor/github.com/mgechev/revive/lint/failure.go new file mode 100644 index 0000000000..479b0cb48b --- /dev/null +++ b/vendor/github.com/mgechev/revive/lint/failure.go @@ -0,0 +1,39 @@ +package lint + +import ( + "go/ast" + "go/token" +) + +const ( + // SeverityWarning declares failures of type warning + SeverityWarning = "warning" + // SeverityError declares failures of type error. + SeverityError = "error" +) + +// Severity is the type for the failure types. +type Severity string + +// FailurePosition returns the failure position +type FailurePosition struct { + Start token.Position + End token.Position +} + +// Failure defines a struct for a linting failure. +type Failure struct { + Failure string + RuleName string + Category string + Position FailurePosition + Node ast.Node `json:"-"` + Confidence float64 + // For future use + ReplacementLine string +} + +// GetFilename returns the filename. +func (f *Failure) GetFilename() string { + return f.Position.Start.Filename +} diff --git a/vendor/github.com/mgechev/revive/lint/file.go b/vendor/github.com/mgechev/revive/lint/file.go new file mode 100644 index 0000000000..8bef9c220c --- /dev/null +++ b/vendor/github.com/mgechev/revive/lint/file.go @@ -0,0 +1,278 @@ +package lint + +import ( + "bytes" + "go/ast" + "go/parser" + "go/printer" + "go/token" + "go/types" + "math" + "regexp" + "strings" +) + +// File abstraction used for representing files. +type File struct { + Name string + Pkg *Package + content []byte + AST *ast.File +} + +// IsTest returns if the file contains tests. +func (f *File) IsTest() bool { return strings.HasSuffix(f.Name, "_test.go") } + +// Content returns the file's content. +func (f *File) Content() []byte { + return f.content +} + +// NewFile creates a new file +func NewFile(name string, content []byte, pkg *Package) (*File, error) { + f, err := parser.ParseFile(pkg.fset, name, content, parser.ParseComments) + if err != nil { + return nil, err + } + return &File{ + Name: name, + content: content, + Pkg: pkg, + AST: f, + }, nil +} + +// ToPosition returns line and column for given position. +func (f *File) ToPosition(pos token.Pos) token.Position { + return f.Pkg.fset.Position(pos) +} + +// Render renters a node. +func (f *File) Render(x interface{}) string { + var buf bytes.Buffer + if err := printer.Fprint(&buf, f.Pkg.fset, x); err != nil { + panic(err) + } + return buf.String() +} + +// CommentMap builds a comment map for the file. +func (f *File) CommentMap() ast.CommentMap { + return ast.NewCommentMap(f.Pkg.fset, f.AST, f.AST.Comments) +} + +var basicTypeKinds = map[types.BasicKind]string{ + types.UntypedBool: "bool", + types.UntypedInt: "int", + types.UntypedRune: "rune", + types.UntypedFloat: "float64", + types.UntypedComplex: "complex128", + types.UntypedString: "string", +} + +// IsUntypedConst reports whether expr is an untyped constant, +// and indicates what its default type is. +// scope may be nil. +func (f *File) IsUntypedConst(expr ast.Expr) (defType string, ok bool) { + // Re-evaluate expr outside of its context to see if it's untyped. + // (An expr evaluated within, for example, an assignment context will get the type of the LHS.) + exprStr := f.Render(expr) + tv, err := types.Eval(f.Pkg.fset, f.Pkg.TypesPkg, expr.Pos(), exprStr) + if err != nil { + return "", false + } + if b, ok := tv.Type.(*types.Basic); ok { + if dt, ok := basicTypeKinds[b.Kind()]; ok { + return dt, true + } + } + + return "", false +} + +func (f *File) isMain() bool { + if f.AST.Name.Name == "main" { + return true + } + return false +} + +const directiveSpecifyDisableReason = "specify-disable-reason" + +func (f *File) lint(rules []Rule, config Config, failures chan Failure) { + rulesConfig := config.Rules + _, mustSpecifyDisableReason := config.Directives[directiveSpecifyDisableReason] + disabledIntervals := f.disabledIntervals(rules, mustSpecifyDisableReason, failures) + for _, currentRule := range rules { + ruleConfig := rulesConfig[currentRule.Name()] + currentFailures := currentRule.Apply(f, ruleConfig.Arguments) + for idx, failure := range currentFailures { + if failure.RuleName == "" { + failure.RuleName = currentRule.Name() + } + if failure.Node != nil { + failure.Position = ToFailurePosition(failure.Node.Pos(), failure.Node.End(), f) + } + currentFailures[idx] = failure + } + currentFailures = f.filterFailures(currentFailures, disabledIntervals) + for _, failure := range currentFailures { + if failure.Confidence >= config.Confidence { + failures <- failure + } + } + } +} + +type enableDisableConfig struct { + enabled bool + position int +} + +const directiveRE = `^//[\s]*revive:(enable|disable)(?:-(line|next-line))?(?::([^\s]+))?[\s]*(?: (.+))?$` +const directivePos = 1 +const modifierPos = 2 +const rulesPos = 3 +const reasonPos = 4 + +var re = regexp.MustCompile(directiveRE) + +func (f *File) disabledIntervals(rules []Rule, mustSpecifyDisableReason bool, failures chan Failure) disabledIntervalsMap { + enabledDisabledRulesMap := make(map[string][]enableDisableConfig) + + getEnabledDisabledIntervals := func() disabledIntervalsMap { + result := make(disabledIntervalsMap) + + for ruleName, disabledArr := range enabledDisabledRulesMap { + ruleResult := []DisabledInterval{} + for i := 0; i < len(disabledArr); i++ { + interval := DisabledInterval{ + RuleName: ruleName, + From: token.Position{ + Filename: f.Name, + Line: disabledArr[i].position, + }, + To: token.Position{ + Filename: f.Name, + Line: math.MaxInt32, + }, + } + if i%2 == 0 { + ruleResult = append(ruleResult, interval) + } else { + ruleResult[len(ruleResult)-1].To.Line = disabledArr[i].position + } + } + result[ruleName] = ruleResult + } + + return result + } + + handleConfig := func(isEnabled bool, line int, name string) { + existing, ok := enabledDisabledRulesMap[name] + if !ok { + existing = []enableDisableConfig{} + enabledDisabledRulesMap[name] = existing + } + if (len(existing) > 1 && existing[len(existing)-1].enabled == isEnabled) || + (len(existing) == 0 && isEnabled) { + return + } + existing = append(existing, enableDisableConfig{ + enabled: isEnabled, + position: line, + }) + enabledDisabledRulesMap[name] = existing + } + + handleRules := func(filename, modifier string, isEnabled bool, line int, ruleNames []string) []DisabledInterval { + var result []DisabledInterval + for _, name := range ruleNames { + if modifier == "line" { + handleConfig(isEnabled, line, name) + handleConfig(!isEnabled, line, name) + } else if modifier == "next-line" { + handleConfig(isEnabled, line+1, name) + handleConfig(!isEnabled, line+1, name) + } else { + handleConfig(isEnabled, line, name) + } + } + return result + } + + handleComment := func(filename string, c *ast.CommentGroup, line int) { + comments := c.List + for _, c := range comments { + match := re.FindStringSubmatch(c.Text) + if len(match) == 0 { + return + } + + ruleNames := []string{} + tempNames := strings.Split(match[rulesPos], ",") + for _, name := range tempNames { + name = strings.Trim(name, "\n") + if len(name) > 0 { + ruleNames = append(ruleNames, name) + } + } + + mustCheckDisablingReason := mustSpecifyDisableReason && match[directivePos] == "disable" + if mustCheckDisablingReason && strings.Trim(match[reasonPos], " ") == "" { + failures <- Failure{ + Confidence: 1, + RuleName: directiveSpecifyDisableReason, + Failure: "reason of lint disabling not found", + Position: ToFailurePosition(c.Pos(), c.End(), f), + Node: c, + } + continue // skip this linter disabling directive + } + + // TODO: optimize + if len(ruleNames) == 0 { + for _, rule := range rules { + ruleNames = append(ruleNames, rule.Name()) + } + } + + handleRules(filename, match[modifierPos], match[directivePos] == "enable", line, ruleNames) + } + } + + comments := f.AST.Comments + for _, c := range comments { + handleComment(f.Name, c, f.ToPosition(c.End()).Line) + } + + return getEnabledDisabledIntervals() +} + +func (f *File) filterFailures(failures []Failure, disabledIntervals disabledIntervalsMap) []Failure { + result := []Failure{} + for _, failure := range failures { + fStart := failure.Position.Start.Line + fEnd := failure.Position.End.Line + intervals, ok := disabledIntervals[failure.RuleName] + if !ok { + result = append(result, failure) + } else { + include := true + for _, interval := range intervals { + intStart := interval.From.Line + intEnd := interval.To.Line + if (fStart >= intStart && fStart <= intEnd) || + (fEnd >= intStart && fEnd <= intEnd) { + include = false + break + } + } + if include { + result = append(result, failure) + } + } + } + return result +} diff --git a/vendor/github.com/mgechev/revive/lint/formatter.go b/vendor/github.com/mgechev/revive/lint/formatter.go new file mode 100644 index 0000000000..7c19af278a --- /dev/null +++ b/vendor/github.com/mgechev/revive/lint/formatter.go @@ -0,0 +1,14 @@ +package lint + +// FormatterMetadata configuration of a formatter +type FormatterMetadata struct { + Name string + Description string + Sample string +} + +// Formatter defines an interface for failure formatters +type Formatter interface { + Format(<-chan Failure, Config) (string, error) + Name() string +} diff --git a/vendor/github.com/mgechev/revive/lint/linter.go b/vendor/github.com/mgechev/revive/lint/linter.go new file mode 100644 index 0000000000..cdca84fb56 --- /dev/null +++ b/vendor/github.com/mgechev/revive/lint/linter.go @@ -0,0 +1,99 @@ +package lint + +import ( + "bufio" + "bytes" + "fmt" + "go/token" + "os" + "sync" +) + +// ReadFile defines an abstraction for reading files. +type ReadFile func(path string) (result []byte, err error) + +type disabledIntervalsMap = map[string][]DisabledInterval + +// Linter is used for linting set of files. +type Linter struct { + reader ReadFile +} + +// New creates a new Linter +func New(reader ReadFile) Linter { + return Linter{reader: reader} +} + +var ( + genHdr = []byte("// Code generated ") + genFtr = []byte(" DO NOT EDIT.") +) + +// Lint lints a set of files with the specified rule. +func (l *Linter) Lint(packages [][]string, ruleSet []Rule, config Config) (<-chan Failure, error) { + failures := make(chan Failure) + + var wg sync.WaitGroup + for _, pkg := range packages { + wg.Add(1) + go func(pkg []string) { + if err := l.lintPackage(pkg, ruleSet, config, failures); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + defer wg.Done() + }(pkg) + } + + go func() { + wg.Wait() + close(failures) + }() + + return failures, nil +} + +func (l *Linter) lintPackage(filenames []string, ruleSet []Rule, config Config, failures chan Failure) error { + pkg := &Package{ + fset: token.NewFileSet(), + files: map[string]*File{}, + mu: sync.Mutex{}, + } + for _, filename := range filenames { + content, err := l.reader(filename) + if err != nil { + return err + } + if isGenerated(content) && !config.IgnoreGeneratedHeader { + continue + } + + file, err := NewFile(filename, content, pkg) + if err != nil { + return err + } + pkg.files[filename] = file + } + + if len(pkg.files) == 0 { + return nil + } + + pkg.lint(ruleSet, config, failures) + + return nil +} + +// isGenerated reports whether the source file is generated code +// according the rules from https://golang.org/s/generatedcode. +// This is inherited from the original go lint. +func isGenerated(src []byte) bool { + sc := bufio.NewScanner(bytes.NewReader(src)) + for sc.Scan() { + b := sc.Bytes() + if bytes.HasPrefix(b, genHdr) && bytes.HasSuffix(b, genFtr) && len(b) >= len(genHdr)+len(genFtr) { + return true + } + } + return false +} diff --git a/vendor/github.com/mgechev/revive/lint/package.go b/vendor/github.com/mgechev/revive/lint/package.go new file mode 100644 index 0000000000..7b6046fd7e --- /dev/null +++ b/vendor/github.com/mgechev/revive/lint/package.go @@ -0,0 +1,178 @@ +package lint + +import ( + "go/ast" + "go/token" + "go/types" + "sync" + + "golang.org/x/tools/go/gcexportdata" +) + +// Package represents a package in the project. +type Package struct { + fset *token.FileSet + files map[string]*File + + TypesPkg *types.Package + TypesInfo *types.Info + + // sortable is the set of types in the package that implement sort.Interface. + Sortable map[string]bool + // main is whether this is a "main" package. + main int + mu sync.Mutex +} + +var newImporter = func(fset *token.FileSet) types.ImporterFrom { + return gcexportdata.NewImporter(fset, make(map[string]*types.Package)) +} + +var ( + trueValue = 1 + falseValue = 2 + notSet = 3 +) + +// IsMain returns if that's the main package. +func (p *Package) IsMain() bool { + if p.main == trueValue { + return true + } else if p.main == falseValue { + return false + } + for _, f := range p.files { + if f.isMain() { + p.main = trueValue + return true + } + } + p.main = falseValue + return false +} + +// TypeCheck performs type checking for given package. +func (p *Package) TypeCheck() error { + p.mu.Lock() + // If type checking has already been performed + // skip it. + if p.TypesInfo != nil || p.TypesPkg != nil { + p.mu.Unlock() + return nil + } + config := &types.Config{ + // By setting a no-op error reporter, the type checker does as much work as possible. + Error: func(error) {}, + Importer: newImporter(p.fset), + } + info := &types.Info{ + Types: make(map[ast.Expr]types.TypeAndValue), + Defs: make(map[*ast.Ident]types.Object), + Uses: make(map[*ast.Ident]types.Object), + Scopes: make(map[ast.Node]*types.Scope), + } + var anyFile *File + var astFiles []*ast.File + for _, f := range p.files { + anyFile = f + astFiles = append(astFiles, f.AST) + } + + typesPkg, err := check(config, anyFile.AST.Name.Name, p.fset, astFiles, info) + + // Remember the typechecking info, even if config.Check failed, + // since we will get partial information. + p.TypesPkg = typesPkg + p.TypesInfo = info + p.mu.Unlock() + return err +} + +// check function encapsulates the call to go/types.Config.Check method and +// recovers if the called method panics (see issue #59) +func check(config *types.Config, n string, fset *token.FileSet, astFiles []*ast.File, info *types.Info) (p *types.Package, err error) { + defer func() { + if r := recover(); r != nil { + err, _ = r.(error) + p = nil + return + } + }() + + return config.Check(n, fset, astFiles, info) +} + +// TypeOf returns the type of an expression. +func (p *Package) TypeOf(expr ast.Expr) types.Type { + if p.TypesInfo == nil { + return nil + } + return p.TypesInfo.TypeOf(expr) +} + +type walker struct { + nmap map[string]int + has map[string]int +} + +func (w *walker) Visit(n ast.Node) ast.Visitor { + fn, ok := n.(*ast.FuncDecl) + if !ok || fn.Recv == nil || len(fn.Recv.List) == 0 { + return w + } + // TODO(dsymonds): We could check the signature to be more precise. + recv := receiverType(fn) + if i, ok := w.nmap[fn.Name.Name]; ok { + w.has[recv] |= i + } + return w +} + +func (p *Package) scanSortable() { + p.Sortable = make(map[string]bool) + + // bitfield for which methods exist on each type. + const ( + Len = 1 << iota + Less + Swap + ) + nmap := map[string]int{"Len": Len, "Less": Less, "Swap": Swap} + has := make(map[string]int) + for _, f := range p.files { + ast.Walk(&walker{nmap, has}, f.AST) + } + for typ, ms := range has { + if ms == Len|Less|Swap { + p.Sortable[typ] = true + } + } +} + +// receiverType returns the named type of the method receiver, sans "*", +// or "invalid-type" if fn.Recv is ill formed. +func receiverType(fn *ast.FuncDecl) string { + switch e := fn.Recv.List[0].Type.(type) { + case *ast.Ident: + return e.Name + case *ast.StarExpr: + if id, ok := e.X.(*ast.Ident); ok { + return id.Name + } + } + // The parser accepts much more than just the legal forms. + return "invalid-type" +} + +func (p *Package) lint(rules []Rule, config Config, failures chan Failure) { + p.scanSortable() + var wg sync.WaitGroup + for _, file := range p.files { + wg.Add(1) + go (func(file *File) { + file.lint(rules, config, failures) + defer wg.Done() + })(file) + } + wg.Wait() +} diff --git a/vendor/github.com/mgechev/revive/lint/rule.go b/vendor/github.com/mgechev/revive/lint/rule.go new file mode 100644 index 0000000000..815abfdd88 --- /dev/null +++ b/vendor/github.com/mgechev/revive/lint/rule.go @@ -0,0 +1,31 @@ +package lint + +import ( + "go/token" +) + +// DisabledInterval contains a single disabled interval and the associated rule name. +type DisabledInterval struct { + From token.Position + To token.Position + RuleName string +} + +// Rule defines an abstract rule interaface +type Rule interface { + Name() string + Apply(*File, Arguments) []Failure +} + +// AbstractRule defines an abstract rule. +type AbstractRule struct { + Failures []Failure +} + +// ToFailurePosition returns the failure position. +func ToFailurePosition(start token.Pos, end token.Pos, file *File) FailurePosition { + return FailurePosition{ + Start: file.ToPosition(start), + End: file.ToPosition(end), + } +} diff --git a/vendor/github.com/mgechev/revive/lint/utils.go b/vendor/github.com/mgechev/revive/lint/utils.go new file mode 100644 index 0000000000..28657c6df0 --- /dev/null +++ b/vendor/github.com/mgechev/revive/lint/utils.go @@ -0,0 +1,128 @@ +package lint + +import ( + "strings" + "unicode" +) + +// Name returns a different name if it should be different. +func Name(name string, whitelist, blacklist []string) (should string) { + // Fast path for simple cases: "_" and all lowercase. + if name == "_" { + return name + } + allLower := true + for _, r := range name { + if !unicode.IsLower(r) { + allLower = false + break + } + } + if allLower { + return name + } + + // Split camelCase at any lower->upper transition, and split on underscores. + // Check each word for common initialisms. + runes := []rune(name) + w, i := 0, 0 // index of start of word, scan + for i+1 <= len(runes) { + eow := false // whether we hit the end of a word + if i+1 == len(runes) { + eow = true + } else if runes[i+1] == '_' { + // underscore; shift the remainder forward over any run of underscores + eow = true + n := 1 + for i+n+1 < len(runes) && runes[i+n+1] == '_' { + n++ + } + + // Leave at most one underscore if the underscore is between two digits + if i+n+1 < len(runes) && unicode.IsDigit(runes[i]) && unicode.IsDigit(runes[i+n+1]) { + n-- + } + + copy(runes[i+1:], runes[i+n+1:]) + runes = runes[:len(runes)-n] + } else if unicode.IsLower(runes[i]) && !unicode.IsLower(runes[i+1]) { + // lower->non-lower + eow = true + } + i++ + if !eow { + continue + } + + // [w,i) is a word. + word := string(runes[w:i]) + ignoreInitWarnings := map[string]bool{} + for _, i := range whitelist { + ignoreInitWarnings[i] = true + } + + extraInits := map[string]bool{} + for _, i := range blacklist { + extraInits[i] = true + } + + if u := strings.ToUpper(word); (commonInitialisms[u] || extraInits[u]) && !ignoreInitWarnings[u] { + // Keep consistent case, which is lowercase only at the start. + if w == 0 && unicode.IsLower(runes[w]) { + u = strings.ToLower(u) + } + // All the common initialisms are ASCII, + // so we can replace the bytes exactly. + copy(runes[w:], []rune(u)) + } else if w > 0 && strings.ToLower(word) == word { + // already all lowercase, and not the first word, so uppercase the first character. + runes[w] = unicode.ToUpper(runes[w]) + } + w = i + } + return string(runes) +} + +// commonInitialisms is a set of common initialisms. +// Only add entries that are highly unlikely to be non-initialisms. +// For instance, "ID" is fine (Freudian code is rare), but "AND" is not. +var commonInitialisms = map[string]bool{ + "ACL": true, + "API": true, + "ASCII": true, + "CPU": true, + "CSS": true, + "DNS": true, + "EOF": true, + "GUID": true, + "HTML": true, + "HTTP": true, + "HTTPS": true, + "ID": true, + "IP": true, + "JSON": true, + "LHS": true, + "QPS": true, + "RAM": true, + "RHS": true, + "RPC": true, + "SLA": true, + "SMTP": true, + "SQL": true, + "SSH": true, + "TCP": true, + "TLS": true, + "TTL": true, + "UDP": true, + "UI": true, + "UID": true, + "UUID": true, + "URI": true, + "URL": true, + "UTF8": true, + "VM": true, + "XML": true, + "XMPP": true, + "XSRF": true, + "XSS": true, +} diff --git a/vendor/github.com/mgechev/revive/rule/add-constant.go b/vendor/github.com/mgechev/revive/rule/add-constant.go new file mode 100644 index 0000000000..881bbd073f --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/add-constant.go @@ -0,0 +1,151 @@ +package rule + +import ( + "fmt" + "github.com/mgechev/revive/lint" + "go/ast" + "strconv" + "strings" +) + +const ( + defaultStrLitLimit = 2 + kindFLOAT = "FLOAT" + kindINT = "INT" + kindSTRING = "STRING" +) + +type whiteList map[string]map[string]bool + +func newWhiteList() whiteList { + return map[string]map[string]bool{kindINT: map[string]bool{}, kindFLOAT: map[string]bool{}, kindSTRING: map[string]bool{}} +} + +func (wl whiteList) add(kind string, list string) { + elems := strings.Split(list, ",") + for _, e := range elems { + wl[kind][e] = true + } +} + +// AddConstantRule lints unused params in functions. +type AddConstantRule struct{} + +// Apply applies the rule to given file. +func (r *AddConstantRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + strLitLimit := defaultStrLitLimit + var whiteList = newWhiteList() + if len(arguments) > 0 { + args, ok := arguments[0].(map[string]interface{}) + if !ok { + panic(fmt.Sprintf("Invalid argument to the add-constant rule. Expecting a k,v map, got %T", arguments[0])) + } + for k, v := range args { + kind := "" + switch k { + case "allowFloats": + kind = kindFLOAT + fallthrough + case "allowInts": + if kind == "" { + kind = kindINT + } + fallthrough + case "allowStrs": + if kind == "" { + kind = kindSTRING + } + list, ok := v.(string) + if !ok { + panic(fmt.Sprintf("Invalid argument to the add-constant rule, string expected. Got '%v' (%T)", v, v)) + } + whiteList.add(kind, list) + case "maxLitCount": + sl, ok := v.(string) + if !ok { + panic(fmt.Sprintf("Invalid argument to the add-constant rule, expecting string representation of an integer. Got '%v' (%T)", v, v)) + } + + limit, err := strconv.Atoi(sl) + if err != nil { + panic(fmt.Sprintf("Invalid argument to the add-constant rule, expecting string representation of an integer. Got '%v'", v)) + } + strLitLimit = limit + } + } + } + + var failures []lint.Failure + + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + w := lintAddConstantRule{onFailure: onFailure, strLits: make(map[string]int, 0), strLitLimit: strLitLimit, whiteLst: whiteList} + + ast.Walk(w, file.AST) + + return failures +} + +// Name returns the rule name. +func (r *AddConstantRule) Name() string { + return "add-constant" +} + +type lintAddConstantRule struct { + onFailure func(lint.Failure) + strLits map[string]int + strLitLimit int + whiteLst whiteList +} + +func (w lintAddConstantRule) Visit(node ast.Node) ast.Visitor { + switch n := node.(type) { + case *ast.GenDecl: + return nil // skip declarations + case *ast.BasicLit: + switch kind := n.Kind.String(); kind { + case kindFLOAT, kindINT: + w.checkNumLit(kind, n) + case kindSTRING: + w.checkStrLit(n) + } + } + + return w + +} + +func (w lintAddConstantRule) checkStrLit(n *ast.BasicLit) { + if w.whiteLst[kindSTRING][n.Value] { + return + } + + count := w.strLits[n.Value] + if count >= 0 { + w.strLits[n.Value] = count + 1 + if w.strLits[n.Value] > w.strLitLimit { + w.onFailure(lint.Failure{ + Confidence: 1, + Node: n, + Category: "style", + Failure: fmt.Sprintf("string literal %s appears, at least, %d times, create a named constant for it", n.Value, w.strLits[n.Value]), + }) + w.strLits[n.Value] = -1 // mark it to avoid failing again on the same literal + } + } +} + +func (w lintAddConstantRule) checkNumLit(kind string, n *ast.BasicLit) { + if w.whiteLst[kind][n.Value] { + return + } + + w.onFailure(lint.Failure{ + Confidence: 1, + Node: n, + Category: "style", + Failure: fmt.Sprintf("avoid magic numbers like '%s', create a named constant for it", n.Value), + }) +} diff --git a/vendor/github.com/mgechev/revive/rule/argument-limit.go b/vendor/github.com/mgechev/revive/rule/argument-limit.go new file mode 100644 index 0000000000..2b11d49825 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/argument-limit.go @@ -0,0 +1,67 @@ +package rule + +import ( + "fmt" + "go/ast" + + "github.com/mgechev/revive/lint" +) + +// ArgumentsLimitRule lints given else constructs. +type ArgumentsLimitRule struct{} + +// Apply applies the rule to given file. +func (r *ArgumentsLimitRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + if len(arguments) != 1 { + panic(`invalid configuration for "argument-limit"`) + } + + total, ok := arguments[0].(int64) // Alt. non panicking version + if !ok { + panic(`invalid value passed as argument number to the "argument-list" rule`) + } + + var failures []lint.Failure + + walker := lintArgsNum{ + total: int(total), + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + } + + ast.Walk(walker, file.AST) + + return failures +} + +// Name returns the rule name. +func (r *ArgumentsLimitRule) Name() string { + return "argument-limit" +} + +type lintArgsNum struct { + total int + onFailure func(lint.Failure) +} + +func (w lintArgsNum) Visit(n ast.Node) ast.Visitor { + node, ok := n.(*ast.FuncDecl) + if ok { + num := 0 + for _, l := range node.Type.Params.List { + for range l.Names { + num++ + } + } + if num > w.total { + w.onFailure(lint.Failure{ + Confidence: 1, + Failure: fmt.Sprintf("maximum number of arguments per function exceeded; max %d but got %d", w.total, num), + Node: node.Type, + }) + return w + } + } + return w +} diff --git a/vendor/github.com/mgechev/revive/rule/atomic.go b/vendor/github.com/mgechev/revive/rule/atomic.go new file mode 100644 index 0000000000..572e141da9 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/atomic.go @@ -0,0 +1,94 @@ +package rule + +import ( + "go/ast" + "go/token" + "go/types" + + "github.com/mgechev/revive/lint" +) + +// AtomicRule lints given else constructs. +type AtomicRule struct{} + +// Apply applies the rule to given file. +func (r *AtomicRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + walker := atomic{ + pkgTypesInfo: file.Pkg.TypesInfo, + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + } + + ast.Walk(walker, file.AST) + + return failures +} + +// Name returns the rule name. +func (r *AtomicRule) Name() string { + return "atomic" +} + +type atomic struct { + pkgTypesInfo *types.Info + onFailure func(lint.Failure) +} + +func (w atomic) Visit(node ast.Node) ast.Visitor { + n, ok := node.(*ast.AssignStmt) + if !ok { + return w + } + + if len(n.Lhs) != len(n.Rhs) { + return nil // skip assignment sub-tree + } + if len(n.Lhs) == 1 && n.Tok == token.DEFINE { + return nil // skip assignment sub-tree + } + + for i, right := range n.Rhs { + call, ok := right.(*ast.CallExpr) + if !ok { + continue + } + sel, ok := call.Fun.(*ast.SelectorExpr) + if !ok { + continue + } + pkgIdent, _ := sel.X.(*ast.Ident) + if w.pkgTypesInfo != nil { + pkgName, ok := w.pkgTypesInfo.Uses[pkgIdent].(*types.PkgName) + if !ok || pkgName.Imported().Path() != "sync/atomic" { + continue + } + } + + switch sel.Sel.Name { + case "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr": + left := n.Lhs[i] + if len(call.Args) != 2 { + continue + } + arg := call.Args[0] + broken := false + + if uarg, ok := arg.(*ast.UnaryExpr); ok && uarg.Op == token.AND { + broken = gofmt(left) == gofmt(uarg.X) + } else if star, ok := left.(*ast.StarExpr); ok { + broken = gofmt(star.X) == gofmt(arg) + } + + if broken { + w.onFailure(lint.Failure{ + Confidence: 1, + Failure: "direct assignment to atomic value", + Node: n, + }) + } + } + } + return w +} diff --git a/vendor/github.com/mgechev/revive/rule/bare-return.go b/vendor/github.com/mgechev/revive/rule/bare-return.go new file mode 100644 index 0000000000..3ee4c4adc2 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/bare-return.go @@ -0,0 +1,84 @@ +package rule + +import ( + "go/ast" + + "github.com/mgechev/revive/lint" +) + +// BareReturnRule lints given else constructs. +type BareReturnRule struct{} + +// Apply applies the rule to given file. +func (r *BareReturnRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + w := lintBareReturnRule{onFailure: onFailure} + ast.Walk(w, file.AST) + return failures +} + +// Name returns the rule name. +func (r *BareReturnRule) Name() string { + return "bare-return" +} + +type lintBareReturnRule struct { + onFailure func(lint.Failure) +} + +func (w lintBareReturnRule) Visit(node ast.Node) ast.Visitor { + switch n := node.(type) { + case *ast.FuncDecl: + w.checkFunc(n.Type.Results, n.Body) + case *ast.FuncLit: // to cope with deferred functions and go-routines + w.checkFunc(n.Type.Results, n.Body) + } + + return w +} + +// checkFunc will verify if the given function has named result and bare returns +func (w lintBareReturnRule) checkFunc(results *ast.FieldList, body *ast.BlockStmt) { + hasNamedResults := results != nil && len(results.List) > 0 && results.List[0].Names != nil + if !hasNamedResults || body == nil { + return // nothing to do + } + + brf := bareReturnFinder{w.onFailure} + ast.Walk(brf, body) +} + +type bareReturnFinder struct { + onFailure func(lint.Failure) +} + +func (w bareReturnFinder) Visit(node ast.Node) ast.Visitor { + _, ok := node.(*ast.FuncLit) + if ok { + // skip analysing function literals + // they will analyzed by the lintBareReturnRule.Visit method + return nil + } + + rs, ok := node.(*ast.ReturnStmt) + if !ok { + return w + } + + if len(rs.Results) > 0 { + return w + } + + w.onFailure(lint.Failure{ + Confidence: 1, + Node: rs, + Failure: "avoid using bare returns, please add return expressions", + }) + + return w +} diff --git a/vendor/github.com/mgechev/revive/rule/blank-imports.go b/vendor/github.com/mgechev/revive/rule/blank-imports.go new file mode 100644 index 0000000000..0a00e3707d --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/blank-imports.go @@ -0,0 +1,74 @@ +package rule + +import ( + "go/ast" + + "github.com/mgechev/revive/lint" +) + +// BlankImportsRule lints given else constructs. +type BlankImportsRule struct{} + +// Apply applies the rule to given file. +func (r *BlankImportsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + fileAst := file.AST + walker := lintBlankImports{ + file: file, + fileAst: fileAst, + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + } + + ast.Walk(walker, fileAst) + + return failures +} + +// Name returns the rule name. +func (r *BlankImportsRule) Name() string { + return "blank-imports" +} + +type lintBlankImports struct { + fileAst *ast.File + file *lint.File + onFailure func(lint.Failure) +} + +func (w lintBlankImports) Visit(_ ast.Node) ast.Visitor { + // In package main and in tests, we don't complain about blank imports. + if w.file.Pkg.IsMain() || w.file.IsTest() { + return nil + } + + // The first element of each contiguous group of blank imports should have + // an explanatory comment of some kind. + for i, imp := range w.fileAst.Imports { + pos := w.file.ToPosition(imp.Pos()) + + if !isBlank(imp.Name) { + continue // Ignore non-blank imports. + } + if i > 0 { + prev := w.fileAst.Imports[i-1] + prevPos := w.file.ToPosition(prev.Pos()) + if isBlank(prev.Name) && prevPos.Line+1 == pos.Line { + continue // A subsequent blank in a group. + } + } + + // This is the first blank import of a group. + if imp.Doc == nil && imp.Comment == nil { + w.onFailure(lint.Failure{ + Node: imp, + Failure: "a blank import should be only in a main or test package, or have a comment justifying it", + Confidence: 1, + Category: "imports", + }) + } + } + return nil +} diff --git a/vendor/github.com/mgechev/revive/rule/bool-literal-in-expr.go b/vendor/github.com/mgechev/revive/rule/bool-literal-in-expr.go new file mode 100644 index 0000000000..0a4e696c63 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/bool-literal-in-expr.go @@ -0,0 +1,73 @@ +package rule + +import ( + "go/ast" + "go/token" + + "github.com/mgechev/revive/lint" +) + +// BoolLiteralRule warns when logic expressions contains Boolean literals. +type BoolLiteralRule struct{} + +// Apply applies the rule to given file. +func (r *BoolLiteralRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + astFile := file.AST + w := &lintBoolLiteral{astFile, onFailure} + ast.Walk(w, astFile) + + return failures +} + +// Name returns the rule name. +func (r *BoolLiteralRule) Name() string { + return "bool-literal-in-expr" +} + +type lintBoolLiteral struct { + file *ast.File + onFailure func(lint.Failure) +} + +func (w *lintBoolLiteral) Visit(node ast.Node) ast.Visitor { + switch n := node.(type) { + case *ast.BinaryExpr: + if !isBoolOp(n.Op) { + return w + } + + lexeme, ok := isExprABooleanLit(n.X) + if !ok { + lexeme, ok = isExprABooleanLit(n.Y) + + if !ok { + return w + } + } + + isConstant := (n.Op == token.LAND && lexeme == "false") || (n.Op == token.LOR && lexeme == "true") + + if isConstant { + w.addFailure(n, "Boolean expression seems to always evaluate to "+lexeme, "logic") + } else { + w.addFailure(n, "omit Boolean literal in expression", "style") + } + } + + return w +} + +func (w lintBoolLiteral) addFailure(node ast.Node, msg string, cat string) { + w.onFailure(lint.Failure{ + Confidence: 1, + Node: node, + Category: cat, + Failure: msg, + }) +} diff --git a/vendor/github.com/mgechev/revive/rule/call-to-gc.go b/vendor/github.com/mgechev/revive/rule/call-to-gc.go new file mode 100644 index 0000000000..06126611bc --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/call-to-gc.go @@ -0,0 +1,70 @@ +package rule + +import ( + "go/ast" + + "github.com/mgechev/revive/lint" +) + +// CallToGCRule lints calls to the garbage collector. +type CallToGCRule struct{} + +// Apply applies the rule to given file. +func (r *CallToGCRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + var gcTriggeringFunctions = map[string]map[string]bool{ + "runtime": map[string]bool{"GC": true}, + } + + w := lintCallToGC{onFailure, gcTriggeringFunctions} + ast.Walk(w, file.AST) + + return failures +} + +// Name returns the rule name. +func (r *CallToGCRule) Name() string { + return "call-to-gc" +} + +type lintCallToGC struct { + onFailure func(lint.Failure) + gcTriggeringFunctions map[string]map[string]bool +} + +func (w lintCallToGC) Visit(node ast.Node) ast.Visitor { + ce, ok := node.(*ast.CallExpr) + if !ok { + return w // nothing to do, the node is not a call + } + + fc, ok := ce.Fun.(*ast.SelectorExpr) + if !ok { + return nil // nothing to do, the call is not of the form pkg.func(...) + } + + id, ok := fc.X.(*ast.Ident) + + if !ok { + return nil // in case X is not an id (it should be!) + } + + fn := fc.Sel.Name + pkg := id.Name + if !w.gcTriggeringFunctions[pkg][fn] { + return nil // it isn't a call to a GC triggering function + } + + w.onFailure(lint.Failure{ + Confidence: 1, + Node: node, + Category: "bad practice", + Failure: "explicit call to the garbage collector", + }) + + return w +} diff --git a/vendor/github.com/mgechev/revive/rule/cognitive-complexity.go b/vendor/github.com/mgechev/revive/rule/cognitive-complexity.go new file mode 100644 index 0000000000..711aa22897 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/cognitive-complexity.go @@ -0,0 +1,195 @@ +package rule + +import ( + "fmt" + "go/ast" + "go/token" + + "github.com/mgechev/revive/lint" + "golang.org/x/tools/go/ast/astutil" +) + +// CognitiveComplexityRule lints given else constructs. +type CognitiveComplexityRule struct{} + +// Apply applies the rule to given file. +func (r *CognitiveComplexityRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + var failures []lint.Failure + + const expectedArgumentsCount = 1 + if len(arguments) < expectedArgumentsCount { + panic(fmt.Sprintf("not enough arguments for cognitive-complexity, expected %d, got %d", expectedArgumentsCount, len(arguments))) + } + complexity, ok := arguments[0].(int64) + if !ok { + panic(fmt.Sprintf("invalid argument type for cognitive-complexity, expected int64, got %T", arguments[0])) + } + + linter := cognitiveComplexityLinter{ + file: file, + maxComplexity: int(complexity), + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + } + + linter.lint() + + return failures +} + +// Name returns the rule name. +func (r *CognitiveComplexityRule) Name() string { + return "cognitive-complexity" +} + +type cognitiveComplexityLinter struct { + file *lint.File + maxComplexity int + onFailure func(lint.Failure) +} + +func (w cognitiveComplexityLinter) lint() { + f := w.file + for _, decl := range f.AST.Decls { + if fn, ok := decl.(*ast.FuncDecl); ok { + v := cognitiveComplexityVisitor{} + c := v.subTreeComplexity(fn.Body) + if c > w.maxComplexity { + w.onFailure(lint.Failure{ + Confidence: 1, + Category: "maintenance", + Failure: fmt.Sprintf("function %s has cognitive complexity %d (> max enabled %d)", funcName(fn), c, w.maxComplexity), + Node: fn, + }) + } + } + } +} + +type cognitiveComplexityVisitor struct { + complexity int + nestingLevel int +} + +// subTreeComplexity calculates the cognitive complexity of an AST-subtree. +func (v cognitiveComplexityVisitor) subTreeComplexity(n ast.Node) int { + ast.Walk(&v, n) + return v.complexity +} + +// Visit implements the ast.Visitor interface. +func (v *cognitiveComplexityVisitor) Visit(n ast.Node) ast.Visitor { + switch n := n.(type) { + case *ast.IfStmt: + targets := []ast.Node{n.Cond, n.Body, n.Else} + v.walk(1, targets...) + return nil + case *ast.ForStmt: + targets := []ast.Node{n.Cond, n.Body} + v.walk(1, targets...) + return nil + case *ast.RangeStmt: + v.walk(1, n.Body) + return nil + case *ast.SelectStmt: + v.walk(1, n.Body) + return nil + case *ast.SwitchStmt: + v.walk(1, n.Body) + return nil + case *ast.TypeSwitchStmt: + v.walk(1, n.Body) + return nil + case *ast.FuncLit: + v.walk(0, n.Body) // do not increment the complexity, just do the nesting + return nil + case *ast.BinaryExpr: + v.complexity += v.binExpComplexity(n) + return nil // skip visiting binexp sub-tree (already visited by binExpComplexity) + case *ast.BranchStmt: + if n.Label != nil { + v.complexity += 1 + } + } + // TODO handle (at least) direct recursion + + return v +} + +func (v *cognitiveComplexityVisitor) walk(complexityIncrement int, targets ...ast.Node) { + v.complexity += complexityIncrement + v.nestingLevel + nesting := v.nestingLevel + v.nestingLevel++ + + for _, t := range targets { + if t == nil { + continue + } + + ast.Walk(v, t) + } + + v.nestingLevel = nesting +} + +func (cognitiveComplexityVisitor) binExpComplexity(n *ast.BinaryExpr) int { + calculator := binExprComplexityCalculator{opsStack: []token.Token{}} + + astutil.Apply(n, calculator.pre, calculator.post) + + return calculator.complexity +} + +type binExprComplexityCalculator struct { + complexity int + opsStack []token.Token // stack of bool operators + subexpStarted bool +} + +func (becc *binExprComplexityCalculator) pre(c *astutil.Cursor) bool { + switch n := c.Node().(type) { + case *ast.BinaryExpr: + isBoolOp := n.Op == token.LAND || n.Op == token.LOR + if !isBoolOp { + break + } + + ops := len(becc.opsStack) + // if + // is the first boolop in the expression OR + // is the first boolop inside a subexpression (...) OR + // is not the same to the previous one + // then + // increment complexity + if ops == 0 || becc.subexpStarted || n.Op != becc.opsStack[ops-1] { + becc.complexity++ + becc.subexpStarted = false + } + + becc.opsStack = append(becc.opsStack, n.Op) + case *ast.ParenExpr: + becc.subexpStarted = true + } + + return true +} + +func (becc *binExprComplexityCalculator) post(c *astutil.Cursor) bool { + switch n := c.Node().(type) { + case *ast.BinaryExpr: + isBoolOp := n.Op == token.LAND || n.Op == token.LOR + if !isBoolOp { + break + } + + ops := len(becc.opsStack) + if ops > 0 { + becc.opsStack = becc.opsStack[:ops-1] + } + case *ast.ParenExpr: + becc.subexpStarted = false + } + + return true +} diff --git a/vendor/github.com/mgechev/revive/rule/confusing-naming.go b/vendor/github.com/mgechev/revive/rule/confusing-naming.go new file mode 100644 index 0000000000..143bb18c33 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/confusing-naming.go @@ -0,0 +1,190 @@ +package rule + +import ( + "fmt" + "go/ast" + + "strings" + "sync" + + "github.com/mgechev/revive/lint" +) + +type referenceMethod struct { + fileName string + id *ast.Ident +} + +type pkgMethods struct { + pkg *lint.Package + methods map[string]map[string]*referenceMethod + mu *sync.Mutex +} + +type packages struct { + pkgs []pkgMethods + mu sync.Mutex +} + +func (ps *packages) methodNames(lp *lint.Package) pkgMethods { + ps.mu.Lock() + + for _, pkg := range ps.pkgs { + if pkg.pkg == lp { + ps.mu.Unlock() + return pkg + } + } + + pkgm := pkgMethods{pkg: lp, methods: make(map[string]map[string]*referenceMethod), mu: &sync.Mutex{}} + ps.pkgs = append(ps.pkgs, pkgm) + + ps.mu.Unlock() + return pkgm +} + +var allPkgs = packages{pkgs: make([]pkgMethods, 1)} + +// ConfusingNamingRule lints method names that differ only by capitalization +type ConfusingNamingRule struct{} + +// Apply applies the rule to given file. +func (r *ConfusingNamingRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + fileAst := file.AST + pkgm := allPkgs.methodNames(file.Pkg) + walker := lintConfusingNames{ + fileName: file.Name, + pkgm: pkgm, + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + } + + ast.Walk(&walker, fileAst) + + return failures +} + +// Name returns the rule name. +func (r *ConfusingNamingRule) Name() string { + return "confusing-naming" +} + +//checkMethodName checks if a given method/function name is similar (just case differences) to other method/function of the same struct/file. +func checkMethodName(holder string, id *ast.Ident, w *lintConfusingNames) { + if id.Name == "init" && holder == defaultStructName { + // ignore init functions + return + } + + pkgm := w.pkgm + name := strings.ToUpper(id.Name) + + pkgm.mu.Lock() + defer pkgm.mu.Unlock() + + if pkgm.methods[holder] != nil { + if pkgm.methods[holder][name] != nil { + refMethod := pkgm.methods[holder][name] + // confusing names + var kind string + if holder == defaultStructName { + kind = "function" + } else { + kind = "method" + } + var fileName string + if w.fileName == refMethod.fileName { + fileName = "the same source file" + } else { + fileName = refMethod.fileName + } + w.onFailure(lint.Failure{ + Failure: fmt.Sprintf("Method '%s' differs only by capitalization to %s '%s' in %s", id.Name, kind, refMethod.id.Name, fileName), + Confidence: 1, + Node: id, + Category: "naming", + }) + + return + } + } else { + pkgm.methods[holder] = make(map[string]*referenceMethod, 1) + } + + // update the black list + if pkgm.methods[holder] == nil { + println("no entry for '", holder, "'") + } + pkgm.methods[holder][name] = &referenceMethod{fileName: w.fileName, id: id} +} + +type lintConfusingNames struct { + fileName string + pkgm pkgMethods + onFailure func(lint.Failure) +} + +const defaultStructName = "_" // used to map functions + +//getStructName of a function receiver. Defaults to defaultStructName +func getStructName(r *ast.FieldList) string { + result := defaultStructName + + if r == nil || len(r.List) < 1 { + return result + } + + t := r.List[0].Type + + if p, _ := t.(*ast.StarExpr); p != nil { // if a pointer receiver => dereference pointer receiver types + t = p.X + } + + if p, _ := t.(*ast.Ident); p != nil { + result = p.Name + } + + return result +} + +func checkStructFields(fields *ast.FieldList, structName string, w *lintConfusingNames) { + bl := make(map[string]bool, len(fields.List)) + for _, f := range fields.List { + for _, id := range f.Names { + normName := strings.ToUpper(id.Name) + if bl[normName] { + w.onFailure(lint.Failure{ + Failure: fmt.Sprintf("Field '%s' differs only by capitalization to other field in the struct type %s", id.Name, structName), + Confidence: 1, + Node: id, + Category: "naming", + }) + } else { + bl[normName] = true + } + } + } +} + +func (w *lintConfusingNames) Visit(n ast.Node) ast.Visitor { + switch v := n.(type) { + case *ast.FuncDecl: + // Exclude naming warnings for functions that are exported to C but + // not exported in the Go API. + // See https://github.com/golang/lint/issues/144. + if ast.IsExported(v.Name.Name) || !isCgoExported(v) { + checkMethodName(getStructName(v.Recv), v.Name, w) + } + case *ast.TypeSpec: + if s, ok := v.Type.(*ast.StructType); ok { + checkStructFields(s.Fields, v.Name.Name, w) + } + + default: + // will add other checks like field names, struct names, etc. + } + + return w +} diff --git a/vendor/github.com/mgechev/revive/rule/confusing-results.go b/vendor/github.com/mgechev/revive/rule/confusing-results.go new file mode 100644 index 0000000000..1d386b3db5 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/confusing-results.go @@ -0,0 +1,67 @@ +package rule + +import ( + "go/ast" + + "github.com/mgechev/revive/lint" +) + +// ConfusingResultsRule lints given function declarations +type ConfusingResultsRule struct{} + +// Apply applies the rule to given file. +func (r *ConfusingResultsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + fileAst := file.AST + walker := lintConfusingResults{ + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + } + + ast.Walk(walker, fileAst) + + return failures +} + +// Name returns the rule name. +func (r *ConfusingResultsRule) Name() string { + return "confusing-results" +} + +type lintConfusingResults struct { + onFailure func(lint.Failure) +} + +func (w lintConfusingResults) Visit(n ast.Node) ast.Visitor { + fn, ok := n.(*ast.FuncDecl) + if !ok || fn.Type.Results == nil || len(fn.Type.Results.List) < 2 { + return w + } + lastType := "" + for _, result := range fn.Type.Results.List { + if len(result.Names) > 0 { + return w + } + + t, ok := result.Type.(*ast.Ident) + if !ok { + return w + } + + if t.Name == lastType { + w.onFailure(lint.Failure{ + Node: n, + Confidence: 1, + Category: "naming", + Failure: "unnamed results of the same type may be confusing, consider using named results", + }) + break + } + lastType = t.Name + + } + + return w +} diff --git a/vendor/github.com/mgechev/revive/rule/constant-logical-expr.go b/vendor/github.com/mgechev/revive/rule/constant-logical-expr.go new file mode 100644 index 0000000000..6a91561111 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/constant-logical-expr.go @@ -0,0 +1,88 @@ +package rule + +import ( + "github.com/mgechev/revive/lint" + "go/ast" + "go/token" +) + +// ConstantLogicalExprRule warns on constant logical expressions. +type ConstantLogicalExprRule struct{} + +// Apply applies the rule to given file. +func (r *ConstantLogicalExprRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + astFile := file.AST + w := &lintConstantLogicalExpr{astFile, onFailure} + ast.Walk(w, astFile) + return failures +} + +// Name returns the rule name. +func (r *ConstantLogicalExprRule) Name() string { + return "constant-logical-expr" +} + +type lintConstantLogicalExpr struct { + file *ast.File + onFailure func(lint.Failure) +} + +func (w *lintConstantLogicalExpr) Visit(node ast.Node) ast.Visitor { + switch n := node.(type) { + case *ast.BinaryExpr: + if !w.isOperatorWithLogicalResult(n.Op) { + return w + } + + if gofmt(n.X) != gofmt(n.Y) { // check if subexpressions are the same + return w + } + + if n.Op == token.EQL { + w.newFailure(n, "expression always evaluates to true") + return w + } + + if w.isInequalityOperator(n.Op) { + w.newFailure(n, "expression always evaluates to false") + return w + } + + w.newFailure(n, "left and right hand-side sub-expressions are the same") + } + + return w +} + +func (w *lintConstantLogicalExpr) isOperatorWithLogicalResult(t token.Token) bool { + switch t { + case token.LAND, token.LOR, token.EQL, token.LSS, token.GTR, token.NEQ, token.LEQ, token.GEQ: + return true + } + + return false +} + +func (w *lintConstantLogicalExpr) isInequalityOperator(t token.Token) bool { + switch t { + case token.LSS, token.GTR, token.NEQ, token.LEQ, token.GEQ: + return true + } + + return false +} + +func (w lintConstantLogicalExpr) newFailure(node ast.Node, msg string) { + w.onFailure(lint.Failure{ + Confidence: 1, + Node: node, + Category: "logic", + Failure: msg, + }) +} diff --git a/vendor/github.com/mgechev/revive/rule/context-as-argument.go b/vendor/github.com/mgechev/revive/rule/context-as-argument.go new file mode 100644 index 0000000000..0ed28a82a5 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/context-as-argument.go @@ -0,0 +1,60 @@ +package rule + +import ( + "go/ast" + + "github.com/mgechev/revive/lint" +) + +// ContextAsArgumentRule lints given else constructs. +type ContextAsArgumentRule struct{} + +// Apply applies the rule to given file. +func (r *ContextAsArgumentRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + fileAst := file.AST + walker := lintContextArguments{ + file: file, + fileAst: fileAst, + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + } + + ast.Walk(walker, fileAst) + + return failures +} + +// Name returns the rule name. +func (r *ContextAsArgumentRule) Name() string { + return "context-as-argument" +} + +type lintContextArguments struct { + file *lint.File + fileAst *ast.File + onFailure func(lint.Failure) +} + +func (w lintContextArguments) Visit(n ast.Node) ast.Visitor { + fn, ok := n.(*ast.FuncDecl) + if !ok || len(fn.Type.Params.List) <= 1 { + return w + } + // A context.Context should be the first parameter of a function. + // Flag any that show up after the first. + for _, arg := range fn.Type.Params.List[1:] { + if isPkgDot(arg.Type, "context", "Context") { + w.onFailure(lint.Failure{ + Node: fn, + Category: "arg-order", + Failure: "context.Context should be the first parameter of a function", + Confidence: 0.9, + }) + break // only flag one + } + } + return w +} diff --git a/vendor/github.com/mgechev/revive/rule/context-keys-type.go b/vendor/github.com/mgechev/revive/rule/context-keys-type.go new file mode 100644 index 0000000000..9c2f0bbd7d --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/context-keys-type.go @@ -0,0 +1,81 @@ +package rule + +import ( + "fmt" + "go/ast" + "go/types" + + "github.com/mgechev/revive/lint" +) + +// ContextKeysType lints given else constructs. +type ContextKeysType struct{} + +// Apply applies the rule to given file. +func (r *ContextKeysType) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + fileAst := file.AST + walker := lintContextKeyTypes{ + file: file, + fileAst: fileAst, + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + } + + file.Pkg.TypeCheck() + ast.Walk(walker, fileAst) + + return failures +} + +// Name returns the rule name. +func (r *ContextKeysType) Name() string { + return "context-keys-type" +} + +type lintContextKeyTypes struct { + file *lint.File + fileAst *ast.File + onFailure func(lint.Failure) +} + +func (w lintContextKeyTypes) Visit(n ast.Node) ast.Visitor { + switch n := n.(type) { + case *ast.CallExpr: + checkContextKeyType(w, n) + } + + return w +} + +func checkContextKeyType(w lintContextKeyTypes, x *ast.CallExpr) { + f := w.file + sel, ok := x.Fun.(*ast.SelectorExpr) + if !ok { + return + } + pkg, ok := sel.X.(*ast.Ident) + if !ok || pkg.Name != "context" { + return + } + if sel.Sel.Name != "WithValue" { + return + } + + // key is second argument to context.WithValue + if len(x.Args) != 3 { + return + } + key := f.Pkg.TypesInfo.Types[x.Args[1]] + + if ktyp, ok := key.Type.(*types.Basic); ok && ktyp.Kind() != types.Invalid { + w.onFailure(lint.Failure{ + Confidence: 1, + Node: x, + Category: "content", + Failure: fmt.Sprintf("should not use basic type %s as key in context.WithValue", key.Type), + }) + } +} diff --git a/vendor/github.com/mgechev/revive/rule/cyclomatic.go b/vendor/github.com/mgechev/revive/rule/cyclomatic.go new file mode 100644 index 0000000000..48ea80a6aa --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/cyclomatic.go @@ -0,0 +1,115 @@ +package rule + +import ( + "fmt" + "go/ast" + "go/token" + + "github.com/mgechev/revive/lint" +) + +// Based on https://github.com/fzipp/gocyclo + +// CyclomaticRule lints given else constructs. +type CyclomaticRule struct{} + +// Apply applies the rule to given file. +func (r *CyclomaticRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + var failures []lint.Failure + + complexity, ok := arguments[0].(int64) // Alt. non panicking version + if !ok { + panic("invalid argument for cyclomatic complexity") + } + + fileAst := file.AST + walker := lintCyclomatic{ + file: file, + complexity: int(complexity), + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + } + + ast.Walk(walker, fileAst) + + return failures +} + +// Name returns the rule name. +func (r *CyclomaticRule) Name() string { + return "cyclomatic" +} + +type lintCyclomatic struct { + file *lint.File + complexity int + onFailure func(lint.Failure) +} + +func (w lintCyclomatic) Visit(_ ast.Node) ast.Visitor { + f := w.file + for _, decl := range f.AST.Decls { + if fn, ok := decl.(*ast.FuncDecl); ok { + c := complexity(fn) + if c > w.complexity { + w.onFailure(lint.Failure{ + Confidence: 1, + Category: "maintenance", + Failure: fmt.Sprintf("function %s has cyclomatic complexity %d", funcName(fn), c), + Node: fn, + }) + } + } + } + return nil +} + +// funcName returns the name representation of a function or method: +// "(Type).Name" for methods or simply "Name" for functions. +func funcName(fn *ast.FuncDecl) string { + if fn.Recv != nil { + if fn.Recv.NumFields() > 0 { + typ := fn.Recv.List[0].Type + return fmt.Sprintf("(%s).%s", recvString(typ), fn.Name) + } + } + return fn.Name.Name +} + +// recvString returns a string representation of recv of the +// form "T", "*T", or "BADRECV" (if not a proper receiver type). +func recvString(recv ast.Expr) string { + switch t := recv.(type) { + case *ast.Ident: + return t.Name + case *ast.StarExpr: + return "*" + recvString(t.X) + } + return "BADRECV" +} + +// complexity calculates the cyclomatic complexity of a function. +func complexity(fn *ast.FuncDecl) int { + v := complexityVisitor{} + ast.Walk(&v, fn) + return v.Complexity +} + +type complexityVisitor struct { + // Complexity is the cyclomatic complexity + Complexity int +} + +// Visit implements the ast.Visitor interface. +func (v *complexityVisitor) Visit(n ast.Node) ast.Visitor { + switch n := n.(type) { + case *ast.FuncDecl, *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt, *ast.CaseClause, *ast.CommClause: + v.Complexity++ + case *ast.BinaryExpr: + if n.Op == token.LAND || n.Op == token.LOR { + v.Complexity++ + } + } + return v +} diff --git a/vendor/github.com/mgechev/revive/rule/deep-exit.go b/vendor/github.com/mgechev/revive/rule/deep-exit.go new file mode 100644 index 0000000000..f49e93dd47 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/deep-exit.go @@ -0,0 +1,94 @@ +package rule + +import ( + "fmt" + "go/ast" + + "github.com/mgechev/revive/lint" +) + +// DeepExitRule lints program exit at functions other than main or init. +type DeepExitRule struct{} + +// Apply applies the rule to given file. +func (r *DeepExitRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + var exitFunctions = map[string]map[string]bool{ + "os": map[string]bool{"Exit": true}, + "syscall": map[string]bool{"Exit": true}, + "log": map[string]bool{ + "Fatal": true, + "Fatalf": true, + "Fatalln": true, + "Panic": true, + "Panicf": true, + "Panicln": true, + }, + } + + w := lintDeepExit{onFailure, exitFunctions, file.IsTest()} + ast.Walk(w, file.AST) + return failures +} + +// Name returns the rule name. +func (r *DeepExitRule) Name() string { + return "deep-exit" +} + +type lintDeepExit struct { + onFailure func(lint.Failure) + exitFunctions map[string]map[string]bool + isTestFile bool +} + +func (w lintDeepExit) Visit(node ast.Node) ast.Visitor { + if fd, ok := node.(*ast.FuncDecl); ok { + if w.mustIgnore(fd) { + return nil // skip analysis of this function + } + + return w + } + + se, ok := node.(*ast.ExprStmt) + if !ok { + return w + } + ce, ok := se.X.(*ast.CallExpr) + if !ok { + return w + } + + fc, ok := ce.Fun.(*ast.SelectorExpr) + if !ok { + return w + } + id, ok := fc.X.(*ast.Ident) + if !ok { + return w + } + + fn := fc.Sel.Name + pkg := id.Name + if w.exitFunctions[pkg] != nil && w.exitFunctions[pkg][fn] { // it's a call to an exit function + w.onFailure(lint.Failure{ + Confidence: 1, + Node: ce, + Category: "bad practice", + Failure: fmt.Sprintf("calls to %s.%s only in main() or init() functions", pkg, fn), + }) + } + + return w +} + +func (w *lintDeepExit) mustIgnore(fd *ast.FuncDecl) bool { + fn := fd.Name.Name + + return fn == "init" || fn == "main" || (w.isTestFile && fn == "TestMain") +} diff --git a/vendor/github.com/mgechev/revive/rule/dot-imports.go b/vendor/github.com/mgechev/revive/rule/dot-imports.go new file mode 100644 index 0000000000..78419d7d6a --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/dot-imports.go @@ -0,0 +1,54 @@ +package rule + +import ( + "go/ast" + + "github.com/mgechev/revive/lint" +) + +// DotImportsRule lints given else constructs. +type DotImportsRule struct{} + +// Apply applies the rule to given file. +func (r *DotImportsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + fileAst := file.AST + walker := lintImports{ + file: file, + fileAst: fileAst, + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + } + + ast.Walk(walker, fileAst) + + return failures +} + +// Name returns the rule name. +func (r *DotImportsRule) Name() string { + return "dot-imports" +} + +type lintImports struct { + file *lint.File + fileAst *ast.File + onFailure func(lint.Failure) +} + +func (w lintImports) Visit(_ ast.Node) ast.Visitor { + for i, is := range w.fileAst.Imports { + _ = i + if is.Name != nil && is.Name.Name == "." && !w.file.IsTest() { + w.onFailure(lint.Failure{ + Confidence: 1, + Failure: "should not use dot imports", + Node: is, + Category: "imports", + }) + } + } + return nil +} diff --git a/vendor/github.com/mgechev/revive/rule/duplicated-imports.go b/vendor/github.com/mgechev/revive/rule/duplicated-imports.go new file mode 100644 index 0000000000..485b6a2ead --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/duplicated-imports.go @@ -0,0 +1,39 @@ +package rule + +import ( + "fmt" + + "github.com/mgechev/revive/lint" +) + +// DuplicatedImportsRule lints given else constructs. +type DuplicatedImportsRule struct{} + +// Apply applies the rule to given file. +func (r *DuplicatedImportsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + impPaths := map[string]struct{}{} + for _, imp := range file.AST.Imports { + path := imp.Path.Value + _, ok := impPaths[path] + if ok { + failures = append(failures, lint.Failure{ + Confidence: 1, + Failure: fmt.Sprintf("Package %s already imported", path), + Node: imp, + Category: "imports", + }) + continue + } + + impPaths[path] = struct{}{} + } + + return failures +} + +// Name returns the rule name. +func (r *DuplicatedImportsRule) Name() string { + return "duplicated-imports" +} diff --git a/vendor/github.com/mgechev/revive/rule/empty-block.go b/vendor/github.com/mgechev/revive/rule/empty-block.go new file mode 100644 index 0000000000..7861394b32 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/empty-block.go @@ -0,0 +1,76 @@ +package rule + +import ( + "go/ast" + + "github.com/mgechev/revive/lint" +) + +// EmptyBlockRule lints given else constructs. +type EmptyBlockRule struct{} + +// Apply applies the rule to given file. +func (r *EmptyBlockRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + w := lintEmptyBlock{make([]*ast.BlockStmt, 0), onFailure} + ast.Walk(w, file.AST) + return failures +} + +// Name returns the rule name. +func (r *EmptyBlockRule) Name() string { + return "empty-block" +} + +type lintEmptyBlock struct { + ignore []*ast.BlockStmt + onFailure func(lint.Failure) +} + +func (w lintEmptyBlock) Visit(node ast.Node) ast.Visitor { + fd, ok := node.(*ast.FuncDecl) + if ok { + w.ignore = append(w.ignore, fd.Body) + return w + } + + fl, ok := node.(*ast.FuncLit) + if ok { + w.ignore = append(w.ignore, fl.Body) + return w + } + + block, ok := node.(*ast.BlockStmt) + if !ok { + return w + } + + if mustIgnore(block, w.ignore) { + return w + } + + if len(block.List) == 0 { + w.onFailure(lint.Failure{ + Confidence: 1, + Node: block, + Category: "logic", + Failure: "this block is empty, you can remove it", + }) + } + + return w +} + +func mustIgnore(block *ast.BlockStmt, blackList []*ast.BlockStmt) bool { + for _, b := range blackList { + if b == block { + return true + } + } + return false +} diff --git a/vendor/github.com/mgechev/revive/rule/empty-lines.go b/vendor/github.com/mgechev/revive/rule/empty-lines.go new file mode 100644 index 0000000000..61d9281bfc --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/empty-lines.go @@ -0,0 +1,113 @@ +package rule + +import ( + "go/ast" + "go/token" + + "github.com/mgechev/revive/lint" +) + +// EmptyLinesRule lints empty lines in blocks. +type EmptyLinesRule struct{} + +// Apply applies the rule to given file. +func (r *EmptyLinesRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + w := lintEmptyLines{file, file.CommentMap(), onFailure} + ast.Walk(w, file.AST) + return failures +} + +// Name returns the rule name. +func (r *EmptyLinesRule) Name() string { + return "empty-lines" +} + +type lintEmptyLines struct { + file *lint.File + cmap ast.CommentMap + onFailure func(lint.Failure) +} + +func (w lintEmptyLines) Visit(node ast.Node) ast.Visitor { + block, ok := node.(*ast.BlockStmt) + if !ok { + return w + } + + w.checkStart(block) + w.checkEnd(block) + + return w +} + +func (w lintEmptyLines) checkStart(block *ast.BlockStmt) { + if len(block.List) == 0 { + return + } + + start := w.position(block.Lbrace) + firstNode := block.List[0] + + if w.commentBetween(start, firstNode) { + return + } + + first := w.position(firstNode.Pos()) + if first.Line-start.Line > 1 { + w.onFailure(lint.Failure{ + Confidence: 1, + Node: block, + Category: "style", + Failure: "extra empty line at the start of a block", + }) + } +} + +func (w lintEmptyLines) checkEnd(block *ast.BlockStmt) { + if len(block.List) < 1 { + return + } + + end := w.position(block.Rbrace) + lastNode := block.List[len(block.List)-1] + + if w.commentBetween(end, lastNode) { + return + } + + last := w.position(lastNode.End()) + if end.Line-last.Line > 1 { + w.onFailure(lint.Failure{ + Confidence: 1, + Node: lastNode, + Category: "style", + Failure: "extra empty line at the end of a block", + }) + } +} + +func (w lintEmptyLines) commentBetween(position token.Position, node ast.Node) bool { + comments := w.cmap.Filter(node).Comments() + if len(comments) == 0 { + return false + } + + for _, comment := range comments { + start, end := w.position(comment.Pos()), w.position(comment.End()) + if start.Line-position.Line == 1 || position.Line-end.Line == 1 { + return true + } + } + + return false +} + +func (w lintEmptyLines) position(pos token.Pos) token.Position { + return w.file.ToPosition(pos) +} diff --git a/vendor/github.com/mgechev/revive/rule/error-naming.go b/vendor/github.com/mgechev/revive/rule/error-naming.go new file mode 100644 index 0000000000..3a1080625e --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/error-naming.go @@ -0,0 +1,79 @@ +package rule + +import ( + "fmt" + "go/ast" + "go/token" + "strings" + + "github.com/mgechev/revive/lint" +) + +// ErrorNamingRule lints given else constructs. +type ErrorNamingRule struct{} + +// Apply applies the rule to given file. +func (r *ErrorNamingRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + fileAst := file.AST + walker := lintErrors{ + file: file, + fileAst: fileAst, + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + } + + ast.Walk(walker, fileAst) + + return failures +} + +// Name returns the rule name. +func (r *ErrorNamingRule) Name() string { + return "error-naming" +} + +type lintErrors struct { + file *lint.File + fileAst *ast.File + onFailure func(lint.Failure) +} + +func (w lintErrors) Visit(_ ast.Node) ast.Visitor { + for _, decl := range w.fileAst.Decls { + gd, ok := decl.(*ast.GenDecl) + if !ok || gd.Tok != token.VAR { + continue + } + for _, spec := range gd.Specs { + spec := spec.(*ast.ValueSpec) + if len(spec.Names) != 1 || len(spec.Values) != 1 { + continue + } + ce, ok := spec.Values[0].(*ast.CallExpr) + if !ok { + continue + } + if !isPkgDot(ce.Fun, "errors", "New") && !isPkgDot(ce.Fun, "fmt", "Errorf") { + continue + } + + id := spec.Names[0] + prefix := "err" + if id.IsExported() { + prefix = "Err" + } + if !strings.HasPrefix(id.Name, prefix) { + w.onFailure(lint.Failure{ + Node: id, + Confidence: 0.9, + Category: "naming", + Failure: fmt.Sprintf("error var %s should have name of the form %sFoo", id.Name, prefix), + }) + } + } + } + return nil +} diff --git a/vendor/github.com/mgechev/revive/rule/error-return.go b/vendor/github.com/mgechev/revive/rule/error-return.go new file mode 100644 index 0000000000..737d8c66f7 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/error-return.go @@ -0,0 +1,67 @@ +package rule + +import ( + "go/ast" + + "github.com/mgechev/revive/lint" +) + +// ErrorReturnRule lints given else constructs. +type ErrorReturnRule struct{} + +// Apply applies the rule to given file. +func (r *ErrorReturnRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + fileAst := file.AST + walker := lintErrorReturn{ + file: file, + fileAst: fileAst, + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + } + + ast.Walk(walker, fileAst) + + return failures +} + +// Name returns the rule name. +func (r *ErrorReturnRule) Name() string { + return "error-return" +} + +type lintErrorReturn struct { + file *lint.File + fileAst *ast.File + onFailure func(lint.Failure) +} + +func (w lintErrorReturn) Visit(n ast.Node) ast.Visitor { + fn, ok := n.(*ast.FuncDecl) + if !ok || fn.Type.Results == nil { + return w + } + ret := fn.Type.Results.List + if len(ret) <= 1 { + return w + } + if isIdent(ret[len(ret)-1].Type, "error") { + return nil + } + // An error return parameter should be the last parameter. + // Flag any error parameters found before the last. + for _, r := range ret[:len(ret)-1] { + if isIdent(r.Type, "error") { + w.onFailure(lint.Failure{ + Category: "arg-order", + Confidence: 0.9, + Node: fn, + Failure: "error should be the last type when returning multiple items", + }) + break // only flag one + } + } + return w +} diff --git a/vendor/github.com/mgechev/revive/rule/error-strings.go b/vendor/github.com/mgechev/revive/rule/error-strings.go new file mode 100644 index 0000000000..b8a5b7ed7a --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/error-strings.go @@ -0,0 +1,98 @@ +package rule + +import ( + "go/ast" + "go/token" + "strconv" + "unicode" + "unicode/utf8" + + "github.com/mgechev/revive/lint" +) + +// ErrorStringsRule lints given else constructs. +type ErrorStringsRule struct{} + +// Apply applies the rule to given file. +func (r *ErrorStringsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + fileAst := file.AST + walker := lintErrorStrings{ + file: file, + fileAst: fileAst, + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + } + + ast.Walk(walker, fileAst) + + return failures +} + +// Name returns the rule name. +func (r *ErrorStringsRule) Name() string { + return "error-strings" +} + +type lintErrorStrings struct { + file *lint.File + fileAst *ast.File + onFailure func(lint.Failure) +} + +func (w lintErrorStrings) Visit(n ast.Node) ast.Visitor { + ce, ok := n.(*ast.CallExpr) + if !ok { + return w + } + if !isPkgDot(ce.Fun, "errors", "New") && !isPkgDot(ce.Fun, "fmt", "Errorf") { + return w + } + if len(ce.Args) < 1 { + return w + } + str, ok := ce.Args[0].(*ast.BasicLit) + if !ok || str.Kind != token.STRING { + return w + } + s, _ := strconv.Unquote(str.Value) // can assume well-formed Go + if s == "" { + return w + } + clean, conf := lintErrorString(s) + if clean { + return w + } + + w.onFailure(lint.Failure{ + Node: str, + Confidence: conf, + Category: "errors", + Failure: "error strings should not be capitalized or end with punctuation or a newline", + }) + return w +} + +func lintErrorString(s string) (isClean bool, conf float64) { + const basicConfidence = 0.8 + const capConfidence = basicConfidence - 0.2 + first, firstN := utf8.DecodeRuneInString(s) + last, _ := utf8.DecodeLastRuneInString(s) + if last == '.' || last == ':' || last == '!' || last == '\n' { + return false, basicConfidence + } + if unicode.IsUpper(first) { + // People use proper nouns and exported Go identifiers in error strings, + // so decrease the confidence of warnings for capitalization. + if len(s) <= firstN { + return false, capConfidence + } + // Flag strings starting with something that doesn't look like an initialism. + if second, _ := utf8.DecodeRuneInString(s[firstN:]); !unicode.IsUpper(second) { + return false, capConfidence + } + } + return true, 0 +} diff --git a/vendor/github.com/mgechev/revive/rule/errorf.go b/vendor/github.com/mgechev/revive/rule/errorf.go new file mode 100644 index 0000000000..1bffbab5bc --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/errorf.go @@ -0,0 +1,93 @@ +package rule + +import ( + "fmt" + "go/ast" + "regexp" + "strings" + + "github.com/mgechev/revive/lint" +) + +// ErrorfRule lints given else constructs. +type ErrorfRule struct{} + +// Apply applies the rule to given file. +func (r *ErrorfRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + fileAst := file.AST + walker := lintErrorf{ + file: file, + fileAst: fileAst, + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + } + + file.Pkg.TypeCheck() + ast.Walk(walker, fileAst) + + return failures +} + +// Name returns the rule name. +func (r *ErrorfRule) Name() string { + return "errorf" +} + +type lintErrorf struct { + file *lint.File + fileAst *ast.File + onFailure func(lint.Failure) +} + +func (w lintErrorf) Visit(n ast.Node) ast.Visitor { + ce, ok := n.(*ast.CallExpr) + if !ok || len(ce.Args) != 1 { + return w + } + isErrorsNew := isPkgDot(ce.Fun, "errors", "New") + var isTestingError bool + se, ok := ce.Fun.(*ast.SelectorExpr) + if ok && se.Sel.Name == "Error" { + if typ := w.file.Pkg.TypeOf(se.X); typ != nil { + isTestingError = typ.String() == "*testing.T" + } + } + if !isErrorsNew && !isTestingError { + return w + } + arg := ce.Args[0] + ce, ok = arg.(*ast.CallExpr) + if !ok || !isPkgDot(ce.Fun, "fmt", "Sprintf") { + return w + } + errorfPrefix := "fmt" + if isTestingError { + errorfPrefix = w.file.Render(se.X) + } + + failure := lint.Failure{ + Category: "errors", + Node: n, + Confidence: 1, + Failure: fmt.Sprintf("should replace %s(fmt.Sprintf(...)) with %s.Errorf(...)", w.file.Render(se), errorfPrefix), + } + + m := srcLineWithMatch(w.file, ce, `^(.*)`+w.file.Render(se)+`\(fmt\.Sprintf\((.*)\)\)(.*)$`) + if m != nil { + failure.ReplacementLine = m[1] + errorfPrefix + ".Errorf(" + m[2] + ")" + m[3] + } + + w.onFailure(failure) + + return w +} + +func srcLineWithMatch(file *lint.File, node ast.Node, pattern string) (m []string) { + line := srcLine(file.Content(), file.ToPosition(node.Pos())) + line = strings.TrimSuffix(line, "\n") + rx := regexp.MustCompile(pattern) + return rx.FindStringSubmatch(line) +} diff --git a/vendor/github.com/mgechev/revive/rule/exported.go b/vendor/github.com/mgechev/revive/rule/exported.go new file mode 100644 index 0000000000..b68f2bacc1 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/exported.go @@ -0,0 +1,272 @@ +package rule + +import ( + "fmt" + "go/ast" + "go/token" + "strings" + "unicode" + "unicode/utf8" + + "github.com/mgechev/revive/lint" +) + +// ExportedRule lints given else constructs. +type ExportedRule struct{} + +// Apply applies the rule to given file. +func (r *ExportedRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + if isTest(file) { + return failures + } + + fileAst := file.AST + walker := lintExported{ + file: file, + fileAst: fileAst, + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + genDeclMissingComments: make(map[*ast.GenDecl]bool), + } + + ast.Walk(&walker, fileAst) + + return failures +} + +// Name returns the rule name. +func (r *ExportedRule) Name() string { + return "exported" +} + +type lintExported struct { + file *lint.File + fileAst *ast.File + lastGen *ast.GenDecl + genDeclMissingComments map[*ast.GenDecl]bool + onFailure func(lint.Failure) +} + +func (w *lintExported) lintFuncDoc(fn *ast.FuncDecl) { + if !ast.IsExported(fn.Name.Name) { + // func is unexported + return + } + kind := "function" + name := fn.Name.Name + if fn.Recv != nil && len(fn.Recv.List) > 0 { + // method + kind = "method" + recv := receiverType(fn) + if !ast.IsExported(recv) { + // receiver is unexported + return + } + if commonMethods[name] { + return + } + switch name { + case "Len", "Less", "Swap": + if w.file.Pkg.Sortable[recv] { + return + } + } + name = recv + "." + name + } + if fn.Doc == nil { + w.onFailure(lint.Failure{ + Node: fn, + Confidence: 1, + Category: "comments", + Failure: fmt.Sprintf("exported %s %s should have comment or be unexported", kind, name), + }) + return + } + s := normalizeText(fn.Doc.Text()) + prefix := fn.Name.Name + " " + if !strings.HasPrefix(s, prefix) { + w.onFailure(lint.Failure{ + Node: fn.Doc, + Confidence: 0.8, + Category: "comments", + Failure: fmt.Sprintf(`comment on exported %s %s should be of the form "%s..."`, kind, name, prefix), + }) + } +} + +func (w *lintExported) checkStutter(id *ast.Ident, thing string) { + pkg, name := w.fileAst.Name.Name, id.Name + if !ast.IsExported(name) { + // unexported name + return + } + // A name stutters if the package name is a strict prefix + // and the next character of the name starts a new word. + if len(name) <= len(pkg) { + // name is too short to stutter. + // This permits the name to be the same as the package name. + return + } + if !strings.EqualFold(pkg, name[:len(pkg)]) { + return + } + // We can assume the name is well-formed UTF-8. + // If the next rune after the package name is uppercase or an underscore + // the it's starting a new word and thus this name stutters. + rem := name[len(pkg):] + if next, _ := utf8.DecodeRuneInString(rem); next == '_' || unicode.IsUpper(next) { + w.onFailure(lint.Failure{ + Node: id, + Confidence: 0.8, + Category: "naming", + Failure: fmt.Sprintf("%s name will be used as %s.%s by other packages, and that stutters; consider calling this %s", thing, pkg, name, rem), + }) + } +} + +func (w *lintExported) lintTypeDoc(t *ast.TypeSpec, doc *ast.CommentGroup) { + if !ast.IsExported(t.Name.Name) { + return + } + if doc == nil { + w.onFailure(lint.Failure{ + Node: t, + Confidence: 1, + Category: "comments", + Failure: fmt.Sprintf("exported type %v should have comment or be unexported", t.Name), + }) + return + } + + s := normalizeText(doc.Text()) + articles := [...]string{"A", "An", "The", "This"} + for _, a := range articles { + if t.Name.Name == a { + continue + } + if strings.HasPrefix(s, a+" ") { + s = s[len(a)+1:] + break + } + } + if !strings.HasPrefix(s, t.Name.Name+" ") { + w.onFailure(lint.Failure{ + Node: doc, + Confidence: 1, + Category: "comments", + Failure: fmt.Sprintf(`comment on exported type %v should be of the form "%v ..." (with optional leading article)`, t.Name, t.Name), + }) + } +} + +func (w *lintExported) lintValueSpecDoc(vs *ast.ValueSpec, gd *ast.GenDecl, genDeclMissingComments map[*ast.GenDecl]bool) { + kind := "var" + if gd.Tok == token.CONST { + kind = "const" + } + + if len(vs.Names) > 1 { + // Check that none are exported except for the first. + for _, n := range vs.Names[1:] { + if ast.IsExported(n.Name) { + w.onFailure(lint.Failure{ + Category: "comments", + Confidence: 1, + Failure: fmt.Sprintf("exported %s %s should have its own declaration", kind, n.Name), + Node: vs, + }) + return + } + } + } + + // Only one name. + name := vs.Names[0].Name + if !ast.IsExported(name) { + return + } + + if vs.Doc == nil && gd.Doc == nil { + if genDeclMissingComments[gd] { + return + } + block := "" + if kind == "const" && gd.Lparen.IsValid() { + block = " (or a comment on this block)" + } + w.onFailure(lint.Failure{ + Confidence: 1, + Node: vs, + Category: "comments", + Failure: fmt.Sprintf("exported %s %s should have comment%s or be unexported", kind, name, block), + }) + genDeclMissingComments[gd] = true + return + } + // If this GenDecl has parens and a comment, we don't check its comment form. + if gd.Lparen.IsValid() && gd.Doc != nil { + return + } + // The relevant text to check will be on either vs.Doc or gd.Doc. + // Use vs.Doc preferentially. + doc := vs.Doc + if doc == nil { + doc = gd.Doc + } + prefix := name + " " + s := normalizeText(doc.Text()) + if !strings.HasPrefix(s, prefix) { + w.onFailure(lint.Failure{ + Confidence: 1, + Node: doc, + Category: "comments", + Failure: fmt.Sprintf(`comment on exported %s %s should be of the form "%s..."`, kind, name, prefix), + }) + } +} + +// normalizeText is a helper function that normalizes comment strings by: +// * removing one leading space +// +// This function is needed because ast.CommentGroup.Text() does not handle //-style and /*-style comments uniformly +func normalizeText(t string) string { + return strings.TrimPrefix(t, " ") +} + +func (w *lintExported) Visit(n ast.Node) ast.Visitor { + switch v := n.(type) { + case *ast.GenDecl: + if v.Tok == token.IMPORT { + return nil + } + // token.CONST, token.TYPE or token.VAR + w.lastGen = v + return w + case *ast.FuncDecl: + w.lintFuncDoc(v) + if v.Recv == nil { + // Only check for stutter on functions, not methods. + // Method names are not used package-qualified. + w.checkStutter(v.Name, "func") + } + // Don't proceed inside funcs. + return nil + case *ast.TypeSpec: + // inside a GenDecl, which usually has the doc + doc := v.Doc + if doc == nil { + doc = w.lastGen.Doc + } + w.lintTypeDoc(v, doc) + w.checkStutter(v.Name, "type") + // Don't proceed inside types. + return nil + case *ast.ValueSpec: + w.lintValueSpecDoc(v, w.lastGen, w.genDeclMissingComments) + return nil + } + return w +} diff --git a/vendor/github.com/mgechev/revive/rule/file-header.go b/vendor/github.com/mgechev/revive/rule/file-header.go new file mode 100644 index 0000000000..6df974e91a --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/file-header.go @@ -0,0 +1,69 @@ +package rule + +import ( + "regexp" + + "github.com/mgechev/revive/lint" +) + +// FileHeaderRule lints given else constructs. +type FileHeaderRule struct{} + +var ( + multiRegexp = regexp.MustCompile("^/\\*") + singleRegexp = regexp.MustCompile("^//") +) + +// Apply applies the rule to given file. +func (r *FileHeaderRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + if len(arguments) != 1 { + panic(`invalid configuration for "file-header" rule`) + } + + header, ok := arguments[0].(string) + if !ok { + panic(`invalid argument for "file-header" rule: first argument should be a string`) + } + + failure := []lint.Failure{ + { + Node: file.AST, + Confidence: 1, + Failure: "the file doesn't have an appropriate header", + }, + } + + if len(file.AST.Comments) == 0 { + return failure + } + + g := file.AST.Comments[0] + if g == nil { + return failure + } + comment := "" + for _, c := range g.List { + text := c.Text + if multiRegexp.Match([]byte(text)) { + text = text[2 : len(text)-2] + } else if singleRegexp.Match([]byte(text)) { + text = text[2:] + } + comment += text + } + + regex, err := regexp.Compile(header) + if err != nil { + panic(err.Error()) + } + + if !regex.Match([]byte(comment)) { + return failure + } + return nil +} + +// Name returns the rule name. +func (r *FileHeaderRule) Name() string { + return "file-header" +} diff --git a/vendor/github.com/mgechev/revive/rule/flag-param.go b/vendor/github.com/mgechev/revive/rule/flag-param.go new file mode 100644 index 0000000000..6cb6daea9b --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/flag-param.go @@ -0,0 +1,104 @@ +package rule + +import ( + "fmt" + "github.com/mgechev/revive/lint" + "go/ast" +) + +// FlagParamRule lints given else constructs. +type FlagParamRule struct{} + +// Apply applies the rule to given file. +func (r *FlagParamRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + w := lintFlagParamRule{onFailure: onFailure} + ast.Walk(w, file.AST) + return failures +} + +// Name returns the rule name. +func (r *FlagParamRule) Name() string { + return "flag-parameter" +} + +type lintFlagParamRule struct { + onFailure func(lint.Failure) +} + +func (w lintFlagParamRule) Visit(node ast.Node) ast.Visitor { + fd, ok := node.(*ast.FuncDecl) + if !ok { + return w + } + + if fd.Body == nil { + return nil // skip whole function declaration + } + + for _, p := range fd.Type.Params.List { + t := p.Type + + id, ok := t.(*ast.Ident) + if !ok { + continue + } + + if id.Name != "bool" { + continue + } + + cv := conditionVisitor{p.Names, fd, w} + ast.Walk(cv, fd.Body) + } + + return w +} + +type conditionVisitor struct { + ids []*ast.Ident + fd *ast.FuncDecl + linter lintFlagParamRule +} + +func (w conditionVisitor) Visit(node ast.Node) ast.Visitor { + ifStmt, ok := node.(*ast.IfStmt) + if !ok { + return w + } + + fselect := func(n ast.Node) bool { + ident, ok := n.(*ast.Ident) + if !ok { + return false + } + + for _, id := range w.ids { + if ident.Name == id.Name { + return true + } + } + + return false + } + + uses := pick(ifStmt.Cond, fselect, nil) + + if len(uses) < 1 { + return w + } + + w.linter.onFailure(lint.Failure{ + Confidence: 1, + Node: w.fd.Type.Params, + Category: "bad practice", + Failure: fmt.Sprintf("parameter '%s' seems to be a control flag, avoid control coupling", uses[0]), + }) + + return nil +} diff --git a/vendor/github.com/mgechev/revive/rule/function-result-limit.go b/vendor/github.com/mgechev/revive/rule/function-result-limit.go new file mode 100644 index 0000000000..1850fc4194 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/function-result-limit.go @@ -0,0 +1,68 @@ +package rule + +import ( + "fmt" + "go/ast" + + "github.com/mgechev/revive/lint" +) + +// FunctionResultsLimitRule lints given else constructs. +type FunctionResultsLimitRule struct{} + +// Apply applies the rule to given file. +func (r *FunctionResultsLimitRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + if len(arguments) != 1 { + panic(`invalid configuration for "function-result-limit"`) + } + + max, ok := arguments[0].(int64) // Alt. non panicking version + if !ok { + panic(fmt.Sprintf(`invalid value passed as return results number to the "function-result-limit" rule; need int64 but got %T`, arguments[0])) + } + if max < 0 { + panic(`the value passed as return results number to the "function-result-limit" rule cannot be negative`) + } + + var failures []lint.Failure + + walker := lintFunctionResultsNum{ + max: int(max), + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + } + + ast.Walk(walker, file.AST) + + return failures +} + +// Name returns the rule name. +func (r *FunctionResultsLimitRule) Name() string { + return "function-result-limit" +} + +type lintFunctionResultsNum struct { + max int + onFailure func(lint.Failure) +} + +func (w lintFunctionResultsNum) Visit(n ast.Node) ast.Visitor { + node, ok := n.(*ast.FuncDecl) + if ok { + num := 0 + if node.Type.Results != nil { + num = node.Type.Results.NumFields() + } + if num > w.max { + w.onFailure(lint.Failure{ + Confidence: 1, + Failure: fmt.Sprintf("maximum number of return results per function exceeded; max %d but got %d", w.max, num), + Node: node.Type, + }) + return w + } + } + return w +} diff --git a/vendor/github.com/mgechev/revive/rule/get-return.go b/vendor/github.com/mgechev/revive/rule/get-return.go new file mode 100644 index 0000000000..494ab6669d --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/get-return.go @@ -0,0 +1,70 @@ +package rule + +import ( + "fmt" + "go/ast" + "strings" + + "github.com/mgechev/revive/lint" +) + +// GetReturnRule lints given else constructs. +type GetReturnRule struct{} + +// Apply applies the rule to given file. +func (r *GetReturnRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + w := lintReturnRule{onFailure} + ast.Walk(w, file.AST) + return failures +} + +// Name returns the rule name. +func (r *GetReturnRule) Name() string { + return "get-return" +} + +type lintReturnRule struct { + onFailure func(lint.Failure) +} + +func isGetter(name string) bool { + if strings.HasPrefix(strings.ToUpper(name), "GET") { + if len(name) > 3 { + c := name[3] + return !(c >= 'a' && c <= 'z') + } + } + + return false +} + +func hasResults(rs *ast.FieldList) bool { + return rs != nil && len(rs.List) > 0 +} + +func (w lintReturnRule) Visit(node ast.Node) ast.Visitor { + fd, ok := node.(*ast.FuncDecl) + if !ok { + return w + } + + if !isGetter(fd.Name.Name) { + return w + } + if !hasResults(fd.Type.Results) { + w.onFailure(lint.Failure{ + Confidence: 0.8, + Node: fd, + Category: "logic", + Failure: fmt.Sprintf("function '%s' seems to be a getter but it does not return any result", fd.Name.Name), + }) + } + + return w +} diff --git a/vendor/github.com/mgechev/revive/rule/if-return.go b/vendor/github.com/mgechev/revive/rule/if-return.go new file mode 100644 index 0000000000..c275d27662 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/if-return.go @@ -0,0 +1,115 @@ +package rule + +import ( + "go/ast" + "go/token" + "strings" + + "github.com/mgechev/revive/lint" +) + +// IfReturnRule lints given else constructs. +type IfReturnRule struct{} + +// Apply applies the rule to given file. +func (r *IfReturnRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + astFile := file.AST + w := &lintElseError{astFile, onFailure} + ast.Walk(w, astFile) + return failures +} + +// Name returns the rule name. +func (r *IfReturnRule) Name() string { + return "if-return" +} + +type lintElseError struct { + file *ast.File + onFailure func(lint.Failure) +} + +func (w *lintElseError) Visit(node ast.Node) ast.Visitor { + switch v := node.(type) { + case *ast.BlockStmt: + for i := 0; i < len(v.List)-1; i++ { + // if var := whatever; var != nil { return var } + s, ok := v.List[i].(*ast.IfStmt) + if !ok || s.Body == nil || len(s.Body.List) != 1 || s.Else != nil { + continue + } + assign, ok := s.Init.(*ast.AssignStmt) + if !ok || len(assign.Lhs) != 1 || !(assign.Tok == token.DEFINE || assign.Tok == token.ASSIGN) { + continue + } + id, ok := assign.Lhs[0].(*ast.Ident) + if !ok { + continue + } + expr, ok := s.Cond.(*ast.BinaryExpr) + if !ok || expr.Op != token.NEQ { + continue + } + if lhs, ok := expr.X.(*ast.Ident); !ok || lhs.Name != id.Name { + continue + } + if rhs, ok := expr.Y.(*ast.Ident); !ok || rhs.Name != "nil" { + continue + } + r, ok := s.Body.List[0].(*ast.ReturnStmt) + if !ok || len(r.Results) != 1 { + continue + } + if r, ok := r.Results[0].(*ast.Ident); !ok || r.Name != id.Name { + continue + } + + // return nil + r, ok = v.List[i+1].(*ast.ReturnStmt) + if !ok || len(r.Results) != 1 { + continue + } + if r, ok := r.Results[0].(*ast.Ident); !ok || r.Name != "nil" { + continue + } + + // check if there are any comments explaining the construct, don't emit an error if there are some. + if containsComments(s.Pos(), r.Pos(), w.file) { + continue + } + + w.onFailure(lint.Failure{ + Confidence: .9, + Node: v.List[i], + Failure: "redundant if ...; err != nil check, just return error instead.", + }) + } + } + return w +} + +func containsComments(start, end token.Pos, f *ast.File) bool { + for _, cgroup := range f.Comments { + comments := cgroup.List + if comments[0].Slash >= end { + // All comments starting with this group are after end pos. + return false + } + if comments[len(comments)-1].Slash < start { + // Comments group ends before start pos. + continue + } + for _, c := range comments { + if start <= c.Slash && c.Slash < end && !strings.HasPrefix(c.Text, "// MATCH ") { + return true + } + } + } + return false +} diff --git a/vendor/github.com/mgechev/revive/rule/import-shadowing.go b/vendor/github.com/mgechev/revive/rule/import-shadowing.go new file mode 100644 index 0000000000..b78234c592 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/import-shadowing.go @@ -0,0 +1,102 @@ +package rule + +import ( + "fmt" + "go/ast" + "go/token" + "strings" + + "github.com/mgechev/revive/lint" +) + +// ImportShadowingRule lints given else constructs. +type ImportShadowingRule struct{} + +// Apply applies the rule to given file. +func (r *ImportShadowingRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + importNames := map[string]struct{}{} + for _, imp := range file.AST.Imports { + importNames[getName(imp)] = struct{}{} + } + + fileAst := file.AST + walker := importShadowing{ + importNames: importNames, + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + alreadySeen: map[*ast.Object]struct{}{}, + } + + ast.Walk(walker, fileAst) + + return failures +} + +// Name returns the rule name. +func (r *ImportShadowingRule) Name() string { + return "import-shadowing" +} + +func getName(imp *ast.ImportSpec) string { + const pathSep = "/" + const strDelim = `"` + if imp.Name != nil { + return imp.Name.Name + } + + path := imp.Path.Value + i := strings.LastIndex(path, pathSep) + if i == -1 { + return strings.Trim(path, strDelim) + } + + return strings.Trim(path[i+1:], strDelim) +} + +type importShadowing struct { + importNames map[string]struct{} + onFailure func(lint.Failure) + alreadySeen map[*ast.Object]struct{} +} + +// Visit visits AST nodes and checks if id nodes (ast.Ident) shadow an import name +func (w importShadowing) Visit(n ast.Node) ast.Visitor { + switch n := n.(type) { + case *ast.AssignStmt: + if n.Tok == token.DEFINE { + return w // analyze variable declarations of the form id := expr + } + + return nil // skip assigns of the form id = expr (not an id declaration) + case *ast.CallExpr, // skip call expressions (not an id declaration) + *ast.ImportSpec, // skip import section subtree because we already have the list of imports + *ast.KeyValueExpr, // skip analysis of key-val expressions ({key:value}): ids of such expressions, even the same of an import name, do not shadow the import name + *ast.ReturnStmt, // skip skipping analysis of returns, ids in expression were already analyzed + *ast.SelectorExpr, // skip analysis of selector expressions (anId.otherId): because if anId shadows an import name, it was already detected, and otherId does not shadows the import name + *ast.StructType: // skip analysis of struct type because struct fields can not shadow an import name + return nil + case *ast.Ident: + id := n.Name + if id == "_" { + return w // skip _ id + } + + _, isImportName := w.importNames[id] + _, alreadySeen := w.alreadySeen[n.Obj] + if isImportName && !alreadySeen { + w.onFailure(lint.Failure{ + Confidence: 1, + Node: n, + Category: "namming", + Failure: fmt.Sprintf("The name '%s' shadows an import name", id), + }) + + w.alreadySeen[n.Obj] = struct{}{} + } + } + + return w +} diff --git a/vendor/github.com/mgechev/revive/rule/imports-blacklist.go b/vendor/github.com/mgechev/revive/rule/imports-blacklist.go new file mode 100644 index 0000000000..31ef901e55 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/imports-blacklist.go @@ -0,0 +1,52 @@ +package rule + +import ( + "fmt" + + "github.com/mgechev/revive/lint" +) + +// ImportsBlacklistRule lints given else constructs. +type ImportsBlacklistRule struct{} + +// Apply applies the rule to given file. +func (r *ImportsBlacklistRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + var failures []lint.Failure + + if file.IsTest() { + return failures // skip, test file + } + + blacklist := make(map[string]bool, len(arguments)) + + for _, arg := range arguments { + argStr, ok := arg.(string) + if !ok { + panic(fmt.Sprintf("Invalid argument to the imports-blacklist rule. Expecting a string, got %T", arg)) + } + // we add quotes if not present, because when parsed, the value of the AST node, will be quoted + if len(argStr) > 2 && argStr[0] != '"' && argStr[len(argStr)-1] != '"' { + argStr = fmt.Sprintf(`"%s"`, argStr) + } + blacklist[argStr] = true + } + + for _, is := range file.AST.Imports { + path := is.Path + if path != nil && blacklist[path.Value] { + failures = append(failures, lint.Failure{ + Confidence: 1, + Failure: "should not use the following blacklisted import: " + path.Value, + Node: is, + Category: "imports", + }) + } + } + + return failures +} + +// Name returns the rule name. +func (r *ImportsBlacklistRule) Name() string { + return "imports-blacklist" +} diff --git a/vendor/github.com/mgechev/revive/rule/increment-decrement.go b/vendor/github.com/mgechev/revive/rule/increment-decrement.go new file mode 100644 index 0000000000..5d6b176719 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/increment-decrement.go @@ -0,0 +1,74 @@ +package rule + +import ( + "fmt" + "go/ast" + "go/token" + + "github.com/mgechev/revive/lint" +) + +// IncrementDecrementRule lints given else constructs. +type IncrementDecrementRule struct{} + +// Apply applies the rule to given file. +func (r *IncrementDecrementRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + fileAst := file.AST + walker := lintIncrementDecrement{ + file: file, + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + } + + ast.Walk(walker, fileAst) + + return failures +} + +// Name returns the rule name. +func (r *IncrementDecrementRule) Name() string { + return "increment-decrement" +} + +type lintIncrementDecrement struct { + file *lint.File + fileAst *ast.File + onFailure func(lint.Failure) +} + +func (w lintIncrementDecrement) Visit(n ast.Node) ast.Visitor { + as, ok := n.(*ast.AssignStmt) + if !ok { + return w + } + if len(as.Lhs) != 1 { + return w + } + if !isOne(as.Rhs[0]) { + return w + } + var suffix string + switch as.Tok { + case token.ADD_ASSIGN: + suffix = "++" + case token.SUB_ASSIGN: + suffix = "--" + default: + return w + } + w.onFailure(lint.Failure{ + Confidence: 0.8, + Node: as, + Category: "unary-op", + Failure: fmt.Sprintf("should replace %s with %s%s", w.file.Render(as), w.file.Render(as.Lhs[0]), suffix), + }) + return w +} + +func isOne(expr ast.Expr) bool { + lit, ok := expr.(*ast.BasicLit) + return ok && lit.Kind == token.INT && lit.Value == "1" +} diff --git a/vendor/github.com/mgechev/revive/rule/indent-error-flow.go b/vendor/github.com/mgechev/revive/rule/indent-error-flow.go new file mode 100644 index 0000000000..4c9799b2a2 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/indent-error-flow.go @@ -0,0 +1,78 @@ +package rule + +import ( + "go/ast" + "go/token" + + "github.com/mgechev/revive/lint" +) + +// IndentErrorFlowRule lints given else constructs. +type IndentErrorFlowRule struct{} + +// Apply applies the rule to given file. +func (r *IndentErrorFlowRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + w := lintElse{make(map[*ast.IfStmt]bool), onFailure} + ast.Walk(w, file.AST) + return failures +} + +// Name returns the rule name. +func (r *IndentErrorFlowRule) Name() string { + return "indent-error-flow" +} + +type lintElse struct { + ignore map[*ast.IfStmt]bool + onFailure func(lint.Failure) +} + +func (w lintElse) Visit(node ast.Node) ast.Visitor { + ifStmt, ok := node.(*ast.IfStmt) + if !ok || ifStmt.Else == nil { + return w + } + if w.ignore[ifStmt] { + if elseif, ok := ifStmt.Else.(*ast.IfStmt); ok { + w.ignore[elseif] = true + } + return w + } + if elseif, ok := ifStmt.Else.(*ast.IfStmt); ok { + w.ignore[elseif] = true + return w + } + if _, ok := ifStmt.Else.(*ast.BlockStmt); !ok { + // only care about elses without conditions + return w + } + if len(ifStmt.Body.List) == 0 { + return w + } + shortDecl := false // does the if statement have a ":=" initialization statement? + if ifStmt.Init != nil { + if as, ok := ifStmt.Init.(*ast.AssignStmt); ok && as.Tok == token.DEFINE { + shortDecl = true + } + } + lastStmt := ifStmt.Body.List[len(ifStmt.Body.List)-1] + if _, ok := lastStmt.(*ast.ReturnStmt); ok { + extra := "" + if shortDecl { + extra = " (move short variable declaration to its own line if necessary)" + } + w.onFailure(lint.Failure{ + Confidence: 1, + Node: ifStmt.Else, + Category: "indent", + Failure: "if block ends with a return statement, so drop this else and outdent its block" + extra, + }) + } + return w +} diff --git a/vendor/github.com/mgechev/revive/rule/line-length-limit.go b/vendor/github.com/mgechev/revive/rule/line-length-limit.go new file mode 100644 index 0000000000..5ee057079f --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/line-length-limit.go @@ -0,0 +1,84 @@ +package rule + +import ( + "bufio" + "bytes" + "fmt" + "go/token" + "strings" + "unicode/utf8" + + "github.com/mgechev/revive/lint" +) + +// LineLengthLimitRule lints given else constructs. +type LineLengthLimitRule struct{} + +// Apply applies the rule to given file. +func (r *LineLengthLimitRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + if len(arguments) != 1 { + panic(`invalid configuration for "line-length-limit"`) + } + + max, ok := arguments[0].(int64) // Alt. non panicking version + if !ok || max < 0 { + panic(`invalid value passed as argument number to the "line-length-limit" rule`) + } + + var failures []lint.Failure + checker := lintLineLengthNum{ + max: int(max), + file: file, + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + } + + checker.check() + + return failures +} + +// Name returns the rule name. +func (r *LineLengthLimitRule) Name() string { + return "line-length-limit" +} + +type lintLineLengthNum struct { + max int + file *lint.File + onFailure func(lint.Failure) +} + +func (r lintLineLengthNum) check() { + f := bytes.NewReader(r.file.Content()) + spaces := strings.Repeat(" ", 4) // tab width = 4 + l := 1 + s := bufio.NewScanner(f) + for s.Scan() { + t := s.Text() + t = strings.Replace(t, "\t", spaces, -1) + c := utf8.RuneCountInString(t) + if c > r.max { + r.onFailure(lint.Failure{ + Category: "code-style", + Position: lint.FailurePosition{ + // Offset not set; it is non-trivial, and doesn't appear to be needed. + Start: token.Position{ + Filename: r.file.Name, + Line: l, + Column: 0, + }, + End: token.Position{ + Filename: r.file.Name, + Line: l, + Column: c, + }, + }, + Confidence: 1, + Failure: fmt.Sprintf("line is %d characters, out of limit %d", c, r.max), + }) + } + l++ + } +} diff --git a/vendor/github.com/mgechev/revive/rule/max-public-structs.go b/vendor/github.com/mgechev/revive/rule/max-public-structs.go new file mode 100644 index 0000000000..9a2d07cbc1 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/max-public-structs.go @@ -0,0 +1,67 @@ +package rule + +import ( + "go/ast" + + "strings" + + "github.com/mgechev/revive/lint" +) + +// MaxPublicStructsRule lints given else constructs. +type MaxPublicStructsRule struct{} + +// Apply applies the rule to given file. +func (r *MaxPublicStructsRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + var failures []lint.Failure + + fileAst := file.AST + walker := &lintMaxPublicStructs{ + fileAst: fileAst, + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + } + + ast.Walk(walker, fileAst) + + max, ok := arguments[0].(int64) // Alt. non panicking version + if !ok { + panic(`invalid value passed as argument number to the "max-public-structs" rule`) + } + + if walker.current > max { + walker.onFailure(lint.Failure{ + Failure: "you have exceeded the maximum number of public struct declarations", + Confidence: 1, + Node: fileAst, + Category: "style", + }) + } + + return failures +} + +// Name returns the rule name. +func (r *MaxPublicStructsRule) Name() string { + return "max-public-structs" +} + +type lintMaxPublicStructs struct { + current int64 + fileAst *ast.File + onFailure func(lint.Failure) +} + +func (w *lintMaxPublicStructs) Visit(n ast.Node) ast.Visitor { + switch v := n.(type) { + case *ast.TypeSpec: + name := v.Name.Name + first := string(name[0]) + if strings.ToUpper(first) == first { + w.current++ + } + break + } + return w +} diff --git a/vendor/github.com/mgechev/revive/rule/modifies-param.go b/vendor/github.com/mgechev/revive/rule/modifies-param.go new file mode 100644 index 0000000000..55136e6c82 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/modifies-param.go @@ -0,0 +1,80 @@ +package rule + +import ( + "fmt" + "go/ast" + + "github.com/mgechev/revive/lint" +) + +// ModifiesParamRule lints given else constructs. +type ModifiesParamRule struct{} + +// Apply applies the rule to given file. +func (r *ModifiesParamRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + w := lintModifiesParamRule{onFailure: onFailure} + ast.Walk(w, file.AST) + return failures +} + +// Name returns the rule name. +func (r *ModifiesParamRule) Name() string { + return "modifies-parameter" +} + +type lintModifiesParamRule struct { + params map[string]bool + onFailure func(lint.Failure) +} + +func retrieveParamNames(pl []*ast.Field) map[string]bool { + result := make(map[string]bool, len(pl)) + for _, p := range pl { + for _, n := range p.Names { + if n.Name == "_" { + continue + } + + result[n.Name] = true + } + } + return result +} + +func (w lintModifiesParamRule) Visit(node ast.Node) ast.Visitor { + switch v := node.(type) { + case *ast.FuncDecl: + w.params = retrieveParamNames(v.Type.Params.List) + case *ast.IncDecStmt: + if id, ok := v.X.(*ast.Ident); ok { + checkParam(id, &w) + } + case *ast.AssignStmt: + lhs := v.Lhs + for _, e := range lhs { + id, ok := e.(*ast.Ident) + if ok { + checkParam(id, &w) + } + } + } + + return w +} + +func checkParam(id *ast.Ident, w *lintModifiesParamRule) { + if w.params[id.Name] { + w.onFailure(lint.Failure{ + Confidence: 0.5, // confidence is low because of shadow variables + Node: id, + Category: "bad practice", + Failure: fmt.Sprintf("parameter '%s' seems to be modified", id), + }) + } +} diff --git a/vendor/github.com/mgechev/revive/rule/modifies-value-receiver.go b/vendor/github.com/mgechev/revive/rule/modifies-value-receiver.go new file mode 100644 index 0000000000..4fe22ddf3f --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/modifies-value-receiver.go @@ -0,0 +1,134 @@ +package rule + +import ( + "go/ast" + "strings" + + "github.com/mgechev/revive/lint" +) + +// ModifiesValRecRule lints assignments to value method-receivers. +type ModifiesValRecRule struct{} + +// Apply applies the rule to given file. +func (r *ModifiesValRecRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + w := lintModifiesValRecRule{file: file, onFailure: onFailure} + file.Pkg.TypeCheck() + ast.Walk(w, file.AST) + + return failures +} + +// Name returns the rule name. +func (r *ModifiesValRecRule) Name() string { + return "modifies-value-receiver" +} + +type lintModifiesValRecRule struct { + file *lint.File + onFailure func(lint.Failure) +} + +func (w lintModifiesValRecRule) Visit(node ast.Node) ast.Visitor { + switch n := node.(type) { + case *ast.FuncDecl: + if n.Recv == nil { + return nil // skip, not a method + } + + receiver := n.Recv.List[0] + if _, ok := receiver.Type.(*ast.StarExpr); ok { + return nil // skip, method with pointer receiver + } + + if w.skipType(receiver.Type) { + return nil // skip, receiver is a map or array + } + + if len(receiver.Names) < 1 { + return nil // skip, anonymous receiver + } + + receiverName := receiver.Names[0].Name + if receiverName == "_" { + return nil // skip, anonymous receiver + } + + fselect := func(n ast.Node) bool { + // look for assignments with the receiver in the right hand + asgmt, ok := n.(*ast.AssignStmt) + if !ok { + return false + } + + for _, exp := range asgmt.Lhs { + switch e := exp.(type) { + case *ast.IndexExpr: // receiver...[] = ... + continue + case *ast.StarExpr: // *receiver = ... + continue + case *ast.SelectorExpr: // receiver.field = ... + name := w.getNameFromExpr(e.X) + if name == "" || name != receiverName { + continue + } + + if w.skipType(ast.Expr(e.Sel)) { + continue + } + + case *ast.Ident: // receiver := ... + if e.Name != receiverName { + continue + } + default: + continue + } + + return true + } + + return false + } + + assignmentsToReceiver := pick(n.Body, fselect, nil) + + for _, assignment := range assignmentsToReceiver { + w.onFailure(lint.Failure{ + Node: assignment, + Confidence: 1, + Failure: "suspicious assignment to a by-value method receiver", + }) + } + } + + return w +} + +func (w lintModifiesValRecRule) skipType(t ast.Expr) bool { + rt := w.file.Pkg.TypeOf(t) + if rt == nil { + return false + } + + rt = rt.Underlying() + rtName := rt.String() + + // skip when receiver is a map or array + return strings.HasPrefix(rtName, "[]") || strings.HasPrefix(rtName, "map[") +} + +func (lintModifiesValRecRule) getNameFromExpr(ie ast.Expr) string { + ident, ok := ie.(*ast.Ident) + if !ok { + return "" + } + + return ident.Name +} diff --git a/vendor/github.com/mgechev/revive/rule/package-comments.go b/vendor/github.com/mgechev/revive/rule/package-comments.go new file mode 100644 index 0000000000..00fc5bb915 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/package-comments.go @@ -0,0 +1,121 @@ +package rule + +import ( + "fmt" + "go/ast" + "go/token" + "strings" + + "github.com/mgechev/revive/lint" +) + +// PackageCommentsRule lints the package comments. It complains if +// there is no package comment, or if it is not of the right form. +// This has a notable false positive in that a package comment +// could rightfully appear in a different file of the same package, +// but that's not easy to fix since this linter is file-oriented. +type PackageCommentsRule struct{} + +// Apply applies the rule to given file. +func (r *PackageCommentsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + if isTest(file) { + return failures + } + + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + fileAst := file.AST + w := &lintPackageComments{fileAst, file, onFailure} + ast.Walk(w, fileAst) + return failures +} + +// Name returns the rule name. +func (r *PackageCommentsRule) Name() string { + return "package-comments" +} + +type lintPackageComments struct { + fileAst *ast.File + file *lint.File + onFailure func(lint.Failure) +} + +func (l *lintPackageComments) Visit(_ ast.Node) ast.Visitor { + if l.file.IsTest() { + return nil + } + + const ref = styleGuideBase + "#package-comments" + prefix := "Package " + l.fileAst.Name.Name + " " + + // Look for a detached package comment. + // First, scan for the last comment that occurs before the "package" keyword. + var lastCG *ast.CommentGroup + for _, cg := range l.fileAst.Comments { + if cg.Pos() > l.fileAst.Package { + // Gone past "package" keyword. + break + } + lastCG = cg + } + if lastCG != nil && strings.HasPrefix(lastCG.Text(), prefix) { + endPos := l.file.ToPosition(lastCG.End()) + pkgPos := l.file.ToPosition(l.fileAst.Package) + if endPos.Line+1 < pkgPos.Line { + // There isn't a great place to anchor this error; + // the start of the blank lines between the doc and the package statement + // is at least pointing at the location of the problem. + pos := token.Position{ + Filename: endPos.Filename, + // Offset not set; it is non-trivial, and doesn't appear to be needed. + Line: endPos.Line + 1, + Column: 1, + } + l.onFailure(lint.Failure{ + Category: "comments", + Position: lint.FailurePosition{ + Start: pos, + End: pos, + }, + Confidence: 0.9, + Failure: "package comment is detached; there should be no blank lines between it and the package statement", + }) + return nil + } + } + + if l.fileAst.Doc == nil { + l.onFailure(lint.Failure{ + Category: "comments", + Node: l.fileAst, + Confidence: 0.2, + Failure: "should have a package comment, unless it's in another file for this package", + }) + return nil + } + s := l.fileAst.Doc.Text() + if ts := strings.TrimLeft(s, " \t"); ts != s { + l.onFailure(lint.Failure{ + Category: "comments", + Node: l.fileAst.Doc, + Confidence: 1, + Failure: "package comment should not have leading space", + }) + s = ts + } + // Only non-main packages need to keep to this form. + if !l.file.Pkg.IsMain() && !strings.HasPrefix(s, prefix) { + l.onFailure(lint.Failure{ + Category: "comments", + Node: l.fileAst.Doc, + Confidence: 1, + Failure: fmt.Sprintf(`package comment should be of the form "%s..."`, prefix), + }) + } + return nil +} diff --git a/vendor/github.com/mgechev/revive/rule/range-val-address.go b/vendor/github.com/mgechev/revive/rule/range-val-address.go new file mode 100644 index 0000000000..18554825a8 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/range-val-address.go @@ -0,0 +1,113 @@ +package rule + +import ( + "fmt" + "go/ast" + "go/token" + + "github.com/mgechev/revive/lint" +) + +// RangeValAddress lints +type RangeValAddress struct{} + +// Apply applies the rule to given file. +func (r *RangeValAddress) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + walker := rangeValAddress{ + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + } + + ast.Walk(walker, file.AST) + + return failures +} + +// Name returns the rule name. +func (r *RangeValAddress) Name() string { + return "range-val-address" +} + +type rangeValAddress struct { + onFailure func(lint.Failure) +} + +func (w rangeValAddress) Visit(node ast.Node) ast.Visitor { + n, ok := node.(*ast.RangeStmt) + if !ok { + return w + } + + value, ok := n.Value.(*ast.Ident) + if !ok { + return w + } + + ast.Walk(rangeBodyVisitor{ + valueID: value.Obj, + onFailure: w.onFailure, + }, n.Body) + + return w +} + +type rangeBodyVisitor struct { + valueID *ast.Object + onFailure func(lint.Failure) +} + +func (bw rangeBodyVisitor) Visit(node ast.Node) ast.Visitor { + asgmt, ok := node.(*ast.AssignStmt) + if !ok { + return bw + } + + for _, exp := range asgmt.Lhs { + e, ok := exp.(*ast.IndexExpr) + if !ok { + continue + } + if bw.isAccessingRangeValueAddress(e.Index) { // e.g. a[&value]... + bw.onFailure(bw.newFailure(e.Index)) + } + } + + for _, exp := range asgmt.Rhs { + switch e := exp.(type) { + case *ast.UnaryExpr: // e.g. ...&value + if bw.isAccessingRangeValueAddress(e) { + bw.onFailure(bw.newFailure(e)) + } + case *ast.CallExpr: + if fun, ok := e.Fun.(*ast.Ident); ok && fun.Name == "append" { // e.g. ...append(arr, &value) + for _, v := range e.Args { + if bw.isAccessingRangeValueAddress(v) { + bw.onFailure(bw.newFailure(e)) + } + } + } + } + } + return bw +} + +func (bw rangeBodyVisitor) isAccessingRangeValueAddress(exp ast.Expr) bool { + u, ok := exp.(*ast.UnaryExpr) + if !ok { + return false + } + + v, ok := u.X.(*ast.Ident) + return ok && u.Op == token.AND && v.Obj == bw.valueID +} + +func (bw rangeBodyVisitor) newFailure(node ast.Node) lint.Failure { + return lint.Failure{ + Node: node, + Confidence: 1, + Failure: fmt.Sprintf("suspicious assignment of '%s'. range-loop variables always have the same address", bw.valueID.Name), + } +} diff --git a/vendor/github.com/mgechev/revive/rule/range-val-in-closure.go b/vendor/github.com/mgechev/revive/rule/range-val-in-closure.go new file mode 100644 index 0000000000..857787be38 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/range-val-in-closure.go @@ -0,0 +1,111 @@ +package rule + +import ( + "fmt" + "go/ast" + + "github.com/mgechev/revive/lint" +) + +// RangeValInClosureRule lints given else constructs. +type RangeValInClosureRule struct{} + +// Apply applies the rule to given file. +func (r *RangeValInClosureRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + walker := rangeValInClosure{ + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + } + + ast.Walk(walker, file.AST) + + return failures +} + +// Name returns the rule name. +func (r *RangeValInClosureRule) Name() string { + return "range-val-in-closure" +} + +type rangeValInClosure struct { + onFailure func(lint.Failure) +} + +func (w rangeValInClosure) Visit(node ast.Node) ast.Visitor { + + // Find the variables updated by the loop statement. + var vars []*ast.Ident + addVar := func(expr ast.Expr) { + if id, ok := expr.(*ast.Ident); ok { + vars = append(vars, id) + } + } + var body *ast.BlockStmt + switch n := node.(type) { + case *ast.RangeStmt: + body = n.Body + addVar(n.Key) + addVar(n.Value) + case *ast.ForStmt: + body = n.Body + switch post := n.Post.(type) { + case *ast.AssignStmt: + // e.g. for p = head; p != nil; p = p.next + for _, lhs := range post.Lhs { + addVar(lhs) + } + case *ast.IncDecStmt: + // e.g. for i := 0; i < n; i++ + addVar(post.X) + } + } + if vars == nil { + return w + } + + // Inspect a go or defer statement + // if it's the last one in the loop body. + // (We give up if there are following statements, + // because it's hard to prove go isn't followed by wait, + // or defer by return.) + if len(body.List) == 0 { + return w + } + var last *ast.CallExpr + switch s := body.List[len(body.List)-1].(type) { + case *ast.GoStmt: + last = s.Call + case *ast.DeferStmt: + last = s.Call + default: + return w + } + lit, ok := last.Fun.(*ast.FuncLit) + if !ok { + return w + } + if lit.Type == nil { + // Not referring to a variable (e.g. struct field name) + return w + } + ast.Inspect(lit.Body, func(n ast.Node) bool { + id, ok := n.(*ast.Ident) + if !ok || id.Obj == nil { + return true + } + for _, v := range vars { + if v.Obj == id.Obj { + w.onFailure(lint.Failure{ + Confidence: 1, + Failure: fmt.Sprintf("loop variable %v captured by func literal", id.Name), + Node: n, + }) + } + } + return true + }) + return w +} diff --git a/vendor/github.com/mgechev/revive/rule/range.go b/vendor/github.com/mgechev/revive/rule/range.go new file mode 100644 index 0000000000..d18492c71a --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/range.go @@ -0,0 +1,82 @@ +package rule + +import ( + "fmt" + "go/ast" + "strings" + + "github.com/mgechev/revive/lint" +) + +// RangeRule lints given else constructs. +type RangeRule struct{} + +// Apply applies the rule to given file. +func (r *RangeRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + w := &lintRanges{file, onFailure} + ast.Walk(w, file.AST) + return failures +} + +// Name returns the rule name. +func (r *RangeRule) Name() string { + return "range" +} + +type lintRanges struct { + file *lint.File + onFailure func(lint.Failure) +} + +func (w *lintRanges) Visit(node ast.Node) ast.Visitor { + rs, ok := node.(*ast.RangeStmt) + if !ok { + return w + } + if rs.Value == nil { + // for x = range m { ... } + return w // single var form + } + if !isIdent(rs.Value, "_") { + // for ?, y = range m { ... } + return w + } + + newRS := *rs // shallow copy + newRS.Value = nil + + w.onFailure(lint.Failure{ + Failure: fmt.Sprintf("should omit 2nd value from range; this loop is equivalent to `for %s %s range ...`", w.file.Render(rs.Key), rs.Tok), + Confidence: 1, + Node: rs.Value, + ReplacementLine: firstLineOf(w.file, &newRS, rs), + }) + + return w +} + +func firstLineOf(f *lint.File, node, match ast.Node) string { + line := f.Render(node) + if i := strings.Index(line, "\n"); i >= 0 { + line = line[:i] + } + return indentOf(f, match) + line +} + +func indentOf(f *lint.File, node ast.Node) string { + line := srcLine(f.Content(), f.ToPosition(node.Pos())) + for i, r := range line { + switch r { + case ' ', '\t': + default: + return line[:i] + } + } + return line // unusual or empty line +} diff --git a/vendor/github.com/mgechev/revive/rule/receiver-naming.go b/vendor/github.com/mgechev/revive/rule/receiver-naming.go new file mode 100644 index 0000000000..589d5f0ef3 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/receiver-naming.go @@ -0,0 +1,81 @@ +package rule + +import ( + "fmt" + "go/ast" + + "github.com/mgechev/revive/lint" +) + +// ReceiverNamingRule lints given else constructs. +type ReceiverNamingRule struct{} + +// Apply applies the rule to given file. +func (r *ReceiverNamingRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + fileAst := file.AST + walker := lintReceiverName{ + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + typeReceiver: map[string]string{}, + } + + ast.Walk(walker, fileAst) + + return failures +} + +// Name returns the rule name. +func (r *ReceiverNamingRule) Name() string { + return "receiver-naming" +} + +type lintReceiverName struct { + onFailure func(lint.Failure) + typeReceiver map[string]string +} + +func (w lintReceiverName) Visit(n ast.Node) ast.Visitor { + fn, ok := n.(*ast.FuncDecl) + if !ok || fn.Recv == nil || len(fn.Recv.List) == 0 { + return w + } + names := fn.Recv.List[0].Names + if len(names) < 1 { + return w + } + name := names[0].Name + const ref = styleGuideBase + "#receiver-names" + if name == "_" { + w.onFailure(lint.Failure{ + Node: n, + Confidence: 1, + Category: "naming", + Failure: "receiver name should not be an underscore, omit the name if it is unused", + }) + return w + } + if name == "this" || name == "self" { + w.onFailure(lint.Failure{ + Node: n, + Confidence: 1, + Category: "naming", + Failure: `receiver name should be a reflection of its identity; don't use generic names such as "this" or "self"`, + }) + return w + } + recv := receiverType(fn) + if prev, ok := w.typeReceiver[recv]; ok && prev != name { + w.onFailure(lint.Failure{ + Node: n, + Confidence: 1, + Category: "naming", + Failure: fmt.Sprintf("receiver name %s should be consistent with previous receiver name %s for %s", name, prev, recv), + }) + return w + } + w.typeReceiver[recv] = name + return w +} diff --git a/vendor/github.com/mgechev/revive/rule/redefines-builtin-id.go b/vendor/github.com/mgechev/revive/rule/redefines-builtin-id.go new file mode 100644 index 0000000000..947b8aac7c --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/redefines-builtin-id.go @@ -0,0 +1,145 @@ +package rule + +import ( + "fmt" + "github.com/mgechev/revive/lint" + "go/ast" + "go/token" +) + +// RedefinesBuiltinIDRule warns when a builtin identifier is shadowed. +type RedefinesBuiltinIDRule struct{} + +// Apply applies the rule to given file. +func (r *RedefinesBuiltinIDRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + var builtInConstAndVars = map[string]bool{ + "true": true, + "false": true, + "iota": true, + "nil": true, + } + + var builtFunctions = map[string]bool{ + "append": true, + "cap": true, + "close": true, + "complex": true, + "copy": true, + "delete": true, + "imag": true, + "len": true, + "make": true, + "new": true, + "panic": true, + "print": true, + "println": true, + "real": true, + "recover": true, + } + + var builtInTypes = map[string]bool{ + "ComplexType": true, + "FloatType": true, + "IntegerType": true, + "Type": true, + "Type1": true, + "bool": true, + "byte": true, + "complex128": true, + "complex64": true, + "error": true, + "float32": true, + "float64": true, + "int": true, + "int16": true, + "int32": true, + "int64": true, + "int8": true, + "rune": true, + "string": true, + "uint": true, + "uint16": true, + "uint32": true, + "uint64": true, + "uint8": true, + "uintptr": true, + } + + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + astFile := file.AST + w := &lintRedefinesBuiltinID{builtInConstAndVars, builtFunctions, builtInTypes, onFailure} + ast.Walk(w, astFile) + + return failures +} + +// Name returns the rule name. +func (r *RedefinesBuiltinIDRule) Name() string { + return "redefines-builtin-id" +} + +type lintRedefinesBuiltinID struct { + constsAndVars map[string]bool + funcs map[string]bool + types map[string]bool + onFailure func(lint.Failure) +} + +func (w *lintRedefinesBuiltinID) Visit(node ast.Node) ast.Visitor { + switch n := node.(type) { + case *ast.GenDecl: + if n.Tok != token.TYPE { + return nil // skip if not type declaration + } + typeSpec, ok := n.Specs[0].(*ast.TypeSpec) + if !ok { + return nil + } + id := typeSpec.Name.Name + if w.types[id] { + w.addFailure(n, fmt.Sprintf("redefinition of the built-in type %s", id)) + } + case *ast.FuncDecl: + if n.Recv != nil { + return w // skip methods + } + + id := n.Name.Name + if w.funcs[id] { + w.addFailure(n, fmt.Sprintf("redefinition of the built-in function %s", id)) + } + case *ast.AssignStmt: + for _, e := range n.Lhs { + id, ok := e.(*ast.Ident) + if !ok { + continue + } + + if w.constsAndVars[id.Name] { + var msg string + if n.Tok == token.DEFINE { + msg = fmt.Sprintf("assignment creates a shadow of built-in identifier %s", id.Name) + } else { + msg = fmt.Sprintf("assignment modifies built-in identifier %s", id.Name) + } + w.addFailure(n, msg) + } + } + } + + return w +} + +func (w lintRedefinesBuiltinID) addFailure(node ast.Node, msg string) { + w.onFailure(lint.Failure{ + Confidence: 1, + Node: node, + Category: "logic", + Failure: msg, + }) +} diff --git a/vendor/github.com/mgechev/revive/rule/string-of-int.go b/vendor/github.com/mgechev/revive/rule/string-of-int.go new file mode 100644 index 0000000000..38f453a4aa --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/string-of-int.go @@ -0,0 +1,95 @@ +package rule + +import ( + "go/ast" + "go/types" + + "github.com/mgechev/revive/lint" +) + +// StringOfIntRule warns when logic expressions contains Boolean literals. +type StringOfIntRule struct{} + +// Apply applies the rule to given file. +func (r *StringOfIntRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + astFile := file.AST + file.Pkg.TypeCheck() + + w := &lintStringInt{file, onFailure} + ast.Walk(w, astFile) + + return failures +} + +// Name returns the rule name. +func (r *StringOfIntRule) Name() string { + return "string-of-int" +} + +type lintStringInt struct { + file *lint.File + onFailure func(lint.Failure) +} + +func (w *lintStringInt) Visit(node ast.Node) ast.Visitor { + ce, ok := node.(*ast.CallExpr) + if !ok { + return w + } + + if !w.isCallStringCast(ce.Fun) { + return w + } + + if !w.isIntExpression(ce.Args) { + return w + } + + w.onFailure(lint.Failure{ + Confidence: 1, + Node: ce, + Failure: "dubious convertion of an integer into a string, use strconv.Itoa", + }) + + return w +} + +func (w *lintStringInt) isCallStringCast(e ast.Expr) bool { + t := w.file.Pkg.TypeOf(e) + if t == nil { + return false + } + + tb, _ := t.Underlying().(*types.Basic) + + return tb != nil && tb.Kind() == types.String +} + +func (w *lintStringInt) isIntExpression(es []ast.Expr) bool { + if len(es) != 1 { + return false + } + + t := w.file.Pkg.TypeOf(es[0]) + if t == nil { + return false + } + + ut, _ := t.Underlying().(*types.Basic) + if ut == nil || ut.Info()&types.IsInteger == 0 { + return false + } + + switch ut.Kind() { + case types.Byte, types.Rune, types.UntypedRune: + return false + } + + return true +} diff --git a/vendor/github.com/mgechev/revive/rule/struct-tag.go b/vendor/github.com/mgechev/revive/rule/struct-tag.go new file mode 100644 index 0000000000..57cf8103a6 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/struct-tag.go @@ -0,0 +1,236 @@ +package rule + +import ( + "fmt" + "go/ast" + "strconv" + "strings" + + "github.com/fatih/structtag" + "github.com/mgechev/revive/lint" +) + +// StructTagRule lints struct tags. +type StructTagRule struct{} + +// Apply applies the rule to given file. +func (r *StructTagRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + w := lintStructTagRule{onFailure: onFailure} + + ast.Walk(w, file.AST) + + return failures +} + +// Name returns the rule name. +func (r *StructTagRule) Name() string { + return "struct-tag" +} + +type lintStructTagRule struct { + onFailure func(lint.Failure) + usedTagNbr map[string]bool // list of used tag numbers +} + +func (w lintStructTagRule) Visit(node ast.Node) ast.Visitor { + switch n := node.(type) { + case *ast.StructType: + if n.Fields == nil || n.Fields.NumFields() < 1 { + return nil // skip empty structs + } + w.usedTagNbr = map[string]bool{} // init + for _, f := range n.Fields.List { + if f.Tag != nil { + w.checkTaggedField(f) + } + } + } + + return w + +} + +// checkTaggedField checks the tag of the given field. +// precondition: the field has a tag +func (w lintStructTagRule) checkTaggedField(f *ast.Field) { + if len(f.Names) > 0 && !f.Names[0].IsExported() { + w.addFailure(f, "tag on not-exported field "+f.Names[0].Name) + } + + tags, err := structtag.Parse(strings.Trim(f.Tag.Value, "`")) + if err != nil || tags == nil { + w.addFailure(f.Tag, "malformed tag") + return + } + + for _, tag := range tags.Tags() { + switch key := tag.Key; key { + case "asn1": + msg, ok := w.checkASN1Tag(f.Type, tag) + if !ok { + w.addFailure(f.Tag, msg) + } + case "bson": + msg, ok := w.checkBSONTag(tag.Options) + if !ok { + w.addFailure(f.Tag, msg) + } + case "default": + if !w.typeValueMatch(f.Type, tag.Name) { + w.addFailure(f.Tag, "field's type and default value's type mismatch") + } + case "json": + msg, ok := w.checkJSONTag(tag.Name, tag.Options) + if !ok { + w.addFailure(f.Tag, msg) + } + case "protobuf": + // Not implemented yet + case "required": + if tag.Name != "true" && tag.Name != "false" { + w.addFailure(f.Tag, "required should be 'true' or 'false'") + } + case "xml": + msg, ok := w.checkXMLTag(tag.Options) + if !ok { + w.addFailure(f.Tag, msg) + } + case "yaml": + msg, ok := w.checkYAMLTag(tag.Options) + if !ok { + w.addFailure(f.Tag, msg) + } + default: + // unknown key + } + } +} + +func (w lintStructTagRule) checkASN1Tag(t ast.Expr, tag *structtag.Tag) (string, bool) { + checkList := append(tag.Options, tag.Name) + for _, opt := range checkList { + switch opt { + case "application", "explicit", "generalized", "ia5", "omitempty", "optional", "set", "utf8": + + default: + if strings.HasPrefix(opt, "tag:") { + parts := strings.Split(opt, ":") + tagNumber := parts[1] + if w.usedTagNbr[tagNumber] { + return fmt.Sprintf("duplicated tag number %s", tagNumber), false + } + w.usedTagNbr[tagNumber] = true + + continue + } + + if strings.HasPrefix(opt, "default:") { + parts := strings.Split(opt, ":") + if len(parts) < 2 { + return "malformed default for ASN1 tag", false + } + if !w.typeValueMatch(t, parts[1]) { + return "field's type and default value's type mismatch", false + } + + continue + } + + return fmt.Sprintf("unknown option '%s' in ASN1 tag", opt), false + } + } + + return "", true +} + +func (w lintStructTagRule) checkBSONTag(options []string) (string, bool) { + for _, opt := range options { + switch opt { + case "inline", "minsize", "omitempty": + default: + return fmt.Sprintf("unknown option '%s' in BSON tag", opt), false + } + } + + return "", true +} + +func (w lintStructTagRule) checkJSONTag(name string, options []string) (string, bool) { + for _, opt := range options { + switch opt { + case "omitempty", "string": + case "": + // special case for JSON key "-" + if name != "-" { + return "option can not be empty in JSON tag", false + } + default: + return fmt.Sprintf("unknown option '%s' in JSON tag", opt), false + } + } + + return "", true +} + +func (w lintStructTagRule) checkXMLTag(options []string) (string, bool) { + for _, opt := range options { + switch opt { + case "any", "attr", "cdata", "chardata", "comment", "innerxml", "omitempty", "typeattr": + default: + return fmt.Sprintf("unknown option '%s' in XML tag", opt), false + } + } + + return "", true +} + +func (w lintStructTagRule) checkYAMLTag(options []string) (string, bool) { + for _, opt := range options { + switch opt { + case "flow", "inline", "omitempty": + default: + return fmt.Sprintf("unknown option '%s' in YAML tag", opt), false + } + } + + return "", true +} + +func (w lintStructTagRule) typeValueMatch(t ast.Expr, val string) bool { + tID, ok := t.(*ast.Ident) + if !ok { + return true + } + + typeMatches := true + switch tID.Name { + case "bool": + typeMatches = val == "true" || val == "false" + case "float64": + _, err := strconv.ParseFloat(val, 64) + typeMatches = err == nil + case "int": + _, err := strconv.ParseInt(val, 10, 64) + typeMatches = err == nil + case "string": + case "nil": + default: + // unchecked type + } + + return typeMatches +} + +func (w lintStructTagRule) addFailure(n ast.Node, msg string) { + w.onFailure(lint.Failure{ + Node: n, + Failure: msg, + Confidence: 1, + }) +} diff --git a/vendor/github.com/mgechev/revive/rule/superfluous-else.go b/vendor/github.com/mgechev/revive/rule/superfluous-else.go new file mode 100644 index 0000000000..c29be9e0d1 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/superfluous-else.go @@ -0,0 +1,114 @@ +package rule + +import ( + "fmt" + "go/ast" + "go/token" + + "github.com/mgechev/revive/lint" +) + +// SuperfluousElseRule lints given else constructs. +type SuperfluousElseRule struct{} + +// Apply applies the rule to given file. +func (r *SuperfluousElseRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + var branchingFunctions = map[string]map[string]bool{ + "os": map[string]bool{"Exit": true}, + "log": map[string]bool{ + "Fatal": true, + "Fatalf": true, + "Fatalln": true, + "Panic": true, + "Panicf": true, + "Panicln": true, + }, + } + + w := lintSuperfluousElse{make(map[*ast.IfStmt]bool), onFailure, branchingFunctions} + ast.Walk(w, file.AST) + return failures +} + +// Name returns the rule name. +func (r *SuperfluousElseRule) Name() string { + return "superfluous-else" +} + +type lintSuperfluousElse struct { + ignore map[*ast.IfStmt]bool + onFailure func(lint.Failure) + branchingFunctions map[string]map[string]bool +} + +func (w lintSuperfluousElse) Visit(node ast.Node) ast.Visitor { + ifStmt, ok := node.(*ast.IfStmt) + if !ok || ifStmt.Else == nil { + return w + } + if w.ignore[ifStmt] { + if elseif, ok := ifStmt.Else.(*ast.IfStmt); ok { + w.ignore[elseif] = true + } + return w + } + if elseif, ok := ifStmt.Else.(*ast.IfStmt); ok { + w.ignore[elseif] = true + return w + } + if _, ok := ifStmt.Else.(*ast.BlockStmt); !ok { + // only care about elses without conditions + return w + } + if len(ifStmt.Body.List) == 0 { + return w + } + shortDecl := false // does the if statement have a ":=" initialization statement? + if ifStmt.Init != nil { + if as, ok := ifStmt.Init.(*ast.AssignStmt); ok && as.Tok == token.DEFINE { + shortDecl = true + } + } + extra := "" + if shortDecl { + extra = " (move short variable declaration to its own line if necessary)" + } + + lastStmt := ifStmt.Body.List[len(ifStmt.Body.List)-1] + switch stmt := lastStmt.(type) { + case *ast.BranchStmt: + token := stmt.Tok.String() + if token != "fallthrough" { + w.onFailure(newFailure(ifStmt.Else, "if block ends with a "+token+" statement, so drop this else and outdent its block"+extra)) + } + case *ast.ExprStmt: + if ce, ok := stmt.X.(*ast.CallExpr); ok { // it's a function call + if fc, ok := ce.Fun.(*ast.SelectorExpr); ok { + if id, ok := fc.X.(*ast.Ident); ok { + fn := fc.Sel.Name + pkg := id.Name + if w.branchingFunctions[pkg][fn] { // it's a call to a branching function + w.onFailure( + newFailure(ifStmt.Else, fmt.Sprintf("if block ends with call to %s.%s function, so drop this else and outdent its block%s", pkg, fn, extra))) + } + } + } + } + } + + return w +} + +func newFailure(node ast.Node, msg string) lint.Failure { + return lint.Failure{ + Confidence: 1, + Node: node, + Category: "indent", + Failure: msg, + } +} diff --git a/vendor/github.com/mgechev/revive/rule/time-naming.go b/vendor/github.com/mgechev/revive/rule/time-naming.go new file mode 100644 index 0000000000..a93f4b5ae0 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/time-naming.go @@ -0,0 +1,93 @@ +package rule + +import ( + "fmt" + "go/ast" + "go/types" + "strings" + + "github.com/mgechev/revive/lint" +) + +// TimeNamingRule lints given else constructs. +type TimeNamingRule struct{} + +// Apply applies the rule to given file. +func (r *TimeNamingRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + w := &lintTimeNames{file, onFailure} + + file.Pkg.TypeCheck() + ast.Walk(w, file.AST) + return failures +} + +// Name returns the rule name. +func (r *TimeNamingRule) Name() string { + return "time-naming" +} + +type lintTimeNames struct { + file *lint.File + onFailure func(lint.Failure) +} + +func (w *lintTimeNames) Visit(node ast.Node) ast.Visitor { + v, ok := node.(*ast.ValueSpec) + if !ok { + return w + } + for _, name := range v.Names { + origTyp := w.file.Pkg.TypeOf(name) + // Look for time.Duration or *time.Duration; + // the latter is common when using flag.Duration. + typ := origTyp + if pt, ok := typ.(*types.Pointer); ok { + typ = pt.Elem() + } + if !isNamedType(typ, "time", "Duration") { + continue + } + suffix := "" + for _, suf := range timeSuffixes { + if strings.HasSuffix(name.Name, suf) { + suffix = suf + break + } + } + if suffix == "" { + continue + } + w.onFailure(lint.Failure{ + Category: "time", + Confidence: 0.9, + Node: v, + Failure: fmt.Sprintf("var %s is of type %v; don't use unit-specific suffix %q", name.Name, origTyp, suffix), + }) + } + return w +} + +// timeSuffixes is a list of name suffixes that imply a time unit. +// This is not an exhaustive list. +var timeSuffixes = []string{ + "Sec", "Secs", "Seconds", + "Msec", "Msecs", + "Milli", "Millis", "Milliseconds", + "Usec", "Usecs", "Microseconds", + "MS", "Ms", +} + +func isNamedType(typ types.Type, importPath, name string) bool { + n, ok := typ.(*types.Named) + if !ok { + return false + } + tn := n.Obj() + return tn != nil && tn.Pkg() != nil && tn.Pkg().Path() == importPath && tn.Name() == name +} diff --git a/vendor/github.com/mgechev/revive/rule/unexported-return.go b/vendor/github.com/mgechev/revive/rule/unexported-return.go new file mode 100644 index 0000000000..c9c8a41d38 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/unexported-return.go @@ -0,0 +1,106 @@ +package rule + +import ( + "fmt" + "go/ast" + "go/types" + + "github.com/mgechev/revive/lint" +) + +// UnexportedReturnRule lints given else constructs. +type UnexportedReturnRule struct{} + +// Apply applies the rule to given file. +func (r *UnexportedReturnRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + fileAst := file.AST + walker := lintUnexportedReturn{ + file: file, + fileAst: fileAst, + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + } + + file.Pkg.TypeCheck() + ast.Walk(walker, fileAst) + + return failures +} + +// Name returns the rule name. +func (r *UnexportedReturnRule) Name() string { + return "unexported-return" +} + +type lintUnexportedReturn struct { + file *lint.File + fileAst *ast.File + onFailure func(lint.Failure) +} + +func (w lintUnexportedReturn) Visit(n ast.Node) ast.Visitor { + fn, ok := n.(*ast.FuncDecl) + if !ok { + return w + } + if fn.Type.Results == nil { + return nil + } + if !fn.Name.IsExported() { + return nil + } + thing := "func" + if fn.Recv != nil && len(fn.Recv.List) > 0 { + thing = "method" + if !ast.IsExported(receiverType(fn)) { + // Don't report exported methods of unexported types, + // such as private implementations of sort.Interface. + return nil + } + } + for _, ret := range fn.Type.Results.List { + typ := w.file.Pkg.TypeOf(ret.Type) + if exportedType(typ) { + continue + } + w.onFailure(lint.Failure{ + Category: "unexported-type-in-api", + Node: ret.Type, + Confidence: 0.8, + Failure: fmt.Sprintf("exported %s %s returns unexported type %s, which can be annoying to use", + thing, fn.Name.Name, typ), + }) + break // only flag one + } + return nil +} + +// exportedType reports whether typ is an exported type. +// It is imprecise, and will err on the side of returning true, +// such as for composite types. +func exportedType(typ types.Type) bool { + switch T := typ.(type) { + case *types.Named: + obj := T.Obj() + switch { + // Builtin types have no package. + case obj.Pkg() == nil: + case obj.Exported(): + default: + _, ok := T.Underlying().(*types.Interface) + return ok + } + return true + case *types.Map: + return exportedType(T.Key()) && exportedType(T.Elem()) + case interface { + Elem() types.Type + }: // array, slice, pointer, chan + return exportedType(T.Elem()) + } + // Be conservative about other types, such as struct, interface, etc. + return true +} diff --git a/vendor/github.com/mgechev/revive/rule/unhandled-error.go b/vendor/github.com/mgechev/revive/rule/unhandled-error.go new file mode 100644 index 0000000000..0e2f628758 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/unhandled-error.go @@ -0,0 +1,120 @@ +package rule + +import ( + "fmt" + "go/ast" + "go/types" + + "github.com/mgechev/revive/lint" +) + +// UnhandledErrorRule lints given else constructs. +type UnhandledErrorRule struct{} + +type ignoreListType map[string]struct{} + +// Apply applies the rule to given file. +func (r *UnhandledErrorRule) Apply(file *lint.File, args lint.Arguments) []lint.Failure { + var failures []lint.Failure + + ignoreList := make(ignoreListType, len(args)) + + for _, arg := range args { + argStr, ok := arg.(string) + if !ok { + panic(fmt.Sprintf("Invalid argument to the unhandled-error rule. Expecting a string, got %T", arg)) + } + + ignoreList[argStr] = struct{}{} + } + + walker := &lintUnhandledErrors{ + ignoreList: ignoreList, + pkg: file.Pkg, + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + } + + file.Pkg.TypeCheck() + ast.Walk(walker, file.AST) + + return failures +} + +// Name returns the rule name. +func (r *UnhandledErrorRule) Name() string { + return "unhandled-error" +} + +type lintUnhandledErrors struct { + ignoreList ignoreListType + pkg *lint.Package + onFailure func(lint.Failure) +} + +// Visit looks for statements that are function calls. +// If the called function returns a value of type error a failure will be created. +func (w *lintUnhandledErrors) Visit(node ast.Node) ast.Visitor { + switch n := node.(type) { + case *ast.ExprStmt: + fCall, ok := n.X.(*ast.CallExpr) + if !ok { + return nil // not a function call + } + + funcType := w.pkg.TypeOf(fCall) + if funcType == nil { + return nil // skip, type info not available + } + + switch t := funcType.(type) { + case *types.Named: + if !w.isTypeError(t) { + return nil // func call does not return an error + } + + w.addFailure(fCall) + default: + retTypes, ok := funcType.Underlying().(*types.Tuple) + if !ok { + return nil // skip, unable to retrieve return type of the called function + } + + if w.returnsAnError(retTypes) { + w.addFailure(fCall) + } + } + } + return w +} + +func (w *lintUnhandledErrors) addFailure(n *ast.CallExpr) { + funcName := gofmt(n.Fun) + if _, mustIgnore := w.ignoreList[funcName]; mustIgnore { + return + } + + w.onFailure(lint.Failure{ + Category: "bad practice", + Confidence: 1, + Node: n, + Failure: fmt.Sprintf("Unhandled error in call to function %v", funcName), + }) +} + +func (*lintUnhandledErrors) isTypeError(t *types.Named) bool { + const errorTypeName = "_.error" + + return t.Obj().Id() == errorTypeName +} + +func (w *lintUnhandledErrors) returnsAnError(tt *types.Tuple) bool { + for i := 0; i < tt.Len(); i++ { + nt, ok := tt.At(i).Type().(*types.Named) + if ok && w.isTypeError(nt) { + return true + } + } + return false +} diff --git a/vendor/github.com/mgechev/revive/rule/unnecessary-stmt.go b/vendor/github.com/mgechev/revive/rule/unnecessary-stmt.go new file mode 100644 index 0000000000..732d8a8bb6 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/unnecessary-stmt.go @@ -0,0 +1,107 @@ +package rule + +import ( + "go/ast" + "go/token" + + "github.com/mgechev/revive/lint" +) + +// UnnecessaryStmtRule warns on unnecessary statements. +type UnnecessaryStmtRule struct{} + +// Apply applies the rule to given file. +func (r *UnnecessaryStmtRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + w := lintUnnecessaryStmtRule{onFailure} + ast.Walk(w, file.AST) + return failures +} + +// Name returns the rule name. +func (r *UnnecessaryStmtRule) Name() string { + return "unnecessary-stmt" +} + +type lintUnnecessaryStmtRule struct { + onFailure func(lint.Failure) +} + +func (w lintUnnecessaryStmtRule) Visit(node ast.Node) ast.Visitor { + switch n := node.(type) { + case *ast.FuncDecl: + if n.Body == nil || n.Type.Results != nil { + return w + } + stmts := n.Body.List + if len(stmts) == 0 { + return w + } + + lastStmt := stmts[len(stmts)-1] + rs, ok := lastStmt.(*ast.ReturnStmt) + if !ok { + return w + } + + if len(rs.Results) == 0 { + w.newFailure(lastStmt, "omit unnecessary return statement") + } + + case *ast.SwitchStmt: + w.checkSwitchBody(n.Body) + case *ast.TypeSwitchStmt: + w.checkSwitchBody(n.Body) + case *ast.CaseClause: + if n.Body == nil { + return w + } + stmts := n.Body + if len(stmts) == 0 { + return w + } + + lastStmt := stmts[len(stmts)-1] + rs, ok := lastStmt.(*ast.BranchStmt) + if !ok { + return w + } + + if rs.Tok == token.BREAK && rs.Label == nil { + w.newFailure(lastStmt, "omit unnecessary break at the end of case clause") + } + } + + return w +} + +func (w lintUnnecessaryStmtRule) checkSwitchBody(b *ast.BlockStmt) { + cases := b.List + if len(cases) != 1 { + return + } + + cc, ok := cases[0].(*ast.CaseClause) + if !ok { + return + } + + if len(cc.List) > 1 { // skip cases with multiple expressions + return + } + + w.newFailure(b, "switch with only one case can be replaced by an if-then") +} + +func (w lintUnnecessaryStmtRule) newFailure(node ast.Node, msg string) { + w.onFailure(lint.Failure{ + Confidence: 1, + Node: node, + Category: "style", + Failure: msg, + }) +} diff --git a/vendor/github.com/mgechev/revive/rule/unreachable-code.go b/vendor/github.com/mgechev/revive/rule/unreachable-code.go new file mode 100644 index 0000000000..c81e9e733b --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/unreachable-code.go @@ -0,0 +1,114 @@ +package rule + +import ( + "go/ast" + + "github.com/mgechev/revive/lint" +) + +// UnreachableCodeRule lints unreachable code. +type UnreachableCodeRule struct{} + +// Apply applies the rule to given file. +func (r *UnreachableCodeRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + var branchingFunctions = map[string]map[string]bool{ + "os": map[string]bool{"Exit": true}, + "log": map[string]bool{ + "Fatal": true, + "Fatalf": true, + "Fatalln": true, + "Panic": true, + "Panicf": true, + "Panicln": true, + }, + } + + w := lintUnreachableCode{onFailure, branchingFunctions} + ast.Walk(w, file.AST) + return failures +} + +// Name returns the rule name. +func (r *UnreachableCodeRule) Name() string { + return "unreachable-code" +} + +type lintUnreachableCode struct { + onFailure func(lint.Failure) + branchingFunctions map[string]map[string]bool +} + +func (w lintUnreachableCode) Visit(node ast.Node) ast.Visitor { + blk, ok := node.(*ast.BlockStmt) + if !ok { + return w + } + + if len(blk.List) < 2 { + return w + } +loop: + for i, stmt := range blk.List[:len(blk.List)-1] { + // println("iterating ", len(blk.List)) + next := blk.List[i+1] + if _, ok := next.(*ast.LabeledStmt); ok { + continue // skip if next statement is labeled + } + + switch s := stmt.(type) { + case *ast.ReturnStmt: + w.onFailure(newUnreachableCodeFailure(s)) + break loop + case *ast.BranchStmt: + token := s.Tok.String() + if token != "fallthrough" { + w.onFailure(newUnreachableCodeFailure(s)) + break loop + } + case *ast.ExprStmt: + ce, ok := s.X.(*ast.CallExpr) + if !ok { + continue + } + // it's a function call + fc, ok := ce.Fun.(*ast.SelectorExpr) + if !ok { + continue + } + + id, ok := fc.X.(*ast.Ident) + + if !ok { + continue + } + fn := fc.Sel.Name + pkg := id.Name + if !w.branchingFunctions[pkg][fn] { // it isn't a call to a branching function + continue + } + + if _, ok := next.(*ast.ReturnStmt); ok { // return statement needed to satisfy function signature + continue + } + + w.onFailure(newUnreachableCodeFailure(s)) + break loop + } + } + + return w +} + +func newUnreachableCodeFailure(node ast.Node) lint.Failure { + return lint.Failure{ + Confidence: 1, + Node: node, + Category: "logic", + Failure: "unreachable code after this statement", + } +} diff --git a/vendor/github.com/mgechev/revive/rule/unused-param.go b/vendor/github.com/mgechev/revive/rule/unused-param.go new file mode 100644 index 0000000000..60df908d3d --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/unused-param.go @@ -0,0 +1,102 @@ +package rule + +import ( + "fmt" + "go/ast" + + "github.com/mgechev/revive/lint" +) + +// UnusedParamRule lints unused params in functions. +type UnusedParamRule struct{} + +// Apply applies the rule to given file. +func (r *UnusedParamRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + w := lintUnusedParamRule{onFailure: onFailure} + + ast.Walk(w, file.AST) + + return failures +} + +// Name returns the rule name. +func (r *UnusedParamRule) Name() string { + return "unused-parameter" +} + +type lintUnusedParamRule struct { + onFailure func(lint.Failure) +} + +func (w lintUnusedParamRule) Visit(node ast.Node) ast.Visitor { + switch n := node.(type) { + case *ast.FuncDecl: + params := retrieveNamedParams(n.Type.Params) + if len(params) < 1 { + return nil // skip, func without parameters + } + + if n.Body == nil { + return nil // skip, is a function prototype + } + + // inspect the func body looking for references to parameters + fselect := func(n ast.Node) bool { + ident, isAnID := n.(*ast.Ident) + + if !isAnID { + return false + } + + _, isAParam := params[ident.Obj] + if isAParam { + params[ident.Obj] = false // mark as used + } + + return false + } + _ = pick(n.Body, fselect, nil) + + for _, p := range n.Type.Params.List { + for _, n := range p.Names { + if params[n.Obj] { + w.onFailure(lint.Failure{ + Confidence: 1, + Node: n, + Category: "bad practice", + Failure: fmt.Sprintf("parameter '%s' seems to be unused, consider removing or renaming it as _", n.Name), + }) + } + } + } + + return nil // full method body already inspected + } + + return w +} + +func retrieveNamedParams(params *ast.FieldList) map[*ast.Object]bool { + result := map[*ast.Object]bool{} + if params.List == nil { + return result + } + + for _, p := range params.List { + for _, n := range p.Names { + if n.Name == "_" { + continue + } + + result[n.Obj] = true + } + } + + return result +} diff --git a/vendor/github.com/mgechev/revive/rule/unused-receiver.go b/vendor/github.com/mgechev/revive/rule/unused-receiver.go new file mode 100644 index 0000000000..43eaf83a49 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/unused-receiver.go @@ -0,0 +1,77 @@ +package rule + +import ( + "fmt" + "go/ast" + + "github.com/mgechev/revive/lint" +) + +// UnusedReceiverRule lints unused params in functions. +type UnusedReceiverRule struct{} + +// Apply applies the rule to given file. +func (_ *UnusedReceiverRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + w := lintUnusedReceiverRule{onFailure: onFailure} + + ast.Walk(w, file.AST) + + return failures +} + +// Name returns the rule name. +func (_ *UnusedReceiverRule) Name() string { + return "unused-receiver" +} + +type lintUnusedReceiverRule struct { + onFailure func(lint.Failure) +} + +func (w lintUnusedReceiverRule) Visit(node ast.Node) ast.Visitor { + switch n := node.(type) { + case *ast.FuncDecl: + if n.Recv == nil { + return nil // skip this func decl, not a method + } + + rec := n.Recv.List[0] // safe to access only the first (unique) element of the list + if len(rec.Names) < 1 { + return nil // the receiver is anonymous: func (aType) Foo(...) ... + } + + recID := rec.Names[0] + if recID.Name == "_" { + return nil // the receiver is already named _ + } + + // inspect the func body looking for references to the receiver id + fselect := func(n ast.Node) bool { + ident, isAnID := n.(*ast.Ident) + + return isAnID && ident.Obj == recID.Obj + } + refs2recID := pick(n.Body, fselect, nil) + + if len(refs2recID) > 0 { + return nil // the receiver is referenced in the func body + } + + w.onFailure(lint.Failure{ + Confidence: 1, + Node: recID, + Category: "bad practice", + Failure: fmt.Sprintf("method receiver '%s' is not referenced in method's body, consider removing or renaming it as _", recID.Name), + }) + + return nil // full method body already inspected + } + + return w +} diff --git a/vendor/github.com/mgechev/revive/rule/utils.go b/vendor/github.com/mgechev/revive/rule/utils.go new file mode 100644 index 0000000000..6ba542b716 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/utils.go @@ -0,0 +1,191 @@ +package rule + +import ( + "bytes" + "fmt" + "go/ast" + "go/printer" + "go/token" + "go/types" + "regexp" + "strings" + + "github.com/mgechev/revive/lint" +) + +const styleGuideBase = "https://golang.org/wiki/CodeReviewComments" + +// isBlank returns whether id is the blank identifier "_". +// If id == nil, the answer is false. +func isBlank(id *ast.Ident) bool { return id != nil && id.Name == "_" } + +func isTest(f *lint.File) bool { + return strings.HasSuffix(f.Name, "_test.go") +} + +var commonMethods = map[string]bool{ + "Error": true, + "Read": true, + "ServeHTTP": true, + "String": true, + "Write": true, +} + +func receiverType(fn *ast.FuncDecl) string { + switch e := fn.Recv.List[0].Type.(type) { + case *ast.Ident: + return e.Name + case *ast.StarExpr: + if id, ok := e.X.(*ast.Ident); ok { + return id.Name + } + } + // The parser accepts much more than just the legal forms. + return "invalid-type" +} + +var knownNameExceptions = map[string]bool{ + "LastInsertId": true, // must match database/sql + "kWh": true, +} + +func isCgoExported(f *ast.FuncDecl) bool { + if f.Recv != nil || f.Doc == nil { + return false + } + + cgoExport := regexp.MustCompile(fmt.Sprintf("(?m)^//export %s$", regexp.QuoteMeta(f.Name.Name))) + for _, c := range f.Doc.List { + if cgoExport.MatchString(c.Text) { + return true + } + } + return false +} + +var allCapsRE = regexp.MustCompile(`^[A-Z0-9_]+$`) + +func isIdent(expr ast.Expr, ident string) bool { + id, ok := expr.(*ast.Ident) + return ok && id.Name == ident +} + +var zeroLiteral = map[string]bool{ + "false": true, // bool + // runes + `'\x00'`: true, + `'\000'`: true, + // strings + `""`: true, + "``": true, + // numerics + "0": true, + "0.": true, + "0.0": true, + "0i": true, +} + +func validType(T types.Type) bool { + return T != nil && + T != types.Typ[types.Invalid] && + !strings.Contains(T.String(), "invalid type") // good but not foolproof +} + +func isPkgDot(expr ast.Expr, pkg, name string) bool { + sel, ok := expr.(*ast.SelectorExpr) + return ok && isIdent(sel.X, pkg) && isIdent(sel.Sel, name) +} + +func srcLine(src []byte, p token.Position) string { + // Run to end of line in both directions if not at line start/end. + lo, hi := p.Offset, p.Offset+1 + for lo > 0 && src[lo-1] != '\n' { + lo-- + } + for hi < len(src) && src[hi-1] != '\n' { + hi++ + } + return string(src[lo:hi]) +} + +// pick yields a list of nodes by picking them from a sub-ast with root node n. +// Nodes are selected by applying the fselect function +// f function is applied to each selected node before inseting it in the final result. +// If f==nil then it defaults to the identity function (ie it returns the node itself) +func pick(n ast.Node, fselect func(n ast.Node) bool, f func(n ast.Node) []ast.Node) []ast.Node { + var result []ast.Node + + if n == nil { + return result + } + + if f == nil { + f = func(n ast.Node) []ast.Node { return []ast.Node{n} } + } + + onSelect := func(n ast.Node) { + result = append(result, f(n)...) + } + p := picker{fselect: fselect, onSelect: onSelect} + ast.Walk(p, n) + return result +} + +func pickFromExpList(l []ast.Expr, fselect func(n ast.Node) bool, f func(n ast.Node) []ast.Node) []ast.Node { + result := make([]ast.Node, 0) + for _, e := range l { + result = append(result, pick(e, fselect, f)...) + } + return result +} + +type picker struct { + fselect func(n ast.Node) bool + onSelect func(n ast.Node) +} + +func (p picker) Visit(node ast.Node) ast.Visitor { + if p.fselect == nil { + return nil + } + + if p.fselect(node) { + p.onSelect(node) + } + + return p +} + +// isBoolOp returns true if the given token corresponds to +// a bool operator +func isBoolOp(t token.Token) bool { + switch t { + case token.LAND, token.LOR, token.EQL, token.NEQ: + return true + } + + return false +} + +const ( + trueName = "true" + falseName = "false" +) + +func isExprABooleanLit(n ast.Node) (lexeme string, ok bool) { + oper, ok := n.(*ast.Ident) + + if !ok { + return "", false + } + + return oper.Name, (oper.Name == trueName || oper.Name == falseName) +} + +// gofmt returns a string representation of the expression. +func gofmt(x ast.Expr) string { + buf := bytes.Buffer{} + fs := token.NewFileSet() + printer.Fprint(&buf, fs, x) + return buf.String() +} diff --git a/vendor/github.com/mgechev/revive/rule/var-declarations.go b/vendor/github.com/mgechev/revive/rule/var-declarations.go new file mode 100644 index 0000000000..441132115e --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/var-declarations.go @@ -0,0 +1,120 @@ +package rule + +import ( + "fmt" + "go/ast" + "go/token" + "go/types" + + "github.com/mgechev/revive/lint" +) + +// VarDeclarationsRule lints given else constructs. +type VarDeclarationsRule struct{} + +// Apply applies the rule to given file. +func (r *VarDeclarationsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + fileAst := file.AST + walker := &lintVarDeclarations{ + file: file, + fileAst: fileAst, + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + } + + file.Pkg.TypeCheck() + ast.Walk(walker, fileAst) + + return failures +} + +// Name returns the rule name. +func (r *VarDeclarationsRule) Name() string { + return "var-declaration" +} + +type lintVarDeclarations struct { + fileAst *ast.File + file *lint.File + lastGen *ast.GenDecl + onFailure func(lint.Failure) +} + +func (w *lintVarDeclarations) Visit(node ast.Node) ast.Visitor { + switch v := node.(type) { + case *ast.GenDecl: + if v.Tok != token.CONST && v.Tok != token.VAR { + return nil + } + w.lastGen = v + return w + case *ast.ValueSpec: + if w.lastGen.Tok == token.CONST { + return nil + } + if len(v.Names) > 1 || v.Type == nil || len(v.Values) == 0 { + return nil + } + rhs := v.Values[0] + // An underscore var appears in a common idiom for compile-time interface satisfaction, + // as in "var _ Interface = (*Concrete)(nil)". + if isIdent(v.Names[0], "_") { + return nil + } + // If the RHS is a zero value, suggest dropping it. + zero := false + if lit, ok := rhs.(*ast.BasicLit); ok { + zero = zeroLiteral[lit.Value] + } else if isIdent(rhs, "nil") { + zero = true + } + if zero { + w.onFailure(lint.Failure{ + Confidence: 0.9, + Node: rhs, + Category: "zero-value", + Failure: fmt.Sprintf("should drop = %s from declaration of var %s; it is the zero value", w.file.Render(rhs), v.Names[0]), + }) + return nil + } + lhsTyp := w.file.Pkg.TypeOf(v.Type) + rhsTyp := w.file.Pkg.TypeOf(rhs) + + if !validType(lhsTyp) || !validType(rhsTyp) { + // Type checking failed (often due to missing imports). + return nil + } + + if !types.Identical(lhsTyp, rhsTyp) { + // Assignment to a different type is not redundant. + return nil + } + + // The next three conditions are for suppressing the warning in situations + // where we were unable to typecheck. + + // If the LHS type is an interface, don't warn, since it is probably a + // concrete type on the RHS. Note that our feeble lexical check here + // will only pick up interface{} and other literal interface types; + // that covers most of the cases we care to exclude right now. + if _, ok := v.Type.(*ast.InterfaceType); ok { + return nil + } + // If the RHS is an untyped const, only warn if the LHS type is its default type. + if defType, ok := w.file.IsUntypedConst(rhs); ok && !isIdent(v.Type, defType) { + return nil + } + + w.onFailure(lint.Failure{ + Category: "type-inference", + Confidence: 0.8, + Node: v.Type, + Failure: fmt.Sprintf("should omit type %s from declaration of var %s; it will be inferred from the right-hand side", w.file.Render(v.Type), v.Names[0]), + }) + return nil + } + return w +} diff --git a/vendor/github.com/mgechev/revive/rule/var-naming.go b/vendor/github.com/mgechev/revive/rule/var-naming.go new file mode 100644 index 0000000000..768f65b966 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/var-naming.go @@ -0,0 +1,230 @@ +package rule + +import ( + "fmt" + "go/ast" + "go/token" + "strings" + + "github.com/mgechev/revive/lint" +) + +// VarNamingRule lints given else constructs. +type VarNamingRule struct{} + +// Apply applies the rule to given file. +func (r *VarNamingRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure { + var failures []lint.Failure + + var whitelist []string + var blacklist []string + + if len(arguments) >= 1 { + whitelist = getList(arguments[0], "whitelist") + } + + if len(arguments) >= 2 { + blacklist = getList(arguments[1], "blacklist") + } + + fileAst := file.AST + walker := lintNames{ + file: file, + fileAst: fileAst, + whitelist: whitelist, + blacklist: blacklist, + onFailure: func(failure lint.Failure) { + failures = append(failures, failure) + }, + } + + // Package names need slightly different handling than other names. + if strings.Contains(walker.fileAst.Name.Name, "_") && !strings.HasSuffix(walker.fileAst.Name.Name, "_test") { + walker.onFailure(lint.Failure{ + Failure: "don't use an underscore in package name", + Confidence: 1, + Node: walker.fileAst, + Category: "naming", + }) + } + + ast.Walk(&walker, fileAst) + + return failures +} + +// Name returns the rule name. +func (r *VarNamingRule) Name() string { + return "var-naming" +} + +func checkList(fl *ast.FieldList, thing string, w *lintNames) { + if fl == nil { + return + } + for _, f := range fl.List { + for _, id := range f.Names { + check(id, thing, w) + } + } +} + +func check(id *ast.Ident, thing string, w *lintNames) { + if id.Name == "_" { + return + } + if knownNameExceptions[id.Name] { + return + } + + // Handle two common styles from other languages that don't belong in Go. + if len(id.Name) >= 5 && allCapsRE.MatchString(id.Name) && strings.Contains(id.Name, "_") { + w.onFailure(lint.Failure{ + Failure: "don't use ALL_CAPS in Go names; use CamelCase", + Confidence: 0.8, + Node: id, + Category: "naming", + }) + return + } + if len(id.Name) > 2 && id.Name[0] == 'k' && id.Name[1] >= 'A' && id.Name[1] <= 'Z' { + should := string(id.Name[1]+'a'-'A') + id.Name[2:] + w.onFailure(lint.Failure{ + Failure: fmt.Sprintf("don't use leading k in Go names; %s %s should be %s", thing, id.Name, should), + Confidence: 0.8, + Node: id, + Category: "naming", + }) + } + + should := lint.Name(id.Name, w.whitelist, w.blacklist) + if id.Name == should { + return + } + + if len(id.Name) > 2 && strings.Contains(id.Name[1:], "_") { + w.onFailure(lint.Failure{ + Failure: fmt.Sprintf("don't use underscores in Go names; %s %s should be %s", thing, id.Name, should), + Confidence: 0.9, + Node: id, + Category: "naming", + }) + return + } + w.onFailure(lint.Failure{ + Failure: fmt.Sprintf("%s %s should be %s", thing, id.Name, should), + Confidence: 0.8, + Node: id, + Category: "naming", + }) +} + +type lintNames struct { + file *lint.File + fileAst *ast.File + lastGen *ast.GenDecl + genDeclMissingComments map[*ast.GenDecl]bool + onFailure func(lint.Failure) + whitelist []string + blacklist []string +} + +func (w *lintNames) Visit(n ast.Node) ast.Visitor { + switch v := n.(type) { + case *ast.AssignStmt: + if v.Tok == token.ASSIGN { + return w + } + for _, exp := range v.Lhs { + if id, ok := exp.(*ast.Ident); ok { + check(id, "var", w) + } + } + case *ast.FuncDecl: + if w.file.IsTest() && (strings.HasPrefix(v.Name.Name, "Example") || strings.HasPrefix(v.Name.Name, "Test") || strings.HasPrefix(v.Name.Name, "Benchmark")) { + return w + } + + thing := "func" + if v.Recv != nil { + thing = "method" + } + + // Exclude naming warnings for functions that are exported to C but + // not exported in the Go API. + // See https://github.com/golang/lint/issues/144. + if ast.IsExported(v.Name.Name) || !isCgoExported(v) { + check(v.Name, thing, w) + } + + checkList(v.Type.Params, thing+" parameter", w) + checkList(v.Type.Results, thing+" result", w) + case *ast.GenDecl: + if v.Tok == token.IMPORT { + return w + } + var thing string + switch v.Tok { + case token.CONST: + thing = "const" + case token.TYPE: + thing = "type" + case token.VAR: + thing = "var" + } + for _, spec := range v.Specs { + switch s := spec.(type) { + case *ast.TypeSpec: + check(s.Name, thing, w) + case *ast.ValueSpec: + for _, id := range s.Names { + check(id, thing, w) + } + } + } + case *ast.InterfaceType: + // Do not check interface method names. + // They are often constrainted by the method names of concrete types. + for _, x := range v.Methods.List { + ft, ok := x.Type.(*ast.FuncType) + if !ok { // might be an embedded interface name + continue + } + checkList(ft.Params, "interface method parameter", w) + checkList(ft.Results, "interface method result", w) + } + case *ast.RangeStmt: + if v.Tok == token.ASSIGN { + return w + } + if id, ok := v.Key.(*ast.Ident); ok { + check(id, "range var", w) + } + if id, ok := v.Value.(*ast.Ident); ok { + check(id, "range var", w) + } + case *ast.StructType: + for _, f := range v.Fields.List { + for _, id := range f.Names { + check(id, "struct field", w) + } + } + } + return w +} + +func getList(arg interface{}, argName string) []string { + temp, ok := arg.([]interface{}) + if !ok { + panic(fmt.Sprintf("Invalid argument to the var-naming rule. Expecting a %s of type slice with initialisms, got %T", argName, arg)) + } + var list []string + for _, v := range temp { + if val, ok := v.(string); ok { + list = append(list, val) + } else { + panic(fmt.Sprintf("Invalid %s values of the var-naming rule. Expecting slice of strings but got element of type %T", val, arg)) + } + } + return list +} diff --git a/vendor/github.com/mgechev/revive/rule/waitgroup-by-value.go b/vendor/github.com/mgechev/revive/rule/waitgroup-by-value.go new file mode 100644 index 0000000000..b86929136c --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/waitgroup-by-value.go @@ -0,0 +1,66 @@ +package rule + +import ( + "go/ast" + + "github.com/mgechev/revive/lint" +) + +// WaitGroupByValueRule lints sync.WaitGroup passed by copy in functions. +type WaitGroupByValueRule struct{} + +// Apply applies the rule to given file. +func (r *WaitGroupByValueRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { + var failures []lint.Failure + + onFailure := func(failure lint.Failure) { + failures = append(failures, failure) + } + + w := lintWaitGroupByValueRule{onFailure: onFailure} + ast.Walk(w, file.AST) + return failures +} + +// Name returns the rule name. +func (r *WaitGroupByValueRule) Name() string { + return "waitgroup-by-value" +} + +type lintWaitGroupByValueRule struct { + onFailure func(lint.Failure) +} + +func (w lintWaitGroupByValueRule) Visit(node ast.Node) ast.Visitor { + // look for function declarations + fd, ok := node.(*ast.FuncDecl) + if !ok { + return w + } + + // Check all function's parameters + for _, field := range fd.Type.Params.List { + if !w.isWaitGroup(field.Type) { + continue + } + + w.onFailure(lint.Failure{ + Confidence: 1, + Node: field, + Failure: "sync.WaitGroup passed by value, the function will get a copy of the original one", + }) + } + + return nil +} + +func (lintWaitGroupByValueRule) isWaitGroup(ft ast.Expr) bool { + se, ok := ft.(*ast.SelectorExpr) + if !ok { + return false + } + + x, _ := se.X.(*ast.Ident) + sel := se.Sel.Name + return x.Name == "sync" && sel == "WaitGroup" +} |