249 lines
6.8 KiB
Go
249 lines
6.8 KiB
Go
package lintpack
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"go/token"
|
|
"go/types"
|
|
|
|
"github.com/go-toolsmith/astfmt"
|
|
)
|
|
|
|
// CheckerCollection provides additional information for a group of checkers.
|
|
type CheckerCollection struct {
|
|
// URL is a link for a main source of information on the collection.
|
|
URL string
|
|
}
|
|
|
|
// AddChecker registers a new checker into a checkers pool.
|
|
// Constructor is used to create a new checker instance.
|
|
// Checker name (defined in CheckerInfo.Name) must be unique.
|
|
//
|
|
// CheckerInfo.Collection is automatically set to the coll (the receiver).
|
|
//
|
|
// If checker is never needed, for example if it is disabled,
|
|
// constructor will not be called.
|
|
func (coll *CheckerCollection) AddChecker(info *CheckerInfo, constructor func(*CheckerContext) FileWalker) {
|
|
if coll == nil {
|
|
panic(fmt.Sprintf("adding checker to a nil collection"))
|
|
}
|
|
info.Collection = coll
|
|
addChecker(info, constructor)
|
|
}
|
|
|
|
// CheckerParam describes a single checker customizable parameter.
|
|
type CheckerParam struct {
|
|
// Value holds parameter bound value.
|
|
// It might be overwritten by the integrating linter.
|
|
//
|
|
// Permitted types include:
|
|
// - int
|
|
// - bool
|
|
// - string
|
|
Value interface{}
|
|
|
|
// Usage gives an overview about what parameter does.
|
|
Usage string
|
|
}
|
|
|
|
// CheckerParams holds all checker-specific parameters.
|
|
//
|
|
// Provides convenient access to the loosely typed underlying map.
|
|
type CheckerParams map[string]*CheckerParam
|
|
|
|
// Int lookups pname key in underlying map and type-asserts it to int.
|
|
func (params CheckerParams) Int(pname string) int { return params[pname].Value.(int) }
|
|
|
|
// Bool lookups pname key in underlying map and type-asserts it to bool.
|
|
func (params CheckerParams) Bool(pname string) bool { return params[pname].Value.(bool) }
|
|
|
|
// String lookups pname key in underlying map and type-asserts it to string.
|
|
func (params CheckerParams) String(pname string) string { return params[pname].Value.(string) }
|
|
|
|
// CheckerInfo holds checker metadata and structured documentation.
|
|
type CheckerInfo struct {
|
|
// Name is a checker name.
|
|
Name string
|
|
|
|
// Tags is a list of labels that can be used to enable or disable checker.
|
|
// Common tags are "experimental" and "performance".
|
|
Tags []string
|
|
|
|
// Params declares checker-specific parameters. Optional.
|
|
Params CheckerParams
|
|
|
|
// Summary is a short one sentence description.
|
|
// Should not end with a period.
|
|
Summary string
|
|
|
|
// Details extends summary with additional info. Optional.
|
|
Details string
|
|
|
|
// Before is a code snippet of code that will violate rule.
|
|
Before string
|
|
|
|
// After is a code snippet of fixed code that complies to the rule.
|
|
After string
|
|
|
|
// Note is an optional caution message or advice.
|
|
Note string
|
|
|
|
// Collection establishes a checker-to-collection relationship.
|
|
Collection *CheckerCollection
|
|
}
|
|
|
|
// GetCheckersInfo returns a checkers info list for all registered checkers.
|
|
// The slice is sorted by a checker name.
|
|
//
|
|
// Info objects can be used to instantiate checkers with NewChecker function.
|
|
func GetCheckersInfo() []*CheckerInfo {
|
|
return getCheckersInfo()
|
|
}
|
|
|
|
// HasTag reports whether checker described by the info has specified tag.
|
|
func (info *CheckerInfo) HasTag(tag string) bool {
|
|
for i := range info.Tags {
|
|
if info.Tags[i] == tag {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Checker is an implementation of a check that is described by the associated info.
|
|
type Checker struct {
|
|
// Info is an info object that was used to instantiate this checker.
|
|
Info *CheckerInfo
|
|
|
|
ctx CheckerContext
|
|
|
|
fileWalker FileWalker
|
|
}
|
|
|
|
// Check runs rule checker over file f.
|
|
func (c *Checker) Check(f *ast.File) []Warning {
|
|
c.ctx.warnings = c.ctx.warnings[:0]
|
|
c.fileWalker.WalkFile(f)
|
|
return c.ctx.warnings
|
|
}
|
|
|
|
// Warning represents issue that is found by checker.
|
|
type Warning struct {
|
|
// Node is an AST node that caused warning to trigger.
|
|
// Can be used to obtain proper error location.
|
|
Node ast.Node
|
|
|
|
// Text is warning message without source location info.
|
|
Text string
|
|
}
|
|
|
|
// NewChecker returns initialized checker identified by an info.
|
|
// info must be non-nil.
|
|
// Panics if info describes a checker that was not properly registered.
|
|
func NewChecker(ctx *Context, info *CheckerInfo) *Checker {
|
|
return newChecker(ctx, info)
|
|
}
|
|
|
|
// Context is a readonly state shared among every checker.
|
|
type Context struct {
|
|
// TypesInfo carries parsed packages types information.
|
|
TypesInfo *types.Info
|
|
|
|
// SizesInfo carries alignment and type size information.
|
|
// Arch-dependent.
|
|
SizesInfo types.Sizes
|
|
|
|
// FileSet is a file set that was used during the program loading.
|
|
FileSet *token.FileSet
|
|
|
|
// Pkg describes package that is being checked.
|
|
Pkg *types.Package
|
|
|
|
// Filename is a currently checked file name.
|
|
Filename string
|
|
|
|
// Require records what optional resources are required
|
|
// by the checkers set that use this context.
|
|
//
|
|
// Every require fields makes associated context field
|
|
// to be properly initialized.
|
|
// For example, Context.require.PkgObjects => Context.PkgObjects.
|
|
Require struct {
|
|
PkgObjects bool
|
|
PkgRenames bool
|
|
}
|
|
|
|
// PkgObjects stores all imported packages and their local names.
|
|
PkgObjects map[*types.PkgName]string
|
|
|
|
// PkgRenames maps package path to its local renaming.
|
|
// Contains no entries for packages that were imported without
|
|
// explicit local names.
|
|
PkgRenames map[string]string
|
|
}
|
|
|
|
// NewContext returns new shared context to be used by every checker.
|
|
//
|
|
// All data carried by the context is readonly for checkers,
|
|
// but can be modified by the integrating application.
|
|
func NewContext(fset *token.FileSet, sizes types.Sizes) *Context {
|
|
return &Context{
|
|
FileSet: fset,
|
|
SizesInfo: sizes,
|
|
TypesInfo: &types.Info{},
|
|
}
|
|
}
|
|
|
|
// SetPackageInfo sets package-related metadata.
|
|
//
|
|
// Must be called for every package being checked.
|
|
func (c *Context) SetPackageInfo(info *types.Info, pkg *types.Package) {
|
|
if info != nil {
|
|
// We do this kind of assignment to avoid
|
|
// changing c.typesInfo field address after
|
|
// every re-assignment.
|
|
*c.TypesInfo = *info
|
|
}
|
|
c.Pkg = pkg
|
|
}
|
|
|
|
// SetFileInfo sets file-related metadata.
|
|
//
|
|
// Must be called for every source code file being checked.
|
|
func (c *Context) SetFileInfo(name string, f *ast.File) {
|
|
c.Filename = name
|
|
if c.Require.PkgObjects {
|
|
resolvePkgObjects(c, f)
|
|
}
|
|
if c.Require.PkgRenames {
|
|
resolvePkgRenames(c, f)
|
|
}
|
|
}
|
|
|
|
// CheckerContext is checker-local context copy.
|
|
// Fields that are not from Context itself are writeable.
|
|
type CheckerContext struct {
|
|
*Context
|
|
|
|
// printer used to format warning text.
|
|
printer *astfmt.Printer
|
|
|
|
warnings []Warning
|
|
}
|
|
|
|
// Warn adds a Warning to checker output.
|
|
func (ctx *CheckerContext) Warn(node ast.Node, format string, args ...interface{}) {
|
|
ctx.warnings = append(ctx.warnings, Warning{
|
|
Text: ctx.printer.Sprintf(format, args...),
|
|
Node: node,
|
|
})
|
|
}
|
|
|
|
// FileWalker is an interface every checker should implement.
|
|
//
|
|
// The WalkFile method is executed for every Go file inside the
|
|
// package that is being checked.
|
|
type FileWalker interface {
|
|
WalkFile(*ast.File)
|
|
}
|