summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/mgechev/revive/lint/file.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/mgechev/revive/lint/file.go')
-rw-r--r--vendor/github.com/mgechev/revive/lint/file.go278
1 files changed, 278 insertions, 0 deletions
diff --git a/vendor/github.com/mgechev/revive/lint/file.go b/vendor/github.com/mgechev/revive/lint/file.go
new file mode 100644
index 0000000000..8bef9c220c
--- /dev/null
+++ b/vendor/github.com/mgechev/revive/lint/file.go
@@ -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
+}