136 lines
3.3 KiB
Go
136 lines
3.3 KiB
Go
package lintpack
|
|
|
|
import (
|
|
"go/ast"
|
|
"go/token"
|
|
"go/types"
|
|
"strconv"
|
|
|
|
"github.com/go-toolsmith/astfmt"
|
|
)
|
|
|
|
// 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
|
|
|
|
Params parameters
|
|
|
|
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,
|
|
})
|
|
}
|
|
|
|
func resolvePkgObjects(ctx *Context, f *ast.File) {
|
|
ctx.PkgObjects = make(map[*types.PkgName]string, len(f.Imports))
|
|
|
|
for _, spec := range f.Imports {
|
|
if spec.Name != nil {
|
|
obj := ctx.TypesInfo.ObjectOf(spec.Name)
|
|
ctx.PkgObjects[obj.(*types.PkgName)] = spec.Name.Name
|
|
} else {
|
|
obj := ctx.TypesInfo.Implicits[spec]
|
|
ctx.PkgObjects[obj.(*types.PkgName)] = obj.Name()
|
|
}
|
|
}
|
|
}
|
|
|
|
func resolvePkgRenames(ctx *Context, f *ast.File) {
|
|
ctx.PkgRenames = make(map[string]string)
|
|
|
|
for _, spec := range f.Imports {
|
|
if spec.Name != nil {
|
|
path, err := strconv.Unquote(spec.Path.Value)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
ctx.PkgRenames[path] = spec.Name.Name
|
|
}
|
|
}
|
|
}
|