diff options
Diffstat (limited to 'vendor/github.com/mgechev/revive/rule/exported.go')
-rw-r--r-- | vendor/github.com/mgechev/revive/rule/exported.go | 272 |
1 files changed, 272 insertions, 0 deletions
diff --git a/vendor/github.com/mgechev/revive/rule/exported.go b/vendor/github.com/mgechev/revive/rule/exported.go new file mode 100644 index 0000000000..b68f2bacc1 --- /dev/null +++ b/vendor/github.com/mgechev/revive/rule/exported.go @@ -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 +} |