1
0
Fork 0
forked from forgejo/forgejo

Rename scripts to build and add revive command as a new build tool command (#10942)

Co-authored-by: techknowlogick <techknowlogick@gitea.io>
This commit is contained in:
Lunny Xiao 2020-04-04 03:29:12 +08:00 committed by GitHub
parent 4af7c47b38
commit 4f63f283c4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
182 changed files with 15832 additions and 1226 deletions

21
vendor/github.com/mgechev/revive/LICENSE generated vendored Normal file
View file

@ -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.

View file

@ -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>`

26
vendor/github.com/mgechev/revive/formatter/default.go generated vendored Normal file
View file

@ -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
}

146
vendor/github.com/mgechev/revive/formatter/friendly.go generated vendored Normal file
View file

@ -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()
}

40
vendor/github.com/mgechev/revive/formatter/json.go generated vendored Normal file
View file

@ -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
}

34
vendor/github.com/mgechev/revive/formatter/ndjson.go generated vendored Normal file
View file

@ -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
}

26
vendor/github.com/mgechev/revive/formatter/plain.go generated vendored Normal file
View file

@ -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
}

13
vendor/github.com/mgechev/revive/formatter/severity.go generated vendored Normal file
View file

@ -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
}

89
vendor/github.com/mgechev/revive/formatter/stylish.go generated vendored Normal file
View file

@ -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
}

27
vendor/github.com/mgechev/revive/formatter/unix.go generated vendored Normal file
View file

@ -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
}

32
vendor/github.com/mgechev/revive/lint/config.go generated vendored Normal file
View file

@ -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"`
}

39
vendor/github.com/mgechev/revive/lint/failure.go generated vendored Normal file
View file

@ -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
}

278
vendor/github.com/mgechev/revive/lint/file.go generated vendored Normal file
View file

@ -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
}

14
vendor/github.com/mgechev/revive/lint/formatter.go generated vendored Normal file
View file

@ -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
}

99
vendor/github.com/mgechev/revive/lint/linter.go generated vendored Normal file
View file

@ -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
}

178
vendor/github.com/mgechev/revive/lint/package.go generated vendored Normal file
View file

@ -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()
}

31
vendor/github.com/mgechev/revive/lint/rule.go generated vendored Normal file
View file

@ -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),
}
}

128
vendor/github.com/mgechev/revive/lint/utils.go generated vendored Normal file
View file

@ -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,
}

151
vendor/github.com/mgechev/revive/rule/add-constant.go generated vendored Normal file
View file

@ -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),
})
}

View file

@ -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
}

94
vendor/github.com/mgechev/revive/rule/atomic.go generated vendored Normal file
View file

@ -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
}

84
vendor/github.com/mgechev/revive/rule/bare-return.go generated vendored Normal file
View file

@ -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
}

74
vendor/github.com/mgechev/revive/rule/blank-imports.go generated vendored Normal file
View file

@ -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
}

View file

@ -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,
})
}

70
vendor/github.com/mgechev/revive/rule/call-to-gc.go generated vendored Normal file
View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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,
})
}

View file

@ -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
}

View file

@ -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),
})
}
}

115
vendor/github.com/mgechev/revive/rule/cyclomatic.go generated vendored Normal file
View file

@ -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
}

94
vendor/github.com/mgechev/revive/rule/deep-exit.go generated vendored Normal file
View file

@ -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")
}

54
vendor/github.com/mgechev/revive/rule/dot-imports.go generated vendored Normal file
View file

@ -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
}

View file

@ -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"
}

76
vendor/github.com/mgechev/revive/rule/empty-block.go generated vendored Normal file
View file

@ -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
}

113
vendor/github.com/mgechev/revive/rule/empty-lines.go generated vendored Normal file
View file

@ -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)
}

79
vendor/github.com/mgechev/revive/rule/error-naming.go generated vendored Normal file
View file

@ -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
}

67
vendor/github.com/mgechev/revive/rule/error-return.go generated vendored Normal file
View file

@ -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
}

98
vendor/github.com/mgechev/revive/rule/error-strings.go generated vendored Normal file
View file

@ -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
}

93
vendor/github.com/mgechev/revive/rule/errorf.go generated vendored Normal file
View file

@ -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)
}

272
vendor/github.com/mgechev/revive/rule/exported.go generated vendored Normal file
View file

@ -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
}

69
vendor/github.com/mgechev/revive/rule/file-header.go generated vendored Normal file
View file

@ -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"
}

104
vendor/github.com/mgechev/revive/rule/flag-param.go generated vendored Normal file
View file

@ -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
}

View file

@ -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
}

70
vendor/github.com/mgechev/revive/rule/get-return.go generated vendored Normal file
View file

@ -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
}

115
vendor/github.com/mgechev/revive/rule/if-return.go generated vendored Normal file
View file

@ -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
}

View file

@ -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
}

View file

@ -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"
}

View file

@ -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"
}

View file

@ -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
}

View file

@ -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++
}
}

View file

@ -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
}

View file

@ -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),
})
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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),
}
}

View file

@ -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
}

82
vendor/github.com/mgechev/revive/rule/range.go generated vendored Normal file
View file

@ -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
}

View file

@ -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
}

View file

@ -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,
})
}

95
vendor/github.com/mgechev/revive/rule/string-of-int.go generated vendored Normal file
View file

@ -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
}

236
vendor/github.com/mgechev/revive/rule/struct-tag.go generated vendored Normal file
View file

@ -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,
})
}

View file

@ -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,
}
}

93
vendor/github.com/mgechev/revive/rule/time-naming.go generated vendored Normal file
View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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,
})
}

View file

@ -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",
}
}

102
vendor/github.com/mgechev/revive/rule/unused-param.go generated vendored Normal file
View file

@ -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
}

View file

@ -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
}

191
vendor/github.com/mgechev/revive/rule/utils.go generated vendored Normal file
View file

@ -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()
}

View file

@ -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
}

230
vendor/github.com/mgechev/revive/rule/var-naming.go generated vendored Normal file
View file

@ -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
}

View file

@ -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"
}