Add staticcheck tool
This commit is contained in:
46
vendor/honnef.co/go/tools/stylecheck/analysis.go
vendored
Normal file
46
vendor/honnef.co/go/tools/stylecheck/analysis.go
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
// Code generated by generate.go. DO NOT EDIT.
|
||||
|
||||
package stylecheck
|
||||
|
||||
import (
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/stylecheck/st1000"
|
||||
"honnef.co/go/tools/stylecheck/st1001"
|
||||
"honnef.co/go/tools/stylecheck/st1003"
|
||||
"honnef.co/go/tools/stylecheck/st1005"
|
||||
"honnef.co/go/tools/stylecheck/st1006"
|
||||
"honnef.co/go/tools/stylecheck/st1008"
|
||||
"honnef.co/go/tools/stylecheck/st1011"
|
||||
"honnef.co/go/tools/stylecheck/st1012"
|
||||
"honnef.co/go/tools/stylecheck/st1013"
|
||||
"honnef.co/go/tools/stylecheck/st1015"
|
||||
"honnef.co/go/tools/stylecheck/st1016"
|
||||
"honnef.co/go/tools/stylecheck/st1017"
|
||||
"honnef.co/go/tools/stylecheck/st1018"
|
||||
"honnef.co/go/tools/stylecheck/st1019"
|
||||
"honnef.co/go/tools/stylecheck/st1020"
|
||||
"honnef.co/go/tools/stylecheck/st1021"
|
||||
"honnef.co/go/tools/stylecheck/st1022"
|
||||
"honnef.co/go/tools/stylecheck/st1023"
|
||||
)
|
||||
|
||||
var Analyzers = []*lint.Analyzer{
|
||||
st1000.SCAnalyzer,
|
||||
st1001.SCAnalyzer,
|
||||
st1003.SCAnalyzer,
|
||||
st1005.SCAnalyzer,
|
||||
st1006.SCAnalyzer,
|
||||
st1008.SCAnalyzer,
|
||||
st1011.SCAnalyzer,
|
||||
st1012.SCAnalyzer,
|
||||
st1013.SCAnalyzer,
|
||||
st1015.SCAnalyzer,
|
||||
st1016.SCAnalyzer,
|
||||
st1017.SCAnalyzer,
|
||||
st1018.SCAnalyzer,
|
||||
st1019.SCAnalyzer,
|
||||
st1020.SCAnalyzer,
|
||||
st1021.SCAnalyzer,
|
||||
st1022.SCAnalyzer,
|
||||
st1023.SCAnalyzer,
|
||||
}
|
||||
10
vendor/honnef.co/go/tools/stylecheck/doc.go
vendored
Normal file
10
vendor/honnef.co/go/tools/stylecheck/doc.go
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
//go:generate go run ../generate.go
|
||||
|
||||
// Package stylecheck contains analyzes that enforce style rules.
|
||||
// Most of the recommendations made are universally agreed upon by the wider Go community.
|
||||
// Some analyzes, however, implement stricter rules that not everyone will agree with.
|
||||
// In the context of Staticcheck, these analyzes are not enabled by default.
|
||||
//
|
||||
// For the most part it is recommended to follow the advice given by the analyzers that are enabled by default,
|
||||
// but you may want to disable additional analyzes on a case by case basis.
|
||||
package stylecheck
|
||||
83
vendor/honnef.co/go/tools/stylecheck/st1000/st1000.go
vendored
Normal file
83
vendor/honnef.co/go/tools/stylecheck/st1000/st1000.go
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
package st1000
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"strings"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "ST1000",
|
||||
Run: run,
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Incorrect or missing package comment`,
|
||||
Text: `Packages must have a package comment that is formatted according to
|
||||
the guidelines laid out in
|
||||
https://go.dev/wiki/CodeReviewComments#package-comments.`,
|
||||
Since: "2019.1",
|
||||
NonDefault: true,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
// - At least one file in a non-main package should have a package comment
|
||||
//
|
||||
// - The comment should be of the form
|
||||
// "Package x ...". This has a slight potential for false
|
||||
// positives, as multiple files can have package comments, in
|
||||
// which case they get appended. But that doesn't happen a lot in
|
||||
// the real world.
|
||||
|
||||
if pass.Pkg.Name() == "main" {
|
||||
return nil, nil
|
||||
}
|
||||
hasDocs := false
|
||||
for _, f := range pass.Files {
|
||||
if code.IsInTest(pass, f) {
|
||||
continue
|
||||
}
|
||||
text, ok := docText(f.Doc)
|
||||
if ok {
|
||||
hasDocs = true
|
||||
prefix := "Package " + f.Name.Name
|
||||
isNonAlpha := func(b byte) bool {
|
||||
// This check only considers ASCII, which can lead to false negatives for some malformed package
|
||||
// comments.
|
||||
return !((b >= '0' && b <= '9') || (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z'))
|
||||
}
|
||||
if !strings.HasPrefix(text, prefix) || (len(text) > len(prefix) && !isNonAlpha(text[len(prefix)])) {
|
||||
report.Report(pass, f.Doc, fmt.Sprintf(`package comment should be of the form "%s..."`, prefix))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !hasDocs {
|
||||
for _, f := range pass.Files {
|
||||
if code.IsInTest(pass, f) {
|
||||
continue
|
||||
}
|
||||
report.Report(pass, f, "at least one file in a package should have a package comment", report.ShortRange())
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func docText(doc *ast.CommentGroup) (string, bool) {
|
||||
if doc == nil {
|
||||
return "", false
|
||||
}
|
||||
// We trim spaces primarily because of /**/ style comments, which often have leading space.
|
||||
text := strings.TrimSpace(doc.Text())
|
||||
return text, text != ""
|
||||
}
|
||||
71
vendor/honnef.co/go/tools/stylecheck/st1001/st1001.go
vendored
Normal file
71
vendor/honnef.co/go/tools/stylecheck/st1001/st1001.go
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
package st1001
|
||||
|
||||
import (
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/facts/generated"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/config"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "ST1001",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{generated.Analyzer, config.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Dot imports are discouraged`,
|
||||
Text: `Dot imports that aren't in external test packages are discouraged.
|
||||
|
||||
The \'dot_import_whitelist\' option can be used to whitelist certain
|
||||
imports.
|
||||
|
||||
Quoting Go Code Review Comments:
|
||||
|
||||
> The \'import .\' form can be useful in tests that, due to circular
|
||||
> dependencies, cannot be made part of the package being tested:
|
||||
>
|
||||
> package foo_test
|
||||
>
|
||||
> import (
|
||||
> "bar/testutil" // also imports "foo"
|
||||
> . "foo"
|
||||
> )
|
||||
>
|
||||
> In this case, the test file cannot be in package foo because it
|
||||
> uses \'bar/testutil\', which imports \'foo\'. So we use the \'import .\'
|
||||
> form to let the file pretend to be part of package foo even though
|
||||
> it is not. Except for this one case, do not use \'import .\' in your
|
||||
> programs. It makes the programs much harder to read because it is
|
||||
> unclear whether a name like \'Quux\' is a top-level identifier in the
|
||||
> current package or in an imported package.`,
|
||||
Since: "2019.1",
|
||||
Options: []string{"dot_import_whitelist"},
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for _, f := range pass.Files {
|
||||
imports:
|
||||
for _, imp := range f.Imports {
|
||||
path := imp.Path.Value
|
||||
path = path[1 : len(path)-1]
|
||||
for _, w := range config.For(pass).DotImportWhitelist {
|
||||
if w == path {
|
||||
continue imports
|
||||
}
|
||||
}
|
||||
|
||||
if imp.Name != nil && imp.Name.Name == "." && !code.IsInTest(pass, f) {
|
||||
report.Report(pass, imp, "should not use dot imports", report.FilterGenerated())
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
310
vendor/honnef.co/go/tools/stylecheck/st1003/st1003.go
vendored
Normal file
310
vendor/honnef.co/go/tools/stylecheck/st1003/st1003.go
vendored
Normal file
@@ -0,0 +1,310 @@
|
||||
package st1003
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/facts/generated"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/config"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "ST1003",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer, config.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Poorly chosen identifier`,
|
||||
Text: `Identifiers, such as variable and package names, follow certain rules.
|
||||
|
||||
See the following links for details:
|
||||
|
||||
- https://go.dev/doc/effective_go#package-names
|
||||
- https://go.dev/doc/effective_go#mixed-caps
|
||||
- https://go.dev/wiki/CodeReviewComments#initialisms
|
||||
- https://go.dev/wiki/CodeReviewComments#variable-names`,
|
||||
Since: "2019.1",
|
||||
NonDefault: true,
|
||||
Options: []string{"initialisms"},
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
// knownNameExceptions is a set of names that are known to be exempt from naming checks.
|
||||
// This is usually because they are constrained by having to match names in the
|
||||
// standard library.
|
||||
var knownNameExceptions = map[string]bool{
|
||||
"LastInsertId": true, // must match database/sql
|
||||
"kWh": true,
|
||||
}
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
// A large part of this function is copied from
|
||||
// github.com/golang/lint, Copyright (c) 2013 The Go Authors,
|
||||
// licensed under the BSD 3-clause license.
|
||||
|
||||
allCaps := func(s string) bool {
|
||||
hasUppercaseLetters := false
|
||||
for _, r := range s {
|
||||
if !hasUppercaseLetters && r >= 'A' && r <= 'Z' {
|
||||
hasUppercaseLetters = true
|
||||
}
|
||||
if !((r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return hasUppercaseLetters
|
||||
}
|
||||
|
||||
check := func(id *ast.Ident, thing string, initialisms map[string]bool) {
|
||||
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 && allCaps(id.Name) && strings.Contains(id.Name, "_") {
|
||||
report.Report(pass, id, "should not use ALL_CAPS in Go names; use CamelCase instead", report.FilterGenerated())
|
||||
return
|
||||
}
|
||||
|
||||
should := lintName(id.Name, initialisms)
|
||||
if id.Name == should {
|
||||
return
|
||||
}
|
||||
|
||||
if len(id.Name) > 2 && strings.Contains(id.Name[1:len(id.Name)-1], "_") {
|
||||
report.Report(pass, id, fmt.Sprintf("should not use underscores in Go names; %s %s should be %s", thing, id.Name, should), report.FilterGenerated())
|
||||
return
|
||||
}
|
||||
report.Report(pass, id, fmt.Sprintf("%s %s should be %s", thing, id.Name, should), report.FilterGenerated())
|
||||
}
|
||||
checkList := func(fl *ast.FieldList, thing string, initialisms map[string]bool) {
|
||||
if fl == nil {
|
||||
return
|
||||
}
|
||||
for _, f := range fl.List {
|
||||
for _, id := range f.Names {
|
||||
check(id, thing, initialisms)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
il := config.For(pass).Initialisms
|
||||
initialisms := make(map[string]bool, len(il))
|
||||
for _, word := range il {
|
||||
initialisms[word] = true
|
||||
}
|
||||
for _, f := range pass.Files {
|
||||
// Package names need slightly different handling than other names.
|
||||
if !strings.HasSuffix(f.Name.Name, "_test") && strings.Contains(f.Name.Name, "_") {
|
||||
report.Report(pass, f.Name, "should not use underscores in package names", report.FilterGenerated())
|
||||
}
|
||||
if strings.IndexFunc(f.Name.Name, unicode.IsUpper) != -1 {
|
||||
report.Report(pass, f.Name, fmt.Sprintf("should not use MixedCaps in package name; %s should be %s", f.Name.Name, strings.ToLower(f.Name.Name)), report.FilterGenerated())
|
||||
}
|
||||
}
|
||||
|
||||
fn := func(node ast.Node) {
|
||||
switch v := node.(type) {
|
||||
case *ast.AssignStmt:
|
||||
if v.Tok != token.DEFINE {
|
||||
return
|
||||
}
|
||||
for _, exp := range v.Lhs {
|
||||
if id, ok := exp.(*ast.Ident); ok {
|
||||
check(id, "var", initialisms)
|
||||
}
|
||||
}
|
||||
case *ast.FuncDecl:
|
||||
// Functions with no body are defined elsewhere (in
|
||||
// assembly, or via go:linkname). These are likely to
|
||||
// be something very low level (such as the runtime),
|
||||
// where our rules don't apply.
|
||||
if v.Body == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if code.IsInTest(pass, v) &&
|
||||
(strings.HasPrefix(v.Name.Name, "Example") ||
|
||||
strings.HasPrefix(v.Name.Name, "Test") ||
|
||||
strings.HasPrefix(v.Name.Name, "Benchmark") ||
|
||||
strings.HasPrefix(v.Name.Name, "Fuzz")) {
|
||||
return
|
||||
}
|
||||
|
||||
thing := "func"
|
||||
if v.Recv != nil {
|
||||
thing = "method"
|
||||
}
|
||||
|
||||
if !isTechnicallyExported(v) {
|
||||
check(v.Name, thing, initialisms)
|
||||
}
|
||||
|
||||
checkList(v.Type.Params, thing+" parameter", initialisms)
|
||||
checkList(v.Type.Results, thing+" result", initialisms)
|
||||
case *ast.GenDecl:
|
||||
if v.Tok == token.IMPORT {
|
||||
return
|
||||
}
|
||||
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, initialisms)
|
||||
case *ast.ValueSpec:
|
||||
for _, id := range s.Names {
|
||||
check(id, thing, initialisms)
|
||||
}
|
||||
}
|
||||
}
|
||||
case *ast.InterfaceType:
|
||||
// Do not check interface method names.
|
||||
// They are often constrained 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", initialisms)
|
||||
checkList(ft.Results, "interface method result", initialisms)
|
||||
}
|
||||
case *ast.RangeStmt:
|
||||
if v.Tok == token.ASSIGN {
|
||||
return
|
||||
}
|
||||
if id, ok := v.Key.(*ast.Ident); ok {
|
||||
check(id, "range var", initialisms)
|
||||
}
|
||||
if id, ok := v.Value.(*ast.Ident); ok {
|
||||
check(id, "range var", initialisms)
|
||||
}
|
||||
case *ast.StructType:
|
||||
for _, f := range v.Fields.List {
|
||||
for _, id := range f.Names {
|
||||
check(id, "struct field", initialisms)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
needle := []ast.Node{
|
||||
(*ast.AssignStmt)(nil),
|
||||
(*ast.FuncDecl)(nil),
|
||||
(*ast.GenDecl)(nil),
|
||||
(*ast.InterfaceType)(nil),
|
||||
(*ast.RangeStmt)(nil),
|
||||
(*ast.StructType)(nil),
|
||||
}
|
||||
|
||||
code.Preorder(pass, fn, needle...)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// lintName returns a different name if it should be different.
|
||||
func lintName(name string, initialisms map[string]bool) (should string) {
|
||||
// A large part of this function is copied from
|
||||
// github.com/golang/lint, Copyright (c) 2013 The Go Authors,
|
||||
// licensed under the BSD 3-clause license.
|
||||
|
||||
// Fast path for simple cases: "_" and all lowercase.
|
||||
if name == "_" {
|
||||
return name
|
||||
}
|
||||
if strings.IndexFunc(name, func(r rune) bool { return !unicode.IsLower(r) }) == -1 {
|
||||
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] == '_' && i+1 != len(runes)-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])
|
||||
if u := strings.ToUpper(word); initialisms[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.
|
||||
// TODO(dh): this won't be true once we allow custom initialisms
|
||||
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)
|
||||
}
|
||||
|
||||
func isTechnicallyExported(f *ast.FuncDecl) bool {
|
||||
if f.Recv != nil || f.Doc == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
const export = "//export "
|
||||
const linkname = "//go:linkname "
|
||||
for _, c := range f.Doc.List {
|
||||
if strings.HasPrefix(c.Text, export) && len(c.Text) == len(export)+len(f.Name.Name) && c.Text[len(export):] == f.Name.Name {
|
||||
return true
|
||||
}
|
||||
|
||||
if strings.HasPrefix(c.Text, linkname) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
130
vendor/honnef.co/go/tools/stylecheck/st1005/st1005.go
vendored
Normal file
130
vendor/honnef.co/go/tools/stylecheck/st1005/st1005.go
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
package st1005
|
||||
|
||||
import (
|
||||
"go/constant"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/ir"
|
||||
"honnef.co/go/tools/go/ir/irutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "ST1005",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Incorrectly formatted error string`,
|
||||
Text: `Error strings follow a set of guidelines to ensure uniformity and good
|
||||
composability.
|
||||
|
||||
Quoting Go Code Review Comments:
|
||||
|
||||
> Error strings should not be capitalized (unless beginning with
|
||||
> proper nouns or acronyms) or end with punctuation, since they are
|
||||
> usually printed following other context. That is, use
|
||||
> \'fmt.Errorf("something bad")\' not \'fmt.Errorf("Something bad")\', so
|
||||
> that \'log.Printf("Reading %s: %v", filename, err)\' formats without a
|
||||
> spurious capital letter mid-message.`,
|
||||
Since: "2019.1",
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
objNames := map[*ir.Package]map[string]bool{}
|
||||
irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
|
||||
objNames[irpkg] = map[string]bool{}
|
||||
for _, m := range irpkg.Members {
|
||||
if typ, ok := m.(*ir.Type); ok {
|
||||
objNames[irpkg][typ.Name()] = true
|
||||
}
|
||||
}
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
objNames[fn.Package()][fn.Name()] = true
|
||||
}
|
||||
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
if code.IsInTest(pass, fn) {
|
||||
// We don't care about malformed error messages in tests;
|
||||
// they're usually for direct human consumption, not part
|
||||
// of an API
|
||||
continue
|
||||
}
|
||||
for _, block := range fn.Blocks {
|
||||
instrLoop:
|
||||
for _, ins := range block.Instrs {
|
||||
call, ok := ins.(*ir.Call)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if !irutil.IsCallToAny(call.Common(), "errors.New", "fmt.Errorf") {
|
||||
continue
|
||||
}
|
||||
|
||||
k, ok := call.Common().Args[0].(*ir.Const)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
s := constant.StringVal(k.Value)
|
||||
if len(s) == 0 {
|
||||
continue
|
||||
}
|
||||
switch s[len(s)-1] {
|
||||
case '.', ':', '!', '\n':
|
||||
report.Report(pass, call, "error strings should not end with punctuation or newlines")
|
||||
}
|
||||
before, _, ok0 := strings.Cut(s, " ")
|
||||
if !ok0 {
|
||||
// single word error message, probably not a real
|
||||
// error but something used in tests or during
|
||||
// debugging
|
||||
continue
|
||||
}
|
||||
word := before
|
||||
first, n := utf8.DecodeRuneInString(word)
|
||||
if !unicode.IsUpper(first) {
|
||||
continue
|
||||
}
|
||||
for _, c := range word[n:] {
|
||||
if unicode.IsUpper(c) || unicode.IsDigit(c) {
|
||||
// Word is probably an initialism or multi-word function name. Digits cover elliptic curves like
|
||||
// P384.
|
||||
continue instrLoop
|
||||
}
|
||||
}
|
||||
|
||||
if strings.ContainsRune(word, '(') {
|
||||
// Might be a function call
|
||||
continue instrLoop
|
||||
}
|
||||
word = strings.TrimRightFunc(word, func(r rune) bool { return unicode.IsPunct(r) })
|
||||
if objNames[fn.Package()][word] {
|
||||
// Word is probably the name of a function or type in this package
|
||||
continue
|
||||
}
|
||||
// First word in error starts with a capital
|
||||
// letter, and the word doesn't contain any other
|
||||
// capitals, making it unlikely to be an
|
||||
// initialism or multi-word function name.
|
||||
//
|
||||
// It could still be a proper noun, though.
|
||||
|
||||
report.Report(pass, call, "error strings should not be capitalized")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
65
vendor/honnef.co/go/tools/stylecheck/st1006/st1006.go
vendored
Normal file
65
vendor/honnef.co/go/tools/stylecheck/st1006/st1006.go
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
package st1006
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/facts/generated"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "ST1006",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer, generated.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Poorly chosen receiver name`,
|
||||
Text: `Quoting Go Code Review Comments:
|
||||
|
||||
> The name of a method's receiver should be a reflection of its
|
||||
> identity; often a one or two letter abbreviation of its type
|
||||
> suffices (such as "c" or "cl" for "Client"). Don't use generic
|
||||
> names such as "me", "this" or "self", identifiers typical of
|
||||
> object-oriented languages that place more emphasis on methods as
|
||||
> opposed to functions. The name need not be as descriptive as that
|
||||
> of a method argument, as its role is obvious and serves no
|
||||
> documentary purpose. It can be very short as it will appear on
|
||||
> almost every line of every method of the type; familiarity admits
|
||||
> brevity. Be consistent, too: if you call the receiver "c" in one
|
||||
> method, don't call it "cl" in another.`,
|
||||
Since: "2019.1",
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
|
||||
for _, m := range irpkg.Members {
|
||||
if T, ok := m.Object().(*types.TypeName); ok && !T.IsAlias() {
|
||||
ms := typeutil.IntuitiveMethodSet(T.Type(), nil)
|
||||
for _, sel := range ms {
|
||||
fn := sel.Obj().(*types.Func)
|
||||
recv := fn.Type().(*types.Signature).Recv()
|
||||
if typeutil.Dereference(recv.Type()) != T.Type() {
|
||||
// skip embedded methods
|
||||
continue
|
||||
}
|
||||
if recv.Name() == "self" || recv.Name() == "this" {
|
||||
report.Report(pass, recv, `receiver name should be a reflection of its identity; don't use generic names such as "this" or "self"`, report.FilterGenerated())
|
||||
}
|
||||
if recv.Name() == "_" {
|
||||
report.Report(pass, recv, "receiver name should not be an underscore, omit the name if it is unused", report.FilterGenerated())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
58
vendor/honnef.co/go/tools/stylecheck/st1008/st1008.go
vendored
Normal file
58
vendor/honnef.co/go/tools/stylecheck/st1008/st1008.go
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
package st1008
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "ST1008",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `A function's error value should be its last return value`,
|
||||
Text: `A function's error value should be its last return value.`,
|
||||
Since: `2019.1`,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
fnLoop:
|
||||
for _, fn := range pass.ResultOf[buildir.Analyzer].(*buildir.IR).SrcFuncs {
|
||||
sig := fn.Type().(*types.Signature)
|
||||
rets := sig.Results()
|
||||
if rets == nil || rets.Len() < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
if types.Unalias(rets.At(rets.Len()-1).Type()) == types.Universe.Lookup("error").Type() {
|
||||
// Last return type is error. If the function also returns
|
||||
// errors in other positions, that's fine.
|
||||
continue
|
||||
}
|
||||
|
||||
if rets.Len() >= 2 &&
|
||||
types.Unalias(rets.At(rets.Len()-1).Type()) == types.Universe.Lookup("bool").Type() &&
|
||||
types.Unalias(rets.At(rets.Len()-2).Type()) == types.Universe.Lookup("error").Type() {
|
||||
// Accept (..., error, bool) and assume it's a comma-ok function. It's not clear whether the bool should come last or not for these kinds of functions.
|
||||
continue
|
||||
}
|
||||
for i := rets.Len() - 2; i >= 0; i-- {
|
||||
if types.Unalias(rets.At(i).Type()) == types.Universe.Lookup("error").Type() {
|
||||
report.Report(pass, rets.At(i), "error should be returned as the last argument", report.ShortRange())
|
||||
continue fnLoop
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
88
vendor/honnef.co/go/tools/stylecheck/st1011/st1011.go
vendored
Normal file
88
vendor/honnef.co/go/tools/stylecheck/st1011/st1011.go
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
package st1011
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"strings"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "ST1011",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Poorly chosen name for variable of type \'time.Duration\'`,
|
||||
Text: `\'time.Duration\' values represent an amount of time, which is represented
|
||||
as a count of nanoseconds. An expression like \'5 * time.Microsecond\'
|
||||
yields the value \'5000\'. It is therefore not appropriate to suffix a
|
||||
variable of type \'time.Duration\' with any time unit, such as \'Msec\' or
|
||||
\'Milli\'.`,
|
||||
Since: `2019.1`,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
suffixes := []string{
|
||||
"Sec", "Secs", "Seconds",
|
||||
"Msec", "Msecs",
|
||||
"Milli", "Millis", "Milliseconds",
|
||||
"Usec", "Usecs", "Microseconds",
|
||||
"MS", "Ms",
|
||||
}
|
||||
fn := func(names []*ast.Ident) {
|
||||
for _, name := range names {
|
||||
if _, ok := pass.TypesInfo.Defs[name]; !ok {
|
||||
continue
|
||||
}
|
||||
T := pass.TypesInfo.TypeOf(name)
|
||||
if !typeutil.IsTypeWithName(T, "time.Duration") && !typeutil.IsPointerToTypeWithName(T, "time.Duration") {
|
||||
continue
|
||||
}
|
||||
for _, suffix := range suffixes {
|
||||
if strings.HasSuffix(name.Name, suffix) {
|
||||
report.Report(pass, name, fmt.Sprintf("var %s is of type %v; don't use unit-specific suffix %q", name.Name, T, suffix))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn2 := func(node ast.Node) {
|
||||
switch node := node.(type) {
|
||||
case *ast.ValueSpec:
|
||||
fn(node.Names)
|
||||
case *ast.FieldList:
|
||||
for _, field := range node.List {
|
||||
fn(field.Names)
|
||||
}
|
||||
case *ast.AssignStmt:
|
||||
if node.Tok != token.DEFINE {
|
||||
break
|
||||
}
|
||||
var names []*ast.Ident
|
||||
for _, lhs := range node.Lhs {
|
||||
if lhs, ok := lhs.(*ast.Ident); ok {
|
||||
names = append(names, lhs)
|
||||
}
|
||||
}
|
||||
fn(names)
|
||||
}
|
||||
}
|
||||
|
||||
code.Preorder(pass, fn2, (*ast.ValueSpec)(nil), (*ast.FieldList)(nil), (*ast.AssignStmt)(nil))
|
||||
return nil, nil
|
||||
}
|
||||
68
vendor/honnef.co/go/tools/stylecheck/st1012/st1012.go
vendored
Normal file
68
vendor/honnef.co/go/tools/stylecheck/st1012/st1012.go
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
package st1012
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"strings"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "ST1012",
|
||||
Run: run,
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Poorly chosen name for error variable`,
|
||||
Text: `Error variables that are part of an API should be called \'errFoo\' or
|
||||
\'ErrFoo\'.`,
|
||||
Since: "2019.1",
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for _, f := range pass.Files {
|
||||
for _, decl := range f.Decls {
|
||||
gen, ok := decl.(*ast.GenDecl)
|
||||
if !ok || gen.Tok != token.VAR {
|
||||
continue
|
||||
}
|
||||
for _, spec := range gen.Specs {
|
||||
spec := spec.(*ast.ValueSpec)
|
||||
if len(spec.Names) != len(spec.Values) {
|
||||
continue
|
||||
}
|
||||
|
||||
for i, name := range spec.Names {
|
||||
val := spec.Values[i]
|
||||
if !code.IsCallToAny(pass, val, "errors.New", "fmt.Errorf") {
|
||||
continue
|
||||
}
|
||||
|
||||
if pass.Pkg.Path() == "net/http" && strings.HasPrefix(name.Name, "http2err") {
|
||||
// special case for internal variable names of
|
||||
// bundled HTTP 2 code in net/http
|
||||
continue
|
||||
}
|
||||
prefix := "err"
|
||||
if name.IsExported() {
|
||||
prefix = "Err"
|
||||
}
|
||||
if !strings.HasPrefix(name.Name, prefix) {
|
||||
report.Report(pass, name, fmt.Sprintf("error var %s should have name of the form %sFoo", name.Name, prefix))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
159
vendor/honnef.co/go/tools/stylecheck/st1013/st1013.go
vendored
Normal file
159
vendor/honnef.co/go/tools/stylecheck/st1013/st1013.go
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
package st1013
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"strconv"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/edit"
|
||||
"honnef.co/go/tools/analysis/facts/generated"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/config"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "ST1013",
|
||||
Run: run,
|
||||
Requires: append([]*analysis.Analyzer{generated.Analyzer, config.Analyzer}, code.RequiredAnalyzers...),
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Should use constants for HTTP error codes, not magic numbers`,
|
||||
Text: `HTTP has a tremendous number of status codes. While some of those are
|
||||
well known (200, 400, 404, 500), most of them are not. The \'net/http\'
|
||||
package provides constants for all status codes that are part of the
|
||||
various specifications. It is recommended to use these constants
|
||||
instead of hard-coding magic numbers, to vastly improve the
|
||||
readability of your code.`,
|
||||
Since: "2019.1",
|
||||
Options: []string{"http_status_code_whitelist"},
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var query = pattern.MustParse(`
|
||||
(CallExpr
|
||||
(Symbol
|
||||
name@(Or
|
||||
"net/http.Error"
|
||||
"net/http.Redirect"
|
||||
"net/http.StatusText"
|
||||
"net/http.RedirectHandler"))
|
||||
args)`)
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
whitelist := map[string]bool{}
|
||||
for _, code := range config.For(pass).HTTPStatusCodeWhitelist {
|
||||
whitelist[code] = true
|
||||
}
|
||||
for _, m := range code.Matches(pass, query) {
|
||||
var arg int
|
||||
switch m.State["name"].(string) {
|
||||
case "net/http.Error":
|
||||
arg = 2
|
||||
case "net/http.Redirect":
|
||||
arg = 3
|
||||
case "net/http.StatusText":
|
||||
arg = 0
|
||||
case "net/http.RedirectHandler":
|
||||
arg = 1
|
||||
default:
|
||||
continue
|
||||
}
|
||||
args := m.State["args"].([]ast.Expr)
|
||||
if arg >= len(args) {
|
||||
continue
|
||||
}
|
||||
tv, ok := code.IntegerLiteral(pass, args[arg])
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
n, ok := constant.Int64Val(tv.Value)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if whitelist[strconv.FormatInt(n, 10)] {
|
||||
continue
|
||||
}
|
||||
|
||||
s, ok := httpStatusCodes[n]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
lit := args[arg]
|
||||
report.Report(pass, lit, fmt.Sprintf("should use constant http.%s instead of numeric literal %d", s, n),
|
||||
report.FilterGenerated(),
|
||||
report.Fixes(edit.Fix(fmt.Sprintf("Use http.%s instead of %d", s, n), edit.ReplaceWithString(lit, "http."+s))))
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var httpStatusCodes = map[int64]string{
|
||||
100: "StatusContinue",
|
||||
101: "StatusSwitchingProtocols",
|
||||
102: "StatusProcessing",
|
||||
200: "StatusOK",
|
||||
201: "StatusCreated",
|
||||
202: "StatusAccepted",
|
||||
203: "StatusNonAuthoritativeInfo",
|
||||
204: "StatusNoContent",
|
||||
205: "StatusResetContent",
|
||||
206: "StatusPartialContent",
|
||||
207: "StatusMultiStatus",
|
||||
208: "StatusAlreadyReported",
|
||||
226: "StatusIMUsed",
|
||||
300: "StatusMultipleChoices",
|
||||
301: "StatusMovedPermanently",
|
||||
302: "StatusFound",
|
||||
303: "StatusSeeOther",
|
||||
304: "StatusNotModified",
|
||||
305: "StatusUseProxy",
|
||||
307: "StatusTemporaryRedirect",
|
||||
308: "StatusPermanentRedirect",
|
||||
400: "StatusBadRequest",
|
||||
401: "StatusUnauthorized",
|
||||
402: "StatusPaymentRequired",
|
||||
403: "StatusForbidden",
|
||||
404: "StatusNotFound",
|
||||
405: "StatusMethodNotAllowed",
|
||||
406: "StatusNotAcceptable",
|
||||
407: "StatusProxyAuthRequired",
|
||||
408: "StatusRequestTimeout",
|
||||
409: "StatusConflict",
|
||||
410: "StatusGone",
|
||||
411: "StatusLengthRequired",
|
||||
412: "StatusPreconditionFailed",
|
||||
413: "StatusRequestEntityTooLarge",
|
||||
414: "StatusRequestURITooLong",
|
||||
415: "StatusUnsupportedMediaType",
|
||||
416: "StatusRequestedRangeNotSatisfiable",
|
||||
417: "StatusExpectationFailed",
|
||||
418: "StatusTeapot",
|
||||
422: "StatusUnprocessableEntity",
|
||||
423: "StatusLocked",
|
||||
424: "StatusFailedDependency",
|
||||
426: "StatusUpgradeRequired",
|
||||
428: "StatusPreconditionRequired",
|
||||
429: "StatusTooManyRequests",
|
||||
431: "StatusRequestHeaderFieldsTooLarge",
|
||||
451: "StatusUnavailableForLegalReasons",
|
||||
500: "StatusInternalServerError",
|
||||
501: "StatusNotImplemented",
|
||||
502: "StatusBadGateway",
|
||||
503: "StatusServiceUnavailable",
|
||||
504: "StatusGatewayTimeout",
|
||||
505: "StatusHTTPVersionNotSupported",
|
||||
506: "StatusVariantAlsoNegotiates",
|
||||
507: "StatusInsufficientStorage",
|
||||
508: "StatusLoopDetected",
|
||||
510: "StatusNotExtended",
|
||||
511: "StatusNetworkAuthenticationRequired",
|
||||
}
|
||||
77
vendor/honnef.co/go/tools/stylecheck/st1015/st1015.go
vendored
Normal file
77
vendor/honnef.co/go/tools/stylecheck/st1015/st1015.go
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
package st1015
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/facts/generated"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "ST1015",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `A switch's default case should be the first or last case`,
|
||||
Since: "2019.1",
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
hasFallthrough := func(clause ast.Stmt) bool {
|
||||
// A valid fallthrough statement may be used only as the final non-empty statement in a case clause. Thus we can
|
||||
// easily avoid falsely matching fallthroughs in nested switches by not descending into blocks.
|
||||
|
||||
body := clause.(*ast.CaseClause).Body
|
||||
for i := len(body) - 1; i >= 0; i-- {
|
||||
last := body[i]
|
||||
switch stmt := last.(type) {
|
||||
case *ast.EmptyStmt:
|
||||
// Fallthrough may be followed by empty statements
|
||||
case *ast.BranchStmt:
|
||||
return stmt.Tok == token.FALLTHROUGH
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fn := func(node ast.Node) {
|
||||
stmt := node.(*ast.SwitchStmt)
|
||||
list := stmt.Body.List
|
||||
defaultIdx := -1
|
||||
for i, c := range list {
|
||||
if c.(*ast.CaseClause).List == nil {
|
||||
defaultIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if defaultIdx == -1 || defaultIdx == 0 || defaultIdx == len(list)-1 {
|
||||
// No default case, or it's the first or last case
|
||||
return
|
||||
}
|
||||
|
||||
if hasFallthrough(list[defaultIdx-1]) || hasFallthrough(list[defaultIdx]) {
|
||||
// We either fall into or out of this case; don't mess with the order
|
||||
return
|
||||
}
|
||||
|
||||
report.Report(pass, list[defaultIdx], "default case should be first or last in switch statement", report.FilterGenerated())
|
||||
}
|
||||
code.Preorder(pass, fn, (*ast.SwitchStmt)(nil))
|
||||
return nil, nil
|
||||
}
|
||||
74
vendor/honnef.co/go/tools/stylecheck/st1016/st1016.go
vendored
Normal file
74
vendor/honnef.co/go/tools/stylecheck/st1016/st1016.go
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
package st1016
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/facts/generated"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/go/types/typeutil"
|
||||
"honnef.co/go/tools/internal/passes/buildir"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "ST1016",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{buildir.Analyzer, generated.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Use consistent method receiver names`,
|
||||
Since: "2019.1",
|
||||
NonDefault: true,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
irpkg := pass.ResultOf[buildir.Analyzer].(*buildir.IR).Pkg
|
||||
for _, m := range irpkg.Members {
|
||||
names := map[string]int{}
|
||||
|
||||
var firstFn *types.Func
|
||||
if T, ok := m.Object().(*types.TypeName); ok && !T.IsAlias() {
|
||||
ms := typeutil.IntuitiveMethodSet(T.Type(), nil)
|
||||
for _, sel := range ms {
|
||||
fn := sel.Obj().(*types.Func)
|
||||
recv := fn.Type().(*types.Signature).Recv()
|
||||
if code.IsGenerated(pass, recv.Pos()) {
|
||||
// Don't concern ourselves with methods in generated code
|
||||
continue
|
||||
}
|
||||
if typeutil.Dereference(recv.Type()) != T.Type() {
|
||||
// skip embedded methods
|
||||
continue
|
||||
}
|
||||
if firstFn == nil {
|
||||
firstFn = fn
|
||||
}
|
||||
if recv.Name() != "" && recv.Name() != "_" {
|
||||
names[recv.Name()]++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(names) > 1 {
|
||||
var seen []string
|
||||
for name, count := range names {
|
||||
seen = append(seen, fmt.Sprintf("%dx %q", count, name))
|
||||
}
|
||||
sort.Strings(seen)
|
||||
|
||||
report.Report(pass, firstFn, fmt.Sprintf("methods on the same type should have the same receiver name (seen %s)", strings.Join(seen, ", ")))
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
47
vendor/honnef.co/go/tools/stylecheck/st1017/st1017.go
vendored
Normal file
47
vendor/honnef.co/go/tools/stylecheck/st1017/st1017.go
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
package st1017
|
||||
|
||||
import (
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/edit"
|
||||
"honnef.co/go/tools/analysis/facts/generated"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
"honnef.co/go/tools/pattern"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "ST1017",
|
||||
Run: run,
|
||||
Requires: append([]*analysis.Analyzer{generated.Analyzer}, code.RequiredAnalyzers...),
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Don't use Yoda conditions`,
|
||||
Text: `Yoda conditions are conditions of the kind \"if 42 == x\", where the
|
||||
literal is on the left side of the comparison. These are a common
|
||||
idiom in languages in which assignment is an expression, to avoid bugs
|
||||
of the kind \"if (x = 42)\". In Go, which doesn't allow for this kind of
|
||||
bug, we prefer the more idiomatic \"if x == 42\".`,
|
||||
Since: "2019.2",
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
var (
|
||||
checkYodaConditionsQ = pattern.MustParse(`(BinaryExpr left@(TrulyConstantExpression _) tok@(Or "==" "!=") right@(Not (TrulyConstantExpression _)))`)
|
||||
checkYodaConditionsR = pattern.MustParse(`(BinaryExpr right tok left)`)
|
||||
)
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for node, m := range code.Matches(pass, checkYodaConditionsQ) {
|
||||
edits := code.EditMatch(pass, node, m, checkYodaConditionsR)
|
||||
report.Report(pass, node, "don't use Yoda conditions",
|
||||
report.FilterGenerated(),
|
||||
report.Fixes(edit.Fix("Un-Yoda-fy", edits...)))
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
157
vendor/honnef.co/go/tools/stylecheck/st1018/st1018.go
vendored
Normal file
157
vendor/honnef.co/go/tools/stylecheck/st1018/st1018.go
vendored
Normal file
@@ -0,0 +1,157 @@
|
||||
package st1018
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"strconv"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "ST1018",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Avoid zero-width and control characters in string literals`,
|
||||
Since: "2019.2",
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
fn := func(node ast.Node) {
|
||||
lit := node.(*ast.BasicLit)
|
||||
if lit.Kind != token.STRING {
|
||||
return
|
||||
}
|
||||
|
||||
type invalid struct {
|
||||
r rune
|
||||
off int
|
||||
}
|
||||
var invalids []invalid
|
||||
hasFormat := false
|
||||
hasControl := false
|
||||
prev := rune(-1)
|
||||
const zwj = '\u200d'
|
||||
for off, r := range lit.Value {
|
||||
if unicode.Is(unicode.Cf, r) {
|
||||
if r >= '\U000e0020' && r <= '\U000e007f' {
|
||||
// These are used for spelling out country codes for flag emoji
|
||||
} else if unicode.Is(unicode.Variation_Selector, r) {
|
||||
// Always allow variation selectors
|
||||
} else if r == zwj && (unicode.Is(unicode.S, prev) || unicode.Is(unicode.Variation_Selector, prev)) {
|
||||
// Allow zero-width joiner in emoji, including those that use variation selectors.
|
||||
|
||||
// Technically some foreign scripts make valid use of zero-width joiners, too, but for now we'll err
|
||||
// on the side of flagging all non-emoji uses of ZWJ.
|
||||
} else {
|
||||
switch r {
|
||||
case '\u0600', '\u0601', '\u0602', '\u0603', '\u0604', '\u0605', '\u0890', '\u0891', '\u08e2':
|
||||
// Arabic characters that are not actually invisible. If anyone knows why these are in the
|
||||
// Other, Format category please let me know.
|
||||
case '\u061c', '\u202A', '\u202B', '\u202D', '\u202E', '\u2066', '\u2067', '\u2068', '\u202C', '\u2069':
|
||||
// Bidirectional formatting characters. At best they will render confusingly, at worst they're used
|
||||
// to cause confusion.
|
||||
fallthrough
|
||||
default:
|
||||
invalids = append(invalids, invalid{r, off})
|
||||
hasFormat = true
|
||||
}
|
||||
}
|
||||
} else if unicode.Is(unicode.Cc, r) && r != '\n' && r != '\t' && r != '\r' {
|
||||
invalids = append(invalids, invalid{r, off})
|
||||
hasControl = true
|
||||
}
|
||||
prev = r
|
||||
}
|
||||
|
||||
switch len(invalids) {
|
||||
case 0:
|
||||
return
|
||||
case 1:
|
||||
var kind string
|
||||
if hasFormat {
|
||||
kind = "format"
|
||||
} else if hasControl {
|
||||
kind = "control"
|
||||
} else {
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
r := invalids[0]
|
||||
msg := fmt.Sprintf("string literal contains the Unicode %s character %U, consider using the %q escape sequence instead", kind, r.r, r.r)
|
||||
|
||||
replacement := strconv.QuoteRune(r.r)
|
||||
replacement = replacement[1 : len(replacement)-1]
|
||||
edit := analysis.SuggestedFix{
|
||||
Message: fmt.Sprintf("replace %s character %U with %q", kind, r.r, r.r),
|
||||
TextEdits: []analysis.TextEdit{{
|
||||
Pos: lit.Pos() + token.Pos(r.off),
|
||||
End: lit.Pos() + token.Pos(r.off) + token.Pos(utf8.RuneLen(r.r)),
|
||||
NewText: []byte(replacement),
|
||||
}},
|
||||
}
|
||||
delete := analysis.SuggestedFix{
|
||||
Message: fmt.Sprintf("delete %s character %U", kind, r.r),
|
||||
TextEdits: []analysis.TextEdit{{
|
||||
Pos: lit.Pos() + token.Pos(r.off),
|
||||
End: lit.Pos() + token.Pos(r.off) + token.Pos(utf8.RuneLen(r.r)),
|
||||
}},
|
||||
}
|
||||
report.Report(pass, lit, msg, report.Fixes(edit, delete))
|
||||
default:
|
||||
var kind string
|
||||
if hasFormat && hasControl {
|
||||
kind = "format and control"
|
||||
} else if hasFormat {
|
||||
kind = "format"
|
||||
} else if hasControl {
|
||||
kind = "control"
|
||||
} else {
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("string literal contains Unicode %s characters, consider using escape sequences instead", kind)
|
||||
var edits []analysis.TextEdit
|
||||
var deletions []analysis.TextEdit
|
||||
for _, r := range invalids {
|
||||
replacement := strconv.QuoteRune(r.r)
|
||||
replacement = replacement[1 : len(replacement)-1]
|
||||
edits = append(edits, analysis.TextEdit{
|
||||
Pos: lit.Pos() + token.Pos(r.off),
|
||||
End: lit.Pos() + token.Pos(r.off) + token.Pos(utf8.RuneLen(r.r)),
|
||||
NewText: []byte(replacement),
|
||||
})
|
||||
deletions = append(deletions, analysis.TextEdit{
|
||||
Pos: lit.Pos() + token.Pos(r.off),
|
||||
End: lit.Pos() + token.Pos(r.off) + token.Pos(utf8.RuneLen(r.r)),
|
||||
})
|
||||
}
|
||||
edit := analysis.SuggestedFix{
|
||||
Message: fmt.Sprintf("replace all %s characters with escape sequences", kind),
|
||||
TextEdits: edits,
|
||||
}
|
||||
delete := analysis.SuggestedFix{
|
||||
Message: fmt.Sprintf("delete all %s characters", kind),
|
||||
TextEdits: deletions,
|
||||
}
|
||||
report.Report(pass, lit, msg, report.Fixes(edit, delete))
|
||||
}
|
||||
}
|
||||
code.Preorder(pass, fn, (*ast.BasicLit)(nil))
|
||||
return nil, nil
|
||||
}
|
||||
87
vendor/honnef.co/go/tools/stylecheck/st1019/st1019.go
vendored
Normal file
87
vendor/honnef.co/go/tools/stylecheck/st1019/st1019.go
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
package st1019
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
|
||||
"honnef.co/go/tools/analysis/facts/generated"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "ST1019",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{generated.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: `Importing the same package multiple times`,
|
||||
Text: `Go allows importing the same package multiple times, as long as
|
||||
different import aliases are being used. That is, the following
|
||||
bit of code is valid:
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
fumpt "fmt"
|
||||
format "fmt"
|
||||
)
|
||||
|
||||
However, this is very rarely done on purpose. Usually, it is a
|
||||
sign of code that got refactored, accidentally adding duplicate
|
||||
import statements. It is also a rarely known feature, which may
|
||||
contribute to confusion.
|
||||
|
||||
Do note that sometimes, this feature may be used
|
||||
intentionally (see for example
|
||||
https://github.com/golang/go/commit/3409ce39bfd7584523b7a8c150a310cea92d879d)
|
||||
– if you want to allow this pattern in your code base, you're
|
||||
advised to disable this check.
|
||||
|
||||
It is acceptable to import the same package twice if one of the imports
|
||||
uses the blank identifier. This is allowed in order to increase
|
||||
resilience against erroneous changes when using the same package for its
|
||||
side effects as well as its exported API.`,
|
||||
Since: "2020.1",
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
for _, f := range pass.Files {
|
||||
// Collect all imports by their import path
|
||||
imports := make(map[string][]*ast.ImportSpec, len(f.Imports))
|
||||
for _, imp := range f.Imports {
|
||||
if imp.Name != nil && imp.Name.Name == "_" {
|
||||
// Allow blank imports to coexist with one normal import.
|
||||
//
|
||||
// We don't have to count the number of blank imports,
|
||||
// goimports removes duplicates.
|
||||
continue
|
||||
}
|
||||
imports[imp.Path.Value] = append(imports[imp.Path.Value], imp)
|
||||
}
|
||||
|
||||
for path, value := range imports {
|
||||
if path[1:len(path)-1] == "unsafe" {
|
||||
// Don't flag unsafe. Cgo generated code imports
|
||||
// unsafe as _cgo_unsafe, in addition to the user's import.
|
||||
continue
|
||||
}
|
||||
// If there's more than one import per path, we flag that
|
||||
if len(value) > 1 {
|
||||
s := fmt.Sprintf("package %s is being imported more than once", path)
|
||||
opts := []report.Option{report.FilterGenerated()}
|
||||
for _, imp := range value[1:] {
|
||||
opts = append(opts, report.Related(imp, fmt.Sprintf("other import of %s", path)))
|
||||
}
|
||||
report.Report(pass, value[0], s, opts...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
101
vendor/honnef.co/go/tools/stylecheck/st1020/st1020.go
vendored
Normal file
101
vendor/honnef.co/go/tools/stylecheck/st1020/st1020.go
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
package st1020
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"strings"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/facts/generated"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "ST1020",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{generated.Analyzer, inspect.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: "The documentation of an exported function should start with the function's name",
|
||||
Text: `Doc comments work best as complete sentences, which
|
||||
allow a wide variety of automated presentations. The first sentence
|
||||
should be a one-sentence summary that starts with the name being
|
||||
declared.
|
||||
|
||||
If every doc comment begins with the name of the item it describes,
|
||||
you can use the \'doc\' subcommand of the \'go\' tool and run the output
|
||||
through grep.
|
||||
|
||||
See https://go.dev/doc/effective_go#commentary for more
|
||||
information on how to write good documentation.`,
|
||||
Since: "2020.1",
|
||||
NonDefault: true,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
fn := func(node ast.Node) {
|
||||
if code.IsInTest(pass, node) {
|
||||
return
|
||||
}
|
||||
|
||||
decl := node.(*ast.FuncDecl)
|
||||
text, ok := docText(decl.Doc)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if !ast.IsExported(decl.Name.Name) {
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(text, "Deprecated: ") {
|
||||
return
|
||||
}
|
||||
|
||||
kind := "function"
|
||||
if decl.Recv != nil {
|
||||
kind = "method"
|
||||
var ident *ast.Ident
|
||||
T := decl.Recv.List[0].Type
|
||||
if T_, ok := T.(*ast.StarExpr); ok {
|
||||
T = T_.X
|
||||
}
|
||||
switch T := T.(type) {
|
||||
case *ast.IndexExpr:
|
||||
ident = T.X.(*ast.Ident)
|
||||
case *ast.IndexListExpr:
|
||||
ident = T.X.(*ast.Ident)
|
||||
case *ast.Ident:
|
||||
ident = T
|
||||
default:
|
||||
lint.ExhaustiveTypeSwitch(T)
|
||||
}
|
||||
if !ast.IsExported(ident.Name) {
|
||||
return
|
||||
}
|
||||
}
|
||||
prefix := decl.Name.Name + " "
|
||||
if !strings.HasPrefix(text, prefix) {
|
||||
report.Report(pass, decl.Doc, fmt.Sprintf(`comment on exported %s %s should be of the form "%s..."`, kind, decl.Name.Name, prefix), report.FilterGenerated())
|
||||
}
|
||||
}
|
||||
|
||||
code.Preorder(pass, fn, (*ast.FuncDecl)(nil))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func docText(doc *ast.CommentGroup) (string, bool) {
|
||||
if doc == nil {
|
||||
return "", false
|
||||
}
|
||||
// We trim spaces primarily because of /**/ style comments, which often have leading space.
|
||||
text := strings.TrimSpace(doc.Text())
|
||||
return text, text != ""
|
||||
}
|
||||
124
vendor/honnef.co/go/tools/stylecheck/st1021/st1021.go
vendored
Normal file
124
vendor/honnef.co/go/tools/stylecheck/st1021/st1021.go
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
package st1021
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"strings"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/facts/generated"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
"golang.org/x/tools/go/ast/inspector"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "ST1021",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{generated.Analyzer, inspect.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: "The documentation of an exported type should start with type's name",
|
||||
Text: `Doc comments work best as complete sentences, which
|
||||
allow a wide variety of automated presentations. The first sentence
|
||||
should be a one-sentence summary that starts with the name being
|
||||
declared.
|
||||
|
||||
If every doc comment begins with the name of the item it describes,
|
||||
you can use the \'doc\' subcommand of the \'go\' tool and run the output
|
||||
through grep.
|
||||
|
||||
See https://go.dev/doc/effective_go#commentary for more
|
||||
information on how to write good documentation.`,
|
||||
Since: "2020.1",
|
||||
NonDefault: true,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
var genDecl *ast.GenDecl
|
||||
fn := func(node ast.Node, push bool) bool {
|
||||
if !push {
|
||||
genDecl = nil
|
||||
return false
|
||||
}
|
||||
if code.IsInTest(pass, node) {
|
||||
return false
|
||||
}
|
||||
|
||||
switch node := node.(type) {
|
||||
case *ast.GenDecl:
|
||||
if node.Tok == token.IMPORT {
|
||||
return false
|
||||
}
|
||||
genDecl = node
|
||||
return true
|
||||
case *ast.TypeSpec:
|
||||
if !ast.IsExported(node.Name.Name) {
|
||||
return false
|
||||
}
|
||||
|
||||
doc := node.Doc
|
||||
text, ok := docText(doc)
|
||||
if !ok {
|
||||
if len(genDecl.Specs) != 1 {
|
||||
// more than one spec in the GenDecl, don't validate the
|
||||
// docstring
|
||||
return false
|
||||
}
|
||||
if genDecl.Lparen.IsValid() {
|
||||
// 'type ( T )' is weird, don't guess the user's intention
|
||||
return false
|
||||
}
|
||||
doc = genDecl.Doc
|
||||
text, ok = docText(doc)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check comment before we strip articles in case the type's name is an article.
|
||||
if strings.HasPrefix(text, node.Name.Name+" ") {
|
||||
return false
|
||||
}
|
||||
|
||||
s := text
|
||||
articles := [...]string{"A", "An", "The"}
|
||||
for _, a := range articles {
|
||||
if strings.HasPrefix(s, a+" ") {
|
||||
s = s[len(a)+1:]
|
||||
break
|
||||
}
|
||||
}
|
||||
if !strings.HasPrefix(s, node.Name.Name+" ") {
|
||||
report.Report(pass, doc, fmt.Sprintf(`comment on exported type %s should be of the form "%s ..." (with optional leading article)`, node.Name.Name, node.Name.Name), report.FilterGenerated())
|
||||
}
|
||||
return false
|
||||
case *ast.FuncLit, *ast.FuncDecl:
|
||||
return false
|
||||
default:
|
||||
lint.ExhaustiveTypeSwitch(node)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes([]ast.Node{(*ast.GenDecl)(nil), (*ast.TypeSpec)(nil), (*ast.FuncLit)(nil), (*ast.FuncDecl)(nil)}, fn)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func docText(doc *ast.CommentGroup) (string, bool) {
|
||||
if doc == nil {
|
||||
return "", false
|
||||
}
|
||||
// We trim spaces primarily because of /**/ style comments, which often have leading space.
|
||||
text := strings.TrimSpace(doc.Text())
|
||||
return text, text != ""
|
||||
}
|
||||
105
vendor/honnef.co/go/tools/stylecheck/st1022/st1022.go
vendored
Normal file
105
vendor/honnef.co/go/tools/stylecheck/st1022/st1022.go
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
package st1022
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"strings"
|
||||
|
||||
"honnef.co/go/tools/analysis/code"
|
||||
"honnef.co/go/tools/analysis/facts/generated"
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/analysis/report"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/passes/inspect"
|
||||
"golang.org/x/tools/go/ast/inspector"
|
||||
)
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: &analysis.Analyzer{
|
||||
Name: "ST1022",
|
||||
Run: run,
|
||||
Requires: []*analysis.Analyzer{generated.Analyzer, inspect.Analyzer},
|
||||
},
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: "The documentation of an exported variable or constant should start with variable's name",
|
||||
Text: `Doc comments work best as complete sentences, which
|
||||
allow a wide variety of automated presentations. The first sentence
|
||||
should be a one-sentence summary that starts with the name being
|
||||
declared.
|
||||
|
||||
If every doc comment begins with the name of the item it describes,
|
||||
you can use the \'doc\' subcommand of the \'go\' tool and run the output
|
||||
through grep.
|
||||
|
||||
See https://go.dev/doc/effective_go#commentary for more
|
||||
information on how to write good documentation.`,
|
||||
Since: "2020.1",
|
||||
NonDefault: true,
|
||||
MergeIf: lint.MergeIfAny,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
|
||||
func run(pass *analysis.Pass) (any, error) {
|
||||
var genDecl *ast.GenDecl
|
||||
fn := func(node ast.Node, push bool) bool {
|
||||
if !push {
|
||||
genDecl = nil
|
||||
return false
|
||||
}
|
||||
if code.IsInTest(pass, node) {
|
||||
return false
|
||||
}
|
||||
|
||||
switch node := node.(type) {
|
||||
case *ast.GenDecl:
|
||||
if node.Tok == token.IMPORT {
|
||||
return false
|
||||
}
|
||||
genDecl = node
|
||||
return true
|
||||
case *ast.ValueSpec:
|
||||
if genDecl.Lparen.IsValid() || len(node.Names) > 1 {
|
||||
// Don't try to guess the user's intention
|
||||
return false
|
||||
}
|
||||
name := node.Names[0].Name
|
||||
if !ast.IsExported(name) {
|
||||
return false
|
||||
}
|
||||
text, ok := docText(genDecl.Doc)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
prefix := name + " "
|
||||
if !strings.HasPrefix(text, prefix) {
|
||||
kind := "var"
|
||||
if genDecl.Tok == token.CONST {
|
||||
kind = "const"
|
||||
}
|
||||
report.Report(pass, genDecl.Doc, fmt.Sprintf(`comment on exported %s %s should be of the form "%s..."`, kind, name, prefix), report.FilterGenerated())
|
||||
}
|
||||
return false
|
||||
case *ast.FuncLit, *ast.FuncDecl:
|
||||
return false
|
||||
default:
|
||||
lint.ExhaustiveTypeSwitch(node)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes([]ast.Node{(*ast.GenDecl)(nil), (*ast.ValueSpec)(nil), (*ast.FuncLit)(nil), (*ast.FuncDecl)(nil)}, fn)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func docText(doc *ast.CommentGroup) (string, bool) {
|
||||
if doc == nil {
|
||||
return "", false
|
||||
}
|
||||
// We trim spaces primarily because of /**/ style comments, which often have leading space.
|
||||
text := strings.TrimSpace(doc.Text())
|
||||
return text, text != ""
|
||||
}
|
||||
22
vendor/honnef.co/go/tools/stylecheck/st1023/st1023.go
vendored
Normal file
22
vendor/honnef.co/go/tools/stylecheck/st1023/st1023.go
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
package st1023
|
||||
|
||||
import (
|
||||
"honnef.co/go/tools/analysis/lint"
|
||||
"honnef.co/go/tools/internal/sharedcheck"
|
||||
)
|
||||
|
||||
func init() {
|
||||
SCAnalyzer.Analyzer.Name = "ST1023"
|
||||
}
|
||||
|
||||
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
|
||||
Analyzer: sharedcheck.RedundantTypeInDeclarationChecker("should", false),
|
||||
Doc: &lint.RawDocumentation{
|
||||
Title: "Redundant type in variable declaration",
|
||||
Since: "2021.1",
|
||||
NonDefault: true,
|
||||
MergeIf: lint.MergeIfAll,
|
||||
},
|
||||
})
|
||||
|
||||
var Analyzer = SCAnalyzer.Analyzer
|
||||
Reference in New Issue
Block a user