kilo/vendor/honnef.co/go/tools/analysis/lint/lint.go
Lucas Servén Marín 50fbc2eec2
staticcheck (#313)
* CI: use staticcheck for linting

This commit switches the linter for Go code from golint to staticcheck.
Golint has been deprecated since last year and staticcheck is a
recommended replacement.

Signed-off-by: Lucas Servén Marín <lserven@gmail.com>

* revendor

Signed-off-by: Lucas Servén Marín <lserven@gmail.com>

* cmd,pkg: fix lint warnings

Signed-off-by: Lucas Servén Marín <lserven@gmail.com>
2022-05-19 19:45:43 +02:00

284 lines
6.5 KiB
Go

// Package lint provides abstractions on top of go/analysis.
// These abstractions add extra information to analyzes, such as structured documentation and severities.
package lint
import (
"flag"
"fmt"
"go/ast"
"go/build"
"go/token"
"strconv"
"strings"
"golang.org/x/tools/go/analysis"
)
// Analyzer wraps a go/analysis.Analyzer and provides structured documentation.
type Analyzer struct {
// The analyzer's documentation. Unlike go/analysis.Analyzer.Doc,
// this field is structured, providing access to severity, options
// etc.
Doc *Documentation
Analyzer *analysis.Analyzer
}
func (a *Analyzer) initialize() {
a.Analyzer.Doc = a.Doc.String()
if a.Analyzer.Flags.Usage == nil {
fs := flag.NewFlagSet("", flag.PanicOnError)
fs.Var(newVersionFlag(), "go", "Target Go version")
a.Analyzer.Flags = *fs
}
}
// InitializeAnalyzers takes a map of documentation and a map of go/analysis.Analyzers and returns a slice of Analyzers.
// The map keys are the analyzer names.
func InitializeAnalyzers(docs map[string]*Documentation, analyzers map[string]*analysis.Analyzer) []*Analyzer {
out := make([]*Analyzer, 0, len(analyzers))
for k, v := range analyzers {
v.Name = k
a := &Analyzer{
Doc: docs[k],
Analyzer: v,
}
a.initialize()
out = append(out, a)
}
return out
}
// Severity describes the severity of diagnostics reported by an analyzer.
type Severity int
const (
SeverityNone Severity = iota
SeverityError
SeverityDeprecated
SeverityWarning
SeverityInfo
SeverityHint
)
// MergeStrategy sets how merge mode should behave for diagnostics of an analyzer.
type MergeStrategy int
const (
MergeIfAny MergeStrategy = iota
MergeIfAll
)
type RawDocumentation struct {
Title string
Text string
Before string
After string
Since string
NonDefault bool
Options []string
Severity Severity
MergeIf MergeStrategy
}
type Documentation struct {
Title string
Text string
TitleMarkdown string
TextMarkdown string
Before string
After string
Since string
NonDefault bool
Options []string
Severity Severity
MergeIf MergeStrategy
}
func Markdownify(m map[string]*RawDocumentation) map[string]*Documentation {
out := make(map[string]*Documentation, len(m))
for k, v := range m {
out[k] = &Documentation{
Title: strings.TrimSpace(stripMarkdown(v.Title)),
Text: strings.TrimSpace(stripMarkdown(v.Text)),
TitleMarkdown: strings.TrimSpace(toMarkdown(v.Title)),
TextMarkdown: strings.TrimSpace(toMarkdown(v.Text)),
Before: strings.TrimSpace(v.Before),
After: strings.TrimSpace(v.After),
Since: v.Since,
NonDefault: v.NonDefault,
Options: v.Options,
Severity: v.Severity,
MergeIf: v.MergeIf,
}
}
return out
}
func toMarkdown(s string) string {
return strings.NewReplacer(`\'`, "`", `\"`, "`").Replace(s)
}
func stripMarkdown(s string) string {
return strings.NewReplacer(`\'`, "", `\"`, "'").Replace(s)
}
func (doc *Documentation) Format(metadata bool) string {
return doc.format(false, metadata)
}
func (doc *Documentation) FormatMarkdown(metadata bool) string {
return doc.format(true, metadata)
}
func (doc *Documentation) format(markdown bool, metadata bool) string {
b := &strings.Builder{}
if markdown {
fmt.Fprintf(b, "%s\n\n", doc.TitleMarkdown)
if doc.Text != "" {
fmt.Fprintf(b, "%s\n\n", doc.TextMarkdown)
}
} else {
fmt.Fprintf(b, "%s\n\n", doc.Title)
if doc.Text != "" {
fmt.Fprintf(b, "%s\n\n", doc.Text)
}
}
if doc.Before != "" {
fmt.Fprintln(b, "Before:")
fmt.Fprintln(b, "")
for _, line := range strings.Split(doc.Before, "\n") {
fmt.Fprint(b, " ", line, "\n")
}
fmt.Fprintln(b, "")
fmt.Fprintln(b, "After:")
fmt.Fprintln(b, "")
for _, line := range strings.Split(doc.After, "\n") {
fmt.Fprint(b, " ", line, "\n")
}
fmt.Fprintln(b, "")
}
if metadata {
fmt.Fprint(b, "Available since\n ")
if doc.Since == "" {
fmt.Fprint(b, "unreleased")
} else {
fmt.Fprintf(b, "%s", doc.Since)
}
if doc.NonDefault {
fmt.Fprint(b, ", non-default")
}
fmt.Fprint(b, "\n")
if len(doc.Options) > 0 {
fmt.Fprintf(b, "\nOptions\n")
for _, opt := range doc.Options {
fmt.Fprintf(b, " %s", opt)
}
fmt.Fprint(b, "\n")
}
}
return b.String()
}
func (doc *Documentation) String() string {
return doc.Format(true)
}
func newVersionFlag() flag.Getter {
tags := build.Default.ReleaseTags
v := tags[len(tags)-1][2:]
version := new(VersionFlag)
if err := version.Set(v); err != nil {
panic(fmt.Sprintf("internal error: %s", err))
}
return version
}
type VersionFlag int
func (v *VersionFlag) String() string {
return fmt.Sprintf("1.%d", *v)
}
func (v *VersionFlag) Set(s string) error {
if len(s) < 3 {
return fmt.Errorf("invalid Go version: %q", s)
}
if s[0] != '1' {
return fmt.Errorf("invalid Go version: %q", s)
}
if s[1] != '.' {
return fmt.Errorf("invalid Go version: %q", s)
}
i, err := strconv.Atoi(s[2:])
if err != nil {
return fmt.Errorf("invalid Go version: %q", s)
}
*v = VersionFlag(i)
return nil
}
func (v *VersionFlag) Get() interface{} {
return int(*v)
}
// ExhaustiveTypeSwitch panics when called. It can be used to ensure
// that type switches are exhaustive.
func ExhaustiveTypeSwitch(v interface{}) {
panic(fmt.Sprintf("internal error: unhandled case %T", v))
}
// A directive is a comment of the form '//lint:<command>
// [arguments...]'. It represents instructions to the static analysis
// tool.
type Directive struct {
Command string
Arguments []string
Directive *ast.Comment
Node ast.Node
}
func parseDirective(s string) (cmd string, args []string) {
if !strings.HasPrefix(s, "//lint:") {
return "", nil
}
s = strings.TrimPrefix(s, "//lint:")
fields := strings.Split(s, " ")
return fields[0], fields[1:]
}
// ParseDirectives extracts all directives from a list of Go files.
func ParseDirectives(files []*ast.File, fset *token.FileSet) []Directive {
var dirs []Directive
for _, f := range files {
// OPT(dh): in our old code, we skip all the comment map work if we
// couldn't find any directives, benchmark if that's actually
// worth doing
cm := ast.NewCommentMap(fset, f, f.Comments)
for node, cgs := range cm {
for _, cg := range cgs {
for _, c := range cg.List {
if !strings.HasPrefix(c.Text, "//lint:") {
continue
}
cmd, args := parseDirective(c.Text)
d := Directive{
Command: cmd,
Arguments: args,
Directive: c,
Node: node,
}
dirs = append(dirs, d)
}
}
}
}
return dirs
}