1
0
mirror of https://github.com/mgechev/revive.git synced 2024-12-10 10:40:23 +02:00
revive/lint/file.go

258 lines
6.4 KiB
Go
Raw Normal View History

2018-01-25 01:44:03 +02:00
package lint
2017-08-28 01:57:16 +02:00
import (
2018-01-22 04:41:38 +02:00
"bytes"
2017-08-28 01:57:16 +02:00
"go/ast"
"go/parser"
2018-01-22 04:41:38 +02:00
"go/printer"
2017-08-28 01:57:16 +02:00
"go/token"
2018-01-22 04:41:38 +02:00
"go/types"
2018-01-22 04:27:32 +02:00
"math"
"regexp"
"strings"
2017-08-28 01:57:16 +02:00
)
// File abstraction used for representing files.
type File struct {
Name string
2018-01-22 04:41:38 +02:00
Pkg *Package
2018-01-22 04:29:07 +02:00
content []byte
2018-01-22 04:48:51 +02:00
AST *ast.File
2017-08-28 01:57:16 +02:00
}
2018-01-24 05:13:02 +02:00
// IsTest returns if the file contains tests.
func (f *File) IsTest() bool { return strings.HasSuffix(f.Name, "_test.go") }
2018-01-25 20:35:27 +02:00
// Content returns the file's content.
func (f *File) Content() []byte {
return f.content
}
2018-01-22 04:04:41 +02:00
// NewFile creates a new file
func NewFile(name string, content []byte, pkg *Package) (*File, error) {
2018-01-22 04:29:07 +02:00
f, err := parser.ParseFile(pkg.fset, name, content, parser.ParseComments)
2017-08-28 01:57:16 +02:00
if err != nil {
return nil, err
}
return &File{
Name: name,
2018-01-22 04:29:07 +02:00
content: content,
2018-01-22 04:41:38 +02:00
Pkg: pkg,
2018-01-22 04:48:51 +02:00
AST: f,
2017-08-28 01:57:16 +02:00
}, nil
}
// ToPosition returns line and column for given position.
func (f *File) ToPosition(pos token.Pos) token.Position {
2018-01-22 04:41:38 +02:00
return f.Pkg.fset.Position(pos)
2017-08-28 01:57:16 +02:00
}
2018-01-22 04:41:38 +02:00
// 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)
}
2018-01-22 04:41:38 +02:00
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
}
2018-01-22 04:04:41 +02:00
func (f *File) isMain() bool {
2018-01-22 04:48:51 +02:00
if f.AST.Name.Name == "main" {
2018-01-22 04:04:41 +02:00
return true
}
return false
}
2018-01-22 04:27:32 +02:00
2018-01-28 03:01:18 +02:00
func (f *File) lint(rules []Rule, config Config, failures chan Failure) {
rulesConfig := config.Rules
2018-01-22 04:27:32 +02:00
disabledIntervals := f.disabledIntervals(rules)
for _, currentRule := range rules {
2018-01-28 03:01:18 +02:00
ruleConfig := rulesConfig[currentRule.Name()]
currentFailures := currentRule.Apply(f, ruleConfig.Arguments)
2018-01-22 04:27:32 +02:00
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)
2018-01-24 03:14:23 +02:00
for _, failure := range currentFailures {
2018-01-28 03:01:18 +02:00
if failure.Confidence >= config.Confidence {
failures <- failure
}
2018-01-24 03:14:23 +02:00
}
2018-01-22 04:27:32 +02:00
}
}
type enableDisableConfig struct {
enabled bool
position int
}
func (f *File) disabledIntervals(rules []Rule) disabledIntervalsMap {
2019-01-18 17:33:40 +02:00
re := regexp.MustCompile(`^\s*revive:(enable|disable)(?:-(line|next-line))?(:)?([^\s]*)?(\s|$)`)
2018-01-22 04:27:32 +02:00
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) {
text := c.Text()
parts := re.FindStringSubmatch(text)
if len(parts) == 0 {
return
}
2019-01-18 17:33:40 +02:00
2018-01-22 04:27:32 +02:00
ruleNames := []string{}
2019-01-18 17:33:40 +02:00
if len(parts) > 4 {
tempNames := strings.Split(parts[4], ",")
2018-01-22 04:27:32 +02:00
for _, name := range tempNames {
name = strings.Trim(name, "\n")
if len(name) > 0 {
ruleNames = append(ruleNames, name)
}
}
}
// TODO: optimize
if len(ruleNames) == 0 {
for _, rule := range rules {
ruleNames = append(ruleNames, rule.Name())
}
}
handleRules(filename, parts[2], parts[1] == "enable", line, ruleNames)
}
2018-01-22 04:48:51 +02:00
comments := f.AST.Comments
2018-01-22 04:27:32 +02:00
for _, c := range comments {
handleComment(f.Name, c, f.ToPosition(c.Pos()).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
}