mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-04 02:04:11 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			273 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
		
			Vendored
		
	
	
	
			
		
		
	
	
			273 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Go
		
	
	
	
		
			Vendored
		
	
	
	
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
 | 
						|
}
 |