mirror of
https://github.com/mgechev/revive.git
synced 2025-07-13 01:00:17 +02:00
Refactoring
This commit is contained in:
@ -1,116 +0,0 @@
|
|||||||
package defaultrule
|
|
||||||
|
|
||||||
import (
|
|
||||||
"go/ast"
|
|
||||||
"go/token"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/mgechev/revive/file"
|
|
||||||
"github.com/mgechev/revive/rule"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LintElseRule lints given else constructs.
|
|
||||||
type LintElseRule struct{}
|
|
||||||
|
|
||||||
// Apply applies the rule to given file.
|
|
||||||
func (r *LintElseRule) Apply(file *file.File, arguments rule.Arguments) []rule.Failure {
|
|
||||||
var failures []rule.Failure
|
|
||||||
|
|
||||||
onFailure := func(failure rule.Failure) {
|
|
||||||
failures = append(failures, failure)
|
|
||||||
}
|
|
||||||
|
|
||||||
astFile := file.GetAST()
|
|
||||||
w := &lintElseError{astFile, onFailure}
|
|
||||||
ast.Walk(w, astFile)
|
|
||||||
return failures
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the rule name.
|
|
||||||
func (r *LintElseRule) Name() string {
|
|
||||||
return "no-else-return"
|
|
||||||
}
|
|
||||||
|
|
||||||
type lintElseError struct {
|
|
||||||
file *ast.File
|
|
||||||
onFailure func(rule.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(w.file, s.Pos(), r.Pos()) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
w.onFailure(rule.Failure{
|
|
||||||
Confidence: 0.9,
|
|
||||||
Failure: "redundant if ...; err != nil check, just return error instead.",
|
|
||||||
Node: v.List[i],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
func containsComments(f *ast.File, start, end token.Pos) 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
|
|
||||||
}
|
|
@ -5,7 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/mgechev/revive/rule"
|
"github.com/mgechev/revive/linter"
|
||||||
"github.com/olekukonko/tablewriter"
|
"github.com/olekukonko/tablewriter"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,28 +17,28 @@ const (
|
|||||||
// CLIFormatter is an implementation of the Formatter interface
|
// CLIFormatter is an implementation of the Formatter interface
|
||||||
// which formats the errors to JSON.
|
// which formats the errors to JSON.
|
||||||
type CLIFormatter struct {
|
type CLIFormatter struct {
|
||||||
Metadata FormatterMetadata
|
Metadata linter.FormatterMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatFailure(failure rule.Failure) []string {
|
func formatFailure(failure linter.Failure) []string {
|
||||||
fString := color.BlueString(failure.Failure)
|
fString := color.BlueString(failure.Failure)
|
||||||
fTypeStr := string(failure.Type)
|
fTypeStr := string(failure.Type)
|
||||||
fType := color.RedString(fTypeStr)
|
fType := color.RedString(fTypeStr)
|
||||||
lineColumn := failure.Position
|
lineColumn := failure.Position
|
||||||
pos := fmt.Sprintf("(%d, %d)", lineColumn.Start.Line, lineColumn.Start.Column)
|
pos := fmt.Sprintf("(%d, %d)", lineColumn.Start.Line, lineColumn.Start.Column)
|
||||||
if failure.Type == rule.FailureTypeWarning {
|
if failure.Type == linter.FailureTypeWarning {
|
||||||
fType = color.YellowString(fTypeStr)
|
fType = color.YellowString(fTypeStr)
|
||||||
}
|
}
|
||||||
return []string{failure.GetFilename(), pos, fType, fString}
|
return []string{failure.GetFilename(), pos, fType, fString}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format formats the failures gotten from the linter.
|
// Format formats the failures gotten from the linter.
|
||||||
func (f *CLIFormatter) Format(failures []rule.Failure) (string, error) {
|
func (f *CLIFormatter) Format(failures []linter.Failure) (string, error) {
|
||||||
var result [][]string
|
var result [][]string
|
||||||
var totalErrors = 0
|
var totalErrors = 0
|
||||||
for _, f := range failures {
|
for _, f := range failures {
|
||||||
result = append(result, formatFailure(f))
|
result = append(result, formatFailure(f))
|
||||||
if f.Type == rule.FailureTypeError {
|
if f.Type == linter.FailureTypeError {
|
||||||
totalErrors++
|
totalErrors++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,17 +3,17 @@ package formatter
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/mgechev/revive/rule"
|
"github.com/mgechev/revive/linter"
|
||||||
)
|
)
|
||||||
|
|
||||||
// JSONFormatter is an implementation of the Formatter interface
|
// JSONFormatter is an implementation of the Formatter interface
|
||||||
// which formats the errors to JSON.
|
// which formats the errors to JSON.
|
||||||
type JSONFormatter struct {
|
type JSONFormatter struct {
|
||||||
Metadata FormatterMetadata
|
Metadata linter.FormatterMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format formats the failures gotten from the linter.
|
// Format formats the failures gotten from the linter.
|
||||||
func (f *JSONFormatter) Format(failures []rule.Failure) (string, error) {
|
func (f *JSONFormatter) Format(failures []linter.Failure) (string, error) {
|
||||||
result, error := json.Marshal(failures)
|
result, error := json.Marshal(failures)
|
||||||
if error != nil {
|
if error != nil {
|
||||||
return "", error
|
return "", error
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package file
|
package linter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/ast"
|
"go/ast"
|
||||||
@ -9,31 +9,38 @@ import (
|
|||||||
// File abstraction used for representing files.
|
// File abstraction used for representing files.
|
||||||
type File struct {
|
type File struct {
|
||||||
Name string
|
Name string
|
||||||
files *token.FileSet
|
pkg *Package
|
||||||
Content []byte
|
Content []byte
|
||||||
ast *ast.File
|
ast *ast.File
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new file
|
// NewFile creates a new file
|
||||||
func New(name string, content []byte, files *token.FileSet) (*File, error) {
|
func NewFile(name string, content []byte, pkg *Package) (*File, error) {
|
||||||
f, err := parser.ParseFile(files, name, content, parser.ParseComments)
|
f, err := parser.ParseFile(pkg.Fset, name, content, parser.ParseComments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &File{
|
return &File{
|
||||||
Name: name,
|
Name: name,
|
||||||
Content: content,
|
Content: content,
|
||||||
files: files,
|
pkg: pkg,
|
||||||
ast: f,
|
ast: f,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToPosition returns line and column for given position.
|
// ToPosition returns line and column for given position.
|
||||||
func (f *File) ToPosition(pos token.Pos) token.Position {
|
func (f *File) ToPosition(pos token.Pos) token.Position {
|
||||||
return f.files.Position(pos)
|
return f.pkg.Fset.Position(pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAST returns the AST of the file
|
// GetAST returns the AST of the file
|
||||||
func (f *File) GetAST() *ast.File {
|
func (f *File) GetAST() *ast.File {
|
||||||
return f.ast
|
return f.ast
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *File) isMain() bool {
|
||||||
|
if f.GetAST().Name.Name == "main" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
@ -1,6 +1,4 @@
|
|||||||
package formatter
|
package linter
|
||||||
|
|
||||||
import "github.com/mgechev/revive/rule"
|
|
||||||
|
|
||||||
// FormatterMetadata configuration of a formatter
|
// FormatterMetadata configuration of a formatter
|
||||||
type FormatterMetadata struct {
|
type FormatterMetadata struct {
|
||||||
@ -11,5 +9,5 @@ type FormatterMetadata struct {
|
|||||||
|
|
||||||
// Formatter defines an interface for failure formatters
|
// Formatter defines an interface for failure formatters
|
||||||
type Formatter interface {
|
type Formatter interface {
|
||||||
Format([]rule.Failure) string
|
Format([]Failure) string
|
||||||
}
|
}
|
@ -1,20 +1,20 @@
|
|||||||
package linter
|
package linter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/token"
|
"go/token"
|
||||||
"math"
|
"math"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mgechev/revive/file"
|
|
||||||
"github.com/mgechev/revive/rule"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReadFile defines an abstraction for reading files.
|
// ReadFile defines an abstraction for reading files.
|
||||||
type ReadFile func(path string) (result []byte, err error)
|
type ReadFile func(path string) (result []byte, err error)
|
||||||
|
|
||||||
type disabledIntervalsMap = map[string][]rule.DisabledInterval
|
type disabledIntervalsMap = map[string][]DisabledInterval
|
||||||
|
|
||||||
// Linter is used for linting set of files.
|
// Linter is used for linting set of files.
|
||||||
type Linter struct {
|
type Linter struct {
|
||||||
@ -26,26 +26,62 @@ func New(reader ReadFile) Linter {
|
|||||||
return Linter{reader: reader}
|
return Linter{reader: reader}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
genHdr = []byte("// Code generated ")
|
||||||
|
genFtr = []byte(" DO NOT EDIT.")
|
||||||
|
)
|
||||||
|
|
||||||
|
// isGenerated reports whether the source file is generated code
|
||||||
|
// according the rules from https://golang.org/s/generatedcode.
|
||||||
|
// This is inherited from the original linter.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
// Lint lints a set of files with the specified rule.
|
// Lint lints a set of files with the specified rule.
|
||||||
func (l *Linter) Lint(filenames []string, ruleSet []rule.Rule, rulesConfig rule.RulesConfig) ([]rule.Failure, error) {
|
func (l *Linter) Lint(filenames []string, ruleSet []Rule, rulesConfig RulesConfig) ([]Failure, error) {
|
||||||
var fileSet token.FileSet
|
var failures []Failure
|
||||||
var failures []rule.Failure
|
ruleNames := []string{}
|
||||||
var ruleNames = []string{}
|
|
||||||
for _, r := range ruleSet {
|
for _, r := range ruleSet {
|
||||||
ruleNames = append(ruleNames, r.Name())
|
ruleNames = append(ruleNames, r.Name())
|
||||||
}
|
}
|
||||||
|
pkg := &Package{
|
||||||
|
Fset: token.NewFileSet(),
|
||||||
|
Files: map[string]*File{},
|
||||||
|
}
|
||||||
|
var pkgName string
|
||||||
for _, filename := range filenames {
|
for _, filename := range filenames {
|
||||||
content, err := l.reader(filename)
|
content, err := l.reader(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
file, err := file.New(filename, content, &fileSet)
|
if isGenerated(content) {
|
||||||
disabledIntervals := l.disabledIntervals(file, ruleNames)
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := NewFile(filename, content, pkg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pkgName == "" {
|
||||||
|
pkgName = file.GetAST().Name.Name
|
||||||
|
} else if file.GetAST().Name.Name != pkgName {
|
||||||
|
return nil, fmt.Errorf("%s is in package %s, not %s", filename, file.GetAST().Name.Name, pkgName)
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg.Files[filename] = file
|
||||||
|
disabledIntervals := l.disabledIntervals(file, ruleNames)
|
||||||
|
|
||||||
|
pkg.TypeCheck()
|
||||||
|
|
||||||
for _, currentRule := range ruleSet {
|
for _, currentRule := range ruleSet {
|
||||||
config := rulesConfig[currentRule.Name()]
|
config := rulesConfig[currentRule.Name()]
|
||||||
currentFailures := currentRule.Apply(file, config)
|
currentFailures := currentRule.Apply(file, config)
|
||||||
@ -54,7 +90,7 @@ func (l *Linter) Lint(filenames []string, ruleSet []rule.Rule, rulesConfig rule.
|
|||||||
failure.RuleName = currentRule.Name()
|
failure.RuleName = currentRule.Name()
|
||||||
}
|
}
|
||||||
if failure.Node != nil {
|
if failure.Node != nil {
|
||||||
failure.Position = rule.ToFailurePosition(failure.Node.Pos(), failure.Node.End(), file)
|
failure.Position = ToFailurePosition(failure.Node.Pos(), failure.Node.End(), file)
|
||||||
}
|
}
|
||||||
currentFailures[idx] = failure
|
currentFailures[idx] = failure
|
||||||
}
|
}
|
||||||
@ -71,7 +107,7 @@ type enableDisableConfig struct {
|
|||||||
position int
|
position int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Linter) disabledIntervals(file *file.File, allRuleNames []string) disabledIntervalsMap {
|
func (l *Linter) disabledIntervals(file *File, allRuleNames []string) disabledIntervalsMap {
|
||||||
re := regexp.MustCompile(`^\s*revive:(enable|disable)(?:-(line|next-line))?(:|\s|$)`)
|
re := regexp.MustCompile(`^\s*revive:(enable|disable)(?:-(line|next-line))?(:|\s|$)`)
|
||||||
|
|
||||||
enabledDisabledRulesMap := make(map[string][]enableDisableConfig)
|
enabledDisabledRulesMap := make(map[string][]enableDisableConfig)
|
||||||
@ -80,9 +116,9 @@ func (l *Linter) disabledIntervals(file *file.File, allRuleNames []string) disab
|
|||||||
result := make(disabledIntervalsMap)
|
result := make(disabledIntervalsMap)
|
||||||
|
|
||||||
for ruleName, disabledArr := range enabledDisabledRulesMap {
|
for ruleName, disabledArr := range enabledDisabledRulesMap {
|
||||||
ruleResult := []rule.DisabledInterval{}
|
ruleResult := []DisabledInterval{}
|
||||||
for i := 0; i < len(disabledArr); i++ {
|
for i := 0; i < len(disabledArr); i++ {
|
||||||
interval := rule.DisabledInterval{
|
interval := DisabledInterval{
|
||||||
RuleName: ruleName,
|
RuleName: ruleName,
|
||||||
From: token.Position{
|
From: token.Position{
|
||||||
Filename: file.Name,
|
Filename: file.Name,
|
||||||
@ -122,8 +158,8 @@ func (l *Linter) disabledIntervals(file *file.File, allRuleNames []string) disab
|
|||||||
enabledDisabledRulesMap[name] = existing
|
enabledDisabledRulesMap[name] = existing
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRules := func(filename, modifier string, isEnabled bool, line int, ruleNames []string) []rule.DisabledInterval {
|
handleRules := func(filename, modifier string, isEnabled bool, line int, ruleNames []string) []DisabledInterval {
|
||||||
var result []rule.DisabledInterval
|
var result []DisabledInterval
|
||||||
for _, name := range ruleNames {
|
for _, name := range ruleNames {
|
||||||
if modifier == "line" {
|
if modifier == "line" {
|
||||||
handleConfig(isEnabled, line, name)
|
handleConfig(isEnabled, line, name)
|
||||||
@ -172,8 +208,8 @@ func (l *Linter) disabledIntervals(file *file.File, allRuleNames []string) disab
|
|||||||
return getEnabledDisabledIntervals()
|
return getEnabledDisabledIntervals()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Linter) filterFailures(failures []rule.Failure, disabledIntervals disabledIntervalsMap) []rule.Failure {
|
func (l *Linter) filterFailures(failures []Failure, disabledIntervals disabledIntervalsMap) []Failure {
|
||||||
result := []rule.Failure{}
|
result := []Failure{}
|
||||||
for _, failure := range failures {
|
for _, failure := range failures {
|
||||||
fStart := failure.Position.Start.Line
|
fStart := failure.Position.Start.Line
|
||||||
fEnd := failure.Position.End.Line
|
fEnd := failure.Position.End.Line
|
||||||
|
77
linter/package.go
Normal file
77
linter/package.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
package linter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
|
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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.GetAST())
|
||||||
|
}
|
||||||
|
typesPkg, err := config.Check(anyFile.GetAST().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
|
||||||
|
return err
|
||||||
|
}
|
@ -1,10 +1,8 @@
|
|||||||
package rule
|
package linter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/token"
|
"go/token"
|
||||||
|
|
||||||
"github.com/mgechev/revive/file"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -60,7 +58,7 @@ type RulesConfig = map[string]Arguments
|
|||||||
// Rule defines an abstract rule interaface
|
// Rule defines an abstract rule interaface
|
||||||
type Rule interface {
|
type Rule interface {
|
||||||
Name() string
|
Name() string
|
||||||
Apply(*file.File, Arguments) []Failure
|
Apply(*File, Arguments) []Failure
|
||||||
}
|
}
|
||||||
|
|
||||||
// AbstractRule defines an abstract rule.
|
// AbstractRule defines an abstract rule.
|
||||||
@ -69,7 +67,7 @@ type AbstractRule struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ToFailurePosition returns the failure position.
|
// ToFailurePosition returns the failure position.
|
||||||
func ToFailurePosition(start token.Pos, end token.Pos, file *file.File) FailurePosition {
|
func ToFailurePosition(start token.Pos, end token.Pos, file *File) FailurePosition {
|
||||||
return FailurePosition{
|
return FailurePosition{
|
||||||
Start: file.ToPosition(start),
|
Start: file.ToPosition(start),
|
||||||
End: file.ToPosition(end),
|
End: file.ToPosition(end),
|
11
main.go
11
main.go
@ -3,7 +3,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/mgechev/revive/defaultrule"
|
|
||||||
"github.com/mgechev/revive/formatter"
|
"github.com/mgechev/revive/formatter"
|
||||||
"github.com/mgechev/revive/linter"
|
"github.com/mgechev/revive/linter"
|
||||||
"github.com/mgechev/revive/rule"
|
"github.com/mgechev/revive/rule"
|
||||||
@ -26,17 +25,17 @@ func main() {
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
linter := linter.New(func(file string) ([]byte, error) {
|
revive := linter.New(func(file string) ([]byte, error) {
|
||||||
return []byte(src), nil
|
return []byte(src), nil
|
||||||
})
|
})
|
||||||
var result []rule.Rule
|
var result []linter.Rule
|
||||||
result = append(result, &defaultrule.LintElseRule{}, &defaultrule.ArgumentsLimitRule{})
|
result = append(result, &rule.LintElseRule{}, &rule.ArgumentsLimitRule{})
|
||||||
|
|
||||||
var config = rule.RulesConfig{
|
var config = linter.RulesConfig{
|
||||||
"argument-limit": []string{"3"},
|
"argument-limit": []string{"3"},
|
||||||
}
|
}
|
||||||
|
|
||||||
failures, err := linter.Lint([]string{"foo.go", "bar.go", "baz.go"}, result, config)
|
failures, err := revive.Lint([]string{"foo.go", "bar.go", "baz.go"}, result, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,18 @@
|
|||||||
package defaultrule
|
package rule
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/mgechev/revive/file"
|
"github.com/mgechev/revive/linter"
|
||||||
"github.com/mgechev/revive/rule"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ArgumentsLimitRule lints given else constructs.
|
// ArgumentsLimitRule lints given else constructs.
|
||||||
type ArgumentsLimitRule struct{}
|
type ArgumentsLimitRule struct{}
|
||||||
|
|
||||||
// Apply applies the rule to given file.
|
// Apply applies the rule to given file.
|
||||||
func (r *ArgumentsLimitRule) Apply(file *file.File, arguments rule.Arguments) []rule.Failure {
|
func (r *ArgumentsLimitRule) Apply(file *linter.File, arguments linter.Arguments) []linter.Failure {
|
||||||
if len(arguments) != 1 {
|
if len(arguments) != 1 {
|
||||||
panic(`invalid configuration for "argument-limit"`)
|
panic(`invalid configuration for "argument-limit"`)
|
||||||
}
|
}
|
||||||
@ -22,11 +21,11 @@ func (r *ArgumentsLimitRule) Apply(file *file.File, arguments rule.Arguments) []
|
|||||||
panic(`invalid configuration for "argument-limit"`)
|
panic(`invalid configuration for "argument-limit"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
var failures []rule.Failure
|
var failures []linter.Failure
|
||||||
|
|
||||||
walker := lintArgsNum{
|
walker := lintArgsNum{
|
||||||
total: total,
|
total: total,
|
||||||
onFailure: func(failure rule.Failure) {
|
onFailure: func(failure linter.Failure) {
|
||||||
failures = append(failures, failure)
|
failures = append(failures, failure)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -43,7 +42,7 @@ func (r *ArgumentsLimitRule) Name() string {
|
|||||||
|
|
||||||
type lintArgsNum struct {
|
type lintArgsNum struct {
|
||||||
total int64
|
total int64
|
||||||
onFailure func(rule.Failure)
|
onFailure func(linter.Failure)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w lintArgsNum) Visit(n ast.Node) ast.Visitor {
|
func (w lintArgsNum) Visit(n ast.Node) ast.Visitor {
|
||||||
@ -51,9 +50,9 @@ func (w lintArgsNum) Visit(n ast.Node) ast.Visitor {
|
|||||||
if ok {
|
if ok {
|
||||||
num := int64(len(node.Type.Params.List))
|
num := int64(len(node.Type.Params.List))
|
||||||
if num > w.total {
|
if num > w.total {
|
||||||
w.onFailure(rule.Failure{
|
w.onFailure(linter.Failure{
|
||||||
Failure: fmt.Sprintf("maximum number of arguments per function exceeded; max %d but got %d", w.total, num),
|
Failure: fmt.Sprintf("maximum number of arguments per function exceeded; max %d but got %d", w.total, num),
|
||||||
Type: rule.FailureTypeWarning,
|
Type: linter.FailureTypeWarning,
|
||||||
Node: node.Type,
|
Node: node.Type,
|
||||||
})
|
})
|
||||||
return w
|
return w
|
@ -1,4 +1,4 @@
|
|||||||
package defaultrule
|
package rule
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
@ -1,24 +1,23 @@
|
|||||||
package defaultrule
|
package rule
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/ast"
|
"go/ast"
|
||||||
|
|
||||||
"github.com/mgechev/revive/file"
|
"github.com/mgechev/revive/linter"
|
||||||
"github.com/mgechev/revive/rule"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// BlankImportsRule lints given else constructs.
|
// BlankImportsRule lints given else constructs.
|
||||||
type BlankImportsRule struct{}
|
type BlankImportsRule struct{}
|
||||||
|
|
||||||
// Apply applies the rule to given file.
|
// Apply applies the rule to given file.
|
||||||
func (r *BlankImportsRule) Apply(file *file.File, arguments rule.Arguments) []rule.Failure {
|
func (r *BlankImportsRule) Apply(file *linter.File, arguments linter.Arguments) []linter.Failure {
|
||||||
var failures []rule.Failure
|
var failures []linter.Failure
|
||||||
|
|
||||||
fileAst := file.GetAST()
|
fileAst := file.GetAST()
|
||||||
walker := lintBlankImports{
|
walker := lintBlankImports{
|
||||||
file: file,
|
file: file,
|
||||||
fileAst: fileAst,
|
fileAst: fileAst,
|
||||||
onFailure: func(failure rule.Failure) {
|
onFailure: func(failure linter.Failure) {
|
||||||
failures = append(failures, failure)
|
failures = append(failures, failure)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -35,8 +34,8 @@ func (r *BlankImportsRule) Name() string {
|
|||||||
|
|
||||||
type lintBlankImports struct {
|
type lintBlankImports struct {
|
||||||
fileAst *ast.File
|
fileAst *ast.File
|
||||||
file *file.File
|
file *linter.File
|
||||||
onFailure func(rule.Failure)
|
onFailure func(linter.Failure)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w lintBlankImports) Visit(n ast.Node) ast.Visitor {
|
func (w lintBlankImports) Visit(n ast.Node) ast.Visitor {
|
||||||
@ -63,7 +62,7 @@ func (w lintBlankImports) Visit(n ast.Node) ast.Visitor {
|
|||||||
|
|
||||||
// This is the first blank import of a group.
|
// This is the first blank import of a group.
|
||||||
if imp.Doc == nil && imp.Comment == nil {
|
if imp.Doc == nil && imp.Comment == nil {
|
||||||
w.onFailure(rule.Failure{
|
w.onFailure(linter.Failure{
|
||||||
Node: imp,
|
Node: imp,
|
||||||
Failure: "a blank import should be only in a main or test package, or have a comment justifying it",
|
Failure: "a blank import should be only in a main or test package, or have a comment justifying it",
|
||||||
Confidence: 1,
|
Confidence: 1,
|
@ -1,4 +1,4 @@
|
|||||||
package defaultrule
|
package rule
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
107
rule/else-error.go
Normal file
107
rule/else-error.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
package rule
|
||||||
|
|
||||||
|
// // LintElseRule lints given else constructs.
|
||||||
|
// type LintElseRule struct{}
|
||||||
|
|
||||||
|
// // Apply applies the rule to given file.
|
||||||
|
// func (r *LintElseRule) Apply(file *project.File, arguments rule.Arguments) []rule.Failure {
|
||||||
|
// var failures []rule.Failure
|
||||||
|
|
||||||
|
// onFailure := func(failure rule.Failure) {
|
||||||
|
// failures = append(failures, failure)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// astFile := file.GetAST()
|
||||||
|
// w := &lintElseError{astFile, onFailure}
|
||||||
|
// ast.Walk(w, astFile)
|
||||||
|
// return failures
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Name returns the rule name.
|
||||||
|
// func (r *LintElseRule) Name() string {
|
||||||
|
// return "no-else-return"
|
||||||
|
// }
|
||||||
|
|
||||||
|
// type lintElseError struct {
|
||||||
|
// file *ast.File
|
||||||
|
// onFailure func(rule.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(w.file, s.Pos(), r.Pos()) {
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
|
||||||
|
// w.onFailure(rule.Failure{
|
||||||
|
// Confidence: 0.9,
|
||||||
|
// Failure: "redundant if ...; err != nil check, just return error instead.",
|
||||||
|
// Node: v.List[i],
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return w
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func containsComments(f *ast.File, start, end token.Pos) 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
|
||||||
|
// }
|
@ -1,4 +1,4 @@
|
|||||||
package defaultrule
|
package rule
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -8,16 +8,15 @@ import (
|
|||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/mgechev/revive/file"
|
"github.com/mgechev/revive/linter"
|
||||||
"github.com/mgechev/revive/rule"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExportedRule lints given else constructs.
|
// ExportedRule lints given else constructs.
|
||||||
type ExportedRule struct{}
|
type ExportedRule struct{}
|
||||||
|
|
||||||
// Apply applies the rule to given file.
|
// Apply applies the rule to given file.
|
||||||
func (r *ExportedRule) Apply(file *file.File, arguments rule.Arguments) []rule.Failure {
|
func (r *ExportedRule) Apply(file *linter.File, arguments linter.Arguments) []linter.Failure {
|
||||||
var failures []rule.Failure
|
var failures []linter.Failure
|
||||||
|
|
||||||
if isTest(file) {
|
if isTest(file) {
|
||||||
return failures
|
return failures
|
||||||
@ -27,7 +26,7 @@ func (r *ExportedRule) Apply(file *file.File, arguments rule.Arguments) []rule.F
|
|||||||
walker := lintExported{
|
walker := lintExported{
|
||||||
file: file,
|
file: file,
|
||||||
fileAst: fileAst,
|
fileAst: fileAst,
|
||||||
onFailure: func(failure rule.Failure) {
|
onFailure: func(failure linter.Failure) {
|
||||||
failures = append(failures, failure)
|
failures = append(failures, failure)
|
||||||
},
|
},
|
||||||
genDeclMissingComments: make(map[*ast.GenDecl]bool),
|
genDeclMissingComments: make(map[*ast.GenDecl]bool),
|
||||||
@ -44,11 +43,11 @@ func (r *ExportedRule) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type lintExported struct {
|
type lintExported struct {
|
||||||
file *file.File
|
file *linter.File
|
||||||
fileAst *ast.File
|
fileAst *ast.File
|
||||||
lastGen *ast.GenDecl
|
lastGen *ast.GenDecl
|
||||||
genDeclMissingComments map[*ast.GenDecl]bool
|
genDeclMissingComments map[*ast.GenDecl]bool
|
||||||
onFailure func(rule.Failure)
|
onFailure func(linter.Failure)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *lintExported) lintFuncDoc(fn *ast.FuncDecl) {
|
func (w *lintExported) lintFuncDoc(fn *ast.FuncDecl) {
|
||||||
@ -79,7 +78,7 @@ func (w *lintExported) lintFuncDoc(fn *ast.FuncDecl) {
|
|||||||
name = recv + "." + name
|
name = recv + "." + name
|
||||||
}
|
}
|
||||||
if fn.Doc == nil {
|
if fn.Doc == nil {
|
||||||
w.onFailure(rule.Failure{
|
w.onFailure(linter.Failure{
|
||||||
Node: fn,
|
Node: fn,
|
||||||
Failure: fmt.Sprintf("exported %s %s should have comment or be unexported", kind, name),
|
Failure: fmt.Sprintf("exported %s %s should have comment or be unexported", kind, name),
|
||||||
Confidence: 1,
|
Confidence: 1,
|
||||||
@ -89,7 +88,7 @@ func (w *lintExported) lintFuncDoc(fn *ast.FuncDecl) {
|
|||||||
s := fn.Doc.Text()
|
s := fn.Doc.Text()
|
||||||
prefix := fn.Name.Name + " "
|
prefix := fn.Name.Name + " "
|
||||||
if !strings.HasPrefix(s, prefix) {
|
if !strings.HasPrefix(s, prefix) {
|
||||||
w.onFailure(rule.Failure{
|
w.onFailure(linter.Failure{
|
||||||
Node: fn.Doc,
|
Node: fn.Doc,
|
||||||
Failure: fmt.Sprintf(`comment on exported %s %s should be of the form "%s..."`, kind, name, prefix),
|
Failure: fmt.Sprintf(`comment on exported %s %s should be of the form "%s..."`, kind, name, prefix),
|
||||||
Confidence: 1,
|
Confidence: 1,
|
||||||
@ -118,7 +117,7 @@ func (w *lintExported) checkStutter(id *ast.Ident, thing string) {
|
|||||||
// the it's starting a new word and thus this name stutters.
|
// the it's starting a new word and thus this name stutters.
|
||||||
rem := name[len(pkg):]
|
rem := name[len(pkg):]
|
||||||
if next, _ := utf8.DecodeRuneInString(rem); next == '_' || unicode.IsUpper(next) {
|
if next, _ := utf8.DecodeRuneInString(rem); next == '_' || unicode.IsUpper(next) {
|
||||||
w.onFailure(rule.Failure{
|
w.onFailure(linter.Failure{
|
||||||
Node: id,
|
Node: id,
|
||||||
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),
|
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),
|
||||||
Confidence: 0.8,
|
Confidence: 0.8,
|
||||||
@ -131,7 +130,7 @@ func (w *lintExported) lintTypeDoc(t *ast.TypeSpec, doc *ast.CommentGroup) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if doc == nil {
|
if doc == nil {
|
||||||
w.onFailure(rule.Failure{
|
w.onFailure(linter.Failure{
|
||||||
Node: t,
|
Node: t,
|
||||||
Failure: fmt.Sprintf("exported type %v should have comment or be unexported", t.Name),
|
Failure: fmt.Sprintf("exported type %v should have comment or be unexported", t.Name),
|
||||||
Confidence: 1,
|
Confidence: 1,
|
||||||
@ -148,7 +147,7 @@ func (w *lintExported) lintTypeDoc(t *ast.TypeSpec, doc *ast.CommentGroup) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(s, t.Name.Name+" ") {
|
if !strings.HasPrefix(s, t.Name.Name+" ") {
|
||||||
w.onFailure(rule.Failure{
|
w.onFailure(linter.Failure{
|
||||||
Node: doc,
|
Node: doc,
|
||||||
Failure: fmt.Sprintf(`comment on exported type %v should be of the form "%v ..." (with optional leading article)`, t.Name, t.Name),
|
Failure: fmt.Sprintf(`comment on exported type %v should be of the form "%v ..." (with optional leading article)`, t.Name, t.Name),
|
||||||
Confidence: 1,
|
Confidence: 1,
|
||||||
@ -166,7 +165,7 @@ func (w *lintExported) lintValueSpecDoc(vs *ast.ValueSpec, gd *ast.GenDecl, genD
|
|||||||
// Check that none are exported except for the first.
|
// Check that none are exported except for the first.
|
||||||
for _, n := range vs.Names[1:] {
|
for _, n := range vs.Names[1:] {
|
||||||
if ast.IsExported(n.Name) {
|
if ast.IsExported(n.Name) {
|
||||||
w.onFailure(rule.Failure{
|
w.onFailure(linter.Failure{
|
||||||
Node: vs,
|
Node: vs,
|
||||||
Failure: fmt.Sprintf("exported %s %s should have its own declaration", kind, n.Name),
|
Failure: fmt.Sprintf("exported %s %s should have its own declaration", kind, n.Name),
|
||||||
Confidence: 1,
|
Confidence: 1,
|
||||||
@ -190,7 +189,7 @@ func (w *lintExported) lintValueSpecDoc(vs *ast.ValueSpec, gd *ast.GenDecl, genD
|
|||||||
if kind == "const" && gd.Lparen.IsValid() {
|
if kind == "const" && gd.Lparen.IsValid() {
|
||||||
block = " (or a comment on this block)"
|
block = " (or a comment on this block)"
|
||||||
}
|
}
|
||||||
w.onFailure(rule.Failure{
|
w.onFailure(linter.Failure{
|
||||||
Node: vs,
|
Node: vs,
|
||||||
Failure: fmt.Sprintf("exported %s %s should have comment%s or be unexported", kind, name, block),
|
Failure: fmt.Sprintf("exported %s %s should have comment%s or be unexported", kind, name, block),
|
||||||
Confidence: 1,
|
Confidence: 1,
|
||||||
@ -210,7 +209,7 @@ func (w *lintExported) lintValueSpecDoc(vs *ast.ValueSpec, gd *ast.GenDecl, genD
|
|||||||
}
|
}
|
||||||
prefix := name + " "
|
prefix := name + " "
|
||||||
if !strings.HasPrefix(doc.Text(), prefix) {
|
if !strings.HasPrefix(doc.Text(), prefix) {
|
||||||
w.onFailure(rule.Failure{
|
w.onFailure(linter.Failure{
|
||||||
Node: doc,
|
Node: doc,
|
||||||
Failure: fmt.Sprintf(`comment on exported %s %s should be of the form "%s..."`, kind, name, prefix),
|
Failure: fmt.Sprintf(`comment on exported %s %s should be of the form "%s..."`, kind, name, prefix),
|
||||||
Confidence: 1,
|
Confidence: 1,
|
@ -1,24 +1,23 @@
|
|||||||
package defaultrule
|
package rule
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/ast"
|
"go/ast"
|
||||||
|
|
||||||
"github.com/mgechev/revive/file"
|
"github.com/mgechev/revive/linter"
|
||||||
"github.com/mgechev/revive/rule"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ImportsRule lints given else constructs.
|
// ImportsRule lints given else constructs.
|
||||||
type ImportsRule struct{}
|
type ImportsRule struct{}
|
||||||
|
|
||||||
// Apply applies the rule to given file.
|
// Apply applies the rule to given file.
|
||||||
func (r *ImportsRule) Apply(file *file.File, arguments rule.Arguments) []rule.Failure {
|
func (r *ImportsRule) Apply(file *linter.File, arguments linter.Arguments) []linter.Failure {
|
||||||
var failures []rule.Failure
|
var failures []linter.Failure
|
||||||
|
|
||||||
fileAst := file.GetAST()
|
fileAst := file.GetAST()
|
||||||
walker := lintImports{
|
walker := lintImports{
|
||||||
file: file,
|
file: file,
|
||||||
fileAst: fileAst,
|
fileAst: fileAst,
|
||||||
onFailure: func(failure rule.Failure) {
|
onFailure: func(failure linter.Failure) {
|
||||||
failures = append(failures, failure)
|
failures = append(failures, failure)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -34,15 +33,15 @@ func (r *ImportsRule) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type lintImports struct {
|
type lintImports struct {
|
||||||
file *file.File
|
file *linter.File
|
||||||
fileAst *ast.File
|
fileAst *ast.File
|
||||||
onFailure func(rule.Failure)
|
onFailure func(linter.Failure)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w lintImports) Visit(n ast.Node) ast.Visitor {
|
func (w lintImports) Visit(n ast.Node) ast.Visitor {
|
||||||
for _, is := range w.fileAst.Imports {
|
for _, is := range w.fileAst.Imports {
|
||||||
if is.Name != nil && is.Name.Name == "." && !isTest(w.file) {
|
if is.Name != nil && is.Name.Name == "." && !isTest(w.file) {
|
||||||
w.onFailure(rule.Failure{
|
w.onFailure(linter.Failure{
|
||||||
Confidence: 1,
|
Confidence: 1,
|
||||||
Failure: "should not use dot imports",
|
Failure: "should not use dot imports",
|
||||||
Node: is,
|
Node: is,
|
@ -1,4 +1,4 @@
|
|||||||
package defaultrule
|
package rule
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
@ -1,4 +1,4 @@
|
|||||||
package defaultrule
|
package rule
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -7,16 +7,15 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/mgechev/revive/file"
|
"github.com/mgechev/revive/linter"
|
||||||
"github.com/mgechev/revive/rule"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// NamesRule lints given else constructs.
|
// NamesRule lints given else constructs.
|
||||||
type NamesRule struct{}
|
type NamesRule struct{}
|
||||||
|
|
||||||
// Apply applies the rule to given file.
|
// Apply applies the rule to given file.
|
||||||
func (r *NamesRule) Apply(file *file.File, arguments rule.Arguments) []rule.Failure {
|
func (r *NamesRule) Apply(file *linter.File, arguments linter.Arguments) []linter.Failure {
|
||||||
var failures []rule.Failure
|
var failures []linter.Failure
|
||||||
|
|
||||||
if isTest(file) {
|
if isTest(file) {
|
||||||
return failures
|
return failures
|
||||||
@ -26,7 +25,7 @@ func (r *NamesRule) Apply(file *file.File, arguments rule.Arguments) []rule.Fail
|
|||||||
walker := lintNames{
|
walker := lintNames{
|
||||||
file: file,
|
file: file,
|
||||||
fileAst: fileAst,
|
fileAst: fileAst,
|
||||||
onFailure: func(failure rule.Failure) {
|
onFailure: func(failure linter.Failure) {
|
||||||
failures = append(failures, failure)
|
failures = append(failures, failure)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -42,11 +41,11 @@ func (r *NamesRule) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type lintNames struct {
|
type lintNames struct {
|
||||||
file *file.File
|
file *linter.File
|
||||||
fileAst *ast.File
|
fileAst *ast.File
|
||||||
lastGen *ast.GenDecl
|
lastGen *ast.GenDecl
|
||||||
genDeclMissingComments map[*ast.GenDecl]bool
|
genDeclMissingComments map[*ast.GenDecl]bool
|
||||||
onFailure func(rule.Failure)
|
onFailure func(linter.Failure)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *lintNames) Visit(n ast.Node) ast.Visitor {
|
func (w *lintNames) Visit(n ast.Node) ast.Visitor {
|
||||||
@ -149,7 +148,7 @@ func (w *lintNames) check(id *ast.Ident, thing string) {
|
|||||||
|
|
||||||
// Handle two common styles from other languages that don't belong in Go.
|
// 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, "_") {
|
if len(id.Name) >= 5 && allCapsRE.MatchString(id.Name) && strings.Contains(id.Name, "_") {
|
||||||
w.onFailure(rule.Failure{
|
w.onFailure(linter.Failure{
|
||||||
Failure: fmt.Sprintf("don't use ALL_CAPS in Go names; use CamelCase"),
|
Failure: fmt.Sprintf("don't use ALL_CAPS in Go names; use CamelCase"),
|
||||||
Confidence: 0.8,
|
Confidence: 0.8,
|
||||||
Node: id,
|
Node: id,
|
||||||
@ -158,7 +157,7 @@ func (w *lintNames) check(id *ast.Ident, thing string) {
|
|||||||
}
|
}
|
||||||
if len(id.Name) > 2 && id.Name[0] == 'k' && id.Name[1] >= 'A' && id.Name[1] <= 'Z' {
|
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:]
|
should := string(id.Name[1]+'a'-'A') + id.Name[2:]
|
||||||
w.onFailure(rule.Failure{
|
w.onFailure(linter.Failure{
|
||||||
Failure: fmt.Sprintf("don't use leading k in Go names; %s %s should be %s", thing, id.Name, should),
|
Failure: fmt.Sprintf("don't use leading k in Go names; %s %s should be %s", thing, id.Name, should),
|
||||||
Confidence: 0.8,
|
Confidence: 0.8,
|
||||||
Node: id,
|
Node: id,
|
||||||
@ -171,14 +170,14 @@ func (w *lintNames) check(id *ast.Ident, thing string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(id.Name) > 2 && strings.Contains(id.Name[1:], "_") {
|
if len(id.Name) > 2 && strings.Contains(id.Name[1:], "_") {
|
||||||
w.onFailure(rule.Failure{
|
w.onFailure(linter.Failure{
|
||||||
Failure: fmt.Sprintf("don't use underscores in Go names; %s %s should be %s", thing, id.Name, should),
|
Failure: fmt.Sprintf("don't use underscores in Go names; %s %s should be %s", thing, id.Name, should),
|
||||||
Confidence: 0.9,
|
Confidence: 0.9,
|
||||||
Node: id,
|
Node: id,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.onFailure(rule.Failure{
|
w.onFailure(linter.Failure{
|
||||||
Failure: fmt.Sprintf("%s %s should be %s", thing, id.Name, should),
|
Failure: fmt.Sprintf("%s %s should be %s", thing, id.Name, should),
|
||||||
Confidence: 0.8,
|
Confidence: 0.8,
|
||||||
Node: id,
|
Node: id,
|
@ -1,21 +1,20 @@
|
|||||||
package defaultrule
|
package rule
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/token"
|
"go/token"
|
||||||
|
|
||||||
"github.com/mgechev/revive/file"
|
"github.com/mgechev/revive/linter"
|
||||||
"github.com/mgechev/revive/rule"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// LintElseRule lints given else constructs.
|
// LintElseRule lints given else constructs.
|
||||||
type LintElseRule struct{}
|
type LintElseRule struct{}
|
||||||
|
|
||||||
// Apply applies the rule to given file.
|
// Apply applies the rule to given file.
|
||||||
func (r *LintElseRule) Apply(file *file.File, arguments rule.Arguments) []rule.Failure {
|
func (r *LintElseRule) Apply(file *linter.File, arguments linter.Arguments) []linter.Failure {
|
||||||
var failures []rule.Failure
|
var failures []linter.Failure
|
||||||
|
|
||||||
onFailure := func(failure rule.Failure) {
|
onFailure := func(failure linter.Failure) {
|
||||||
failures = append(failures, failure)
|
failures = append(failures, failure)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,7 +30,7 @@ func (r *LintElseRule) Name() string {
|
|||||||
|
|
||||||
type lintElse struct {
|
type lintElse struct {
|
||||||
ignore map[*ast.IfStmt]bool
|
ignore map[*ast.IfStmt]bool
|
||||||
onFailure func(rule.Failure)
|
onFailure func(linter.Failure)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w lintElse) Visit(node ast.Node) ast.Visitor {
|
func (w lintElse) Visit(node ast.Node) ast.Visitor {
|
||||||
@ -65,9 +64,9 @@ func (w lintElse) Visit(node ast.Node) ast.Visitor {
|
|||||||
if shortDecl {
|
if shortDecl {
|
||||||
extra = " (move short variable declaration to its own line if necessary)"
|
extra = " (move short variable declaration to its own line if necessary)"
|
||||||
}
|
}
|
||||||
w.onFailure(rule.Failure{
|
w.onFailure(linter.Failure{
|
||||||
Failure: "if block ends with a return statement, so drop this else and outdent its block" + extra,
|
Failure: "if block ends with a return statement, so drop this else and outdent its block" + extra,
|
||||||
Type: rule.FailureTypeWarning,
|
Type: linter.FailureTypeWarning,
|
||||||
Node: ifStmt.Else,
|
Node: ifStmt.Else,
|
||||||
})
|
})
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package defaultrule
|
package rule
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -6,8 +6,7 @@ import (
|
|||||||
"go/token"
|
"go/token"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mgechev/revive/file"
|
"github.com/mgechev/revive/linter"
|
||||||
"github.com/mgechev/revive/rule"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// PackageCommentsRule lints the package comments. It complains if
|
// PackageCommentsRule lints the package comments. It complains if
|
||||||
@ -18,14 +17,14 @@ import (
|
|||||||
type PackageCommentsRule struct{}
|
type PackageCommentsRule struct{}
|
||||||
|
|
||||||
// Apply applies the rule to given file.
|
// Apply applies the rule to given file.
|
||||||
func (r *PackageCommentsRule) Apply(file *file.File, arguments rule.Arguments) []rule.Failure {
|
func (r *PackageCommentsRule) Apply(file *linter.File, arguments linter.Arguments) []linter.Failure {
|
||||||
var failures []rule.Failure
|
var failures []linter.Failure
|
||||||
|
|
||||||
if isTest(file) {
|
if isTest(file) {
|
||||||
return failures
|
return failures
|
||||||
}
|
}
|
||||||
|
|
||||||
onFailure := func(failure rule.Failure) {
|
onFailure := func(failure linter.Failure) {
|
||||||
failures = append(failures, failure)
|
failures = append(failures, failure)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,8 +41,8 @@ func (r *PackageCommentsRule) Name() string {
|
|||||||
|
|
||||||
type lintPackageComments struct {
|
type lintPackageComments struct {
|
||||||
fileAst *ast.File
|
fileAst *ast.File
|
||||||
file *file.File
|
file *linter.File
|
||||||
onFailure func(rule.Failure)
|
onFailure func(linter.Failure)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *lintPackageComments) Visit(n ast.Node) ast.Visitor {
|
func (l *lintPackageComments) Visit(n ast.Node) ast.Visitor {
|
||||||
@ -73,17 +72,17 @@ func (l *lintPackageComments) Visit(n ast.Node) ast.Visitor {
|
|||||||
Line: endPos.Line + 1,
|
Line: endPos.Line + 1,
|
||||||
Column: 1,
|
Column: 1,
|
||||||
}
|
}
|
||||||
l.onFailure(rule.Failure{
|
l.onFailure(linter.Failure{
|
||||||
Failure: "package comment is detached; there should be no blank lines between it and the package statement",
|
Failure: "package comment is detached; there should be no blank lines between it and the package statement",
|
||||||
Confidence: 0.9,
|
Confidence: 0.9,
|
||||||
Position: rule.FailurePosition{Start: pos},
|
Position: linter.FailurePosition{Start: pos},
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if l.fileAst.Doc == nil {
|
if l.fileAst.Doc == nil {
|
||||||
l.onFailure(rule.Failure{
|
l.onFailure(linter.Failure{
|
||||||
Failure: "should have a package comment, unless it's in another file for this package",
|
Failure: "should have a package comment, unless it's in another file for this package",
|
||||||
Confidence: 0.2,
|
Confidence: 0.2,
|
||||||
Node: l.fileAst.Name,
|
Node: l.fileAst.Name,
|
||||||
@ -92,7 +91,7 @@ func (l *lintPackageComments) Visit(n ast.Node) ast.Visitor {
|
|||||||
}
|
}
|
||||||
s := l.fileAst.Doc.Text()
|
s := l.fileAst.Doc.Text()
|
||||||
if ts := strings.TrimLeft(s, " \t"); ts != s {
|
if ts := strings.TrimLeft(s, " \t"); ts != s {
|
||||||
l.onFailure(rule.Failure{
|
l.onFailure(linter.Failure{
|
||||||
Failure: "package comment should not have leading space",
|
Failure: "package comment should not have leading space",
|
||||||
Confidence: 1,
|
Confidence: 1,
|
||||||
Node: l.fileAst.Doc,
|
Node: l.fileAst.Doc,
|
||||||
@ -101,7 +100,7 @@ func (l *lintPackageComments) Visit(n ast.Node) ast.Visitor {
|
|||||||
}
|
}
|
||||||
// Only non-main packages need to keep to this form.
|
// Only non-main packages need to keep to this form.
|
||||||
if l.fileAst.Name.Name != "main" && !strings.HasPrefix(s, prefix) {
|
if l.fileAst.Name.Name != "main" && !strings.HasPrefix(s, prefix) {
|
||||||
l.onFailure(rule.Failure{
|
l.onFailure(linter.Failure{
|
||||||
Failure: fmt.Sprintf(`package comment should be of the form "%s..."`, prefix),
|
Failure: fmt.Sprintf(`package comment should be of the form "%s..."`, prefix),
|
||||||
Confidence: 1,
|
Confidence: 1,
|
||||||
Node: l.fileAst.Doc,
|
Node: l.fileAst.Doc,
|
@ -1,4 +1,4 @@
|
|||||||
package defaultrule
|
package rule
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
@ -1,21 +1,20 @@
|
|||||||
package defaultrule
|
package rule
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
|
|
||||||
"github.com/mgechev/revive/file"
|
"github.com/mgechev/revive/linter"
|
||||||
"github.com/mgechev/revive/rule"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// LintRangesRule lints given else constructs.
|
// LintRangesRule lints given else constructs.
|
||||||
type LintRangesRule struct{}
|
type LintRangesRule struct{}
|
||||||
|
|
||||||
// Apply applies the rule to given file.
|
// Apply applies the rule to given file.
|
||||||
func (r *LintRangesRule) Apply(file *file.File, arguments rule.Arguments) []rule.Failure {
|
func (r *LintRangesRule) Apply(file *linter.File, arguments linter.Arguments) []linter.Failure {
|
||||||
var failures []rule.Failure
|
var failures []linter.Failure
|
||||||
|
|
||||||
onFailure := func(failure rule.Failure) {
|
onFailure := func(failure linter.Failure) {
|
||||||
failures = append(failures, failure)
|
failures = append(failures, failure)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,7 +31,7 @@ func (r *LintRangesRule) Name() string {
|
|||||||
|
|
||||||
type lintRanges struct {
|
type lintRanges struct {
|
||||||
file *ast.File
|
file *ast.File
|
||||||
onFailure func(rule.Failure)
|
onFailure func(linter.Failure)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *lintRanges) Visit(node ast.Node) ast.Visitor {
|
func (w *lintRanges) Visit(node ast.Node) ast.Visitor {
|
||||||
@ -49,7 +48,7 @@ func (w *lintRanges) Visit(node ast.Node) ast.Visitor {
|
|||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
w.onFailure(rule.Failure{
|
w.onFailure(linter.Failure{
|
||||||
Failure: fmt.Sprintf("should omit 2nd value from range; this loop is equivalent to `for %s %s range ...`", render(rs.Key), rs.Tok),
|
Failure: fmt.Sprintf("should omit 2nd value from range; this loop is equivalent to `for %s %s range ...`", render(rs.Key), rs.Tok),
|
||||||
Confidence: 1,
|
Confidence: 1,
|
||||||
Node: rs.Value,
|
Node: rs.Value,
|
@ -1,4 +1,4 @@
|
|||||||
package defaultrule
|
package rule
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -7,7 +7,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mgechev/revive/file"
|
"github.com/mgechev/revive/linter"
|
||||||
)
|
)
|
||||||
|
|
||||||
const styleGuideBase = "https://golang.org/wiki/CodeReviewComments"
|
const styleGuideBase = "https://golang.org/wiki/CodeReviewComments"
|
||||||
@ -16,7 +16,7 @@ const styleGuideBase = "https://golang.org/wiki/CodeReviewComments"
|
|||||||
// If id == nil, the answer is false.
|
// If id == nil, the answer is false.
|
||||||
func isBlank(id *ast.Ident) bool { return id != nil && id.Name == "_" }
|
func isBlank(id *ast.Ident) bool { return id != nil && id.Name == "_" }
|
||||||
|
|
||||||
func isTest(f *file.File) bool {
|
func isTest(f *linter.File) bool {
|
||||||
return strings.HasSuffix(f.Name, "_test.go")
|
return strings.HasSuffix(f.Name, "_test.go")
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package defaultrule
|
package rule
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -7,22 +7,21 @@ import (
|
|||||||
"go/printer"
|
"go/printer"
|
||||||
"go/token"
|
"go/token"
|
||||||
|
|
||||||
"github.com/mgechev/revive/file"
|
"github.com/mgechev/revive/linter"
|
||||||
"github.com/mgechev/revive/rule"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// VarDeclarationsRule lints given else constructs.
|
// VarDeclarationsRule lints given else constructs.
|
||||||
type VarDeclarationsRule struct{}
|
type VarDeclarationsRule struct{}
|
||||||
|
|
||||||
// Apply applies the rule to given file.
|
// Apply applies the rule to given file.
|
||||||
func (r *VarDeclarationsRule) Apply(file *file.File, arguments rule.Arguments) []rule.Failure {
|
func (r *VarDeclarationsRule) Apply(file *linter.File, arguments linter.Arguments) []linter.Failure {
|
||||||
var failures []rule.Failure
|
var failures []linter.Failure
|
||||||
|
|
||||||
fileAst := file.GetAST()
|
fileAst := file.GetAST()
|
||||||
walker := &lintVarDeclarations{
|
walker := &lintVarDeclarations{
|
||||||
file: file,
|
file: file,
|
||||||
fileAst: fileAst,
|
fileAst: fileAst,
|
||||||
onFailure: func(failure rule.Failure) {
|
onFailure: func(failure linter.Failure) {
|
||||||
failures = append(failures, failure)
|
failures = append(failures, failure)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -39,9 +38,9 @@ func (r *VarDeclarationsRule) Name() string {
|
|||||||
|
|
||||||
type lintVarDeclarations struct {
|
type lintVarDeclarations struct {
|
||||||
fileAst *ast.File
|
fileAst *ast.File
|
||||||
file *file.File
|
file *linter.File
|
||||||
lastGen *ast.GenDecl
|
lastGen *ast.GenDecl
|
||||||
onFailure func(rule.Failure)
|
onFailure func(linter.Failure)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *lintVarDeclarations) Visit(node ast.Node) ast.Visitor {
|
func (w *lintVarDeclarations) Visit(node ast.Node) ast.Visitor {
|
||||||
@ -73,7 +72,7 @@ func (w *lintVarDeclarations) Visit(node ast.Node) ast.Visitor {
|
|||||||
zero = true
|
zero = true
|
||||||
}
|
}
|
||||||
if zero {
|
if zero {
|
||||||
w.onFailure(rule.Failure{
|
w.onFailure(linter.Failure{
|
||||||
Confidence: 0.9,
|
Confidence: 0.9,
|
||||||
Node: rhs,
|
Node: rhs,
|
||||||
Failure: fmt.Sprintf("should drop = %s from declaration of var %s; it is the zero value", render(rhs), v.Names[0]),
|
Failure: fmt.Sprintf("should drop = %s from declaration of var %s; it is the zero value", render(rhs), v.Names[0]),
|
Reference in New Issue
Block a user