mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-11 11:06:29 +00:00
c28f7cb29f
Initial part of #435
193 lines
4.5 KiB
Go
193 lines
4.5 KiB
Go
// structcheck
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package structcheck
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/token"
|
|
"go/types"
|
|
|
|
"golang.org/x/tools/go/loader"
|
|
)
|
|
|
|
var (
|
|
assignmentsOnly = flag.Bool("structcheck.a", false, "Count assignments only")
|
|
loadTestFiles = flag.Bool("structcheck.t", false, "Load test files too")
|
|
buildTags = flag.String("structcheck.tags", "", "Build tags")
|
|
)
|
|
|
|
type visitor struct {
|
|
prog *loader.Program
|
|
pkg *loader.PackageInfo
|
|
m map[types.Type]map[string]int
|
|
skip map[types.Type]struct{}
|
|
}
|
|
|
|
func (v *visitor) decl(t types.Type, fieldName string) {
|
|
if _, ok := v.m[t]; !ok {
|
|
v.m[t] = make(map[string]int)
|
|
}
|
|
if _, ok := v.m[t][fieldName]; !ok {
|
|
v.m[t][fieldName] = 0
|
|
}
|
|
}
|
|
|
|
func (v *visitor) assignment(t types.Type, fieldName string) {
|
|
if _, ok := v.m[t]; !ok {
|
|
v.m[t] = make(map[string]int)
|
|
}
|
|
if _, ok := v.m[t][fieldName]; ok {
|
|
v.m[t][fieldName]++
|
|
} else {
|
|
v.m[t][fieldName] = 1
|
|
}
|
|
}
|
|
|
|
func (v *visitor) typeSpec(node *ast.TypeSpec) {
|
|
if strukt, ok := node.Type.(*ast.StructType); ok {
|
|
t := v.pkg.Info.Defs[node.Name].Type()
|
|
for _, f := range strukt.Fields.List {
|
|
if len(f.Names) > 0 {
|
|
fieldName := f.Names[0].Name
|
|
v.decl(t, fieldName)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (v *visitor) typeAndFieldName(expr *ast.SelectorExpr) (types.Type, string, bool) {
|
|
selection := v.pkg.Info.Selections[expr]
|
|
if selection == nil {
|
|
return nil, "", false
|
|
}
|
|
recv := selection.Recv()
|
|
if ptr, ok := recv.(*types.Pointer); ok {
|
|
recv = ptr.Elem()
|
|
}
|
|
return recv, selection.Obj().Name(), true
|
|
}
|
|
|
|
func (v *visitor) assignStmt(node *ast.AssignStmt) {
|
|
for _, lhs := range node.Lhs {
|
|
var selector *ast.SelectorExpr
|
|
switch expr := lhs.(type) {
|
|
case *ast.SelectorExpr:
|
|
selector = expr
|
|
case *ast.IndexExpr:
|
|
if expr, ok := expr.X.(*ast.SelectorExpr); ok {
|
|
selector = expr
|
|
}
|
|
}
|
|
if selector != nil {
|
|
if t, fn, ok := v.typeAndFieldName(selector); ok {
|
|
v.assignment(t, fn)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (v *visitor) compositeLiteral(node *ast.CompositeLit) {
|
|
t := v.pkg.Info.Types[node.Type].Type
|
|
for _, expr := range node.Elts {
|
|
if kv, ok := expr.(*ast.KeyValueExpr); ok {
|
|
if ident, ok := kv.Key.(*ast.Ident); ok {
|
|
v.assignment(t, ident.Name)
|
|
}
|
|
} else {
|
|
// Struct literal with positional values.
|
|
// All the fields are assigned.
|
|
v.skip[t] = struct{}{}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func (v *visitor) Visit(node ast.Node) ast.Visitor {
|
|
switch node := node.(type) {
|
|
case *ast.TypeSpec:
|
|
v.typeSpec(node)
|
|
|
|
case *ast.AssignStmt:
|
|
if *assignmentsOnly {
|
|
v.assignStmt(node)
|
|
}
|
|
|
|
case *ast.SelectorExpr:
|
|
if !*assignmentsOnly {
|
|
if t, fn, ok := v.typeAndFieldName(node); ok {
|
|
v.assignment(t, fn)
|
|
}
|
|
}
|
|
|
|
case *ast.CompositeLit:
|
|
v.compositeLiteral(node)
|
|
}
|
|
|
|
return v
|
|
}
|
|
|
|
type Issue struct {
|
|
Pos token.Position
|
|
Type string
|
|
FieldName string
|
|
}
|
|
|
|
func Run(program *loader.Program, reportExported bool) []Issue {
|
|
var issues []Issue
|
|
for _, pkg := range program.InitialPackages() {
|
|
visitor := &visitor{
|
|
m: make(map[types.Type]map[string]int),
|
|
skip: make(map[types.Type]struct{}),
|
|
prog: program,
|
|
pkg: pkg,
|
|
}
|
|
for _, f := range pkg.Files {
|
|
ast.Walk(visitor, f)
|
|
}
|
|
|
|
for t := range visitor.m {
|
|
if _, skip := visitor.skip[t]; skip {
|
|
continue
|
|
}
|
|
for fieldName, v := range visitor.m[t] {
|
|
if !reportExported && ast.IsExported(fieldName) {
|
|
continue
|
|
}
|
|
if v == 0 {
|
|
field, _, _ := types.LookupFieldOrMethod(t, false, pkg.Pkg, fieldName)
|
|
if field == nil {
|
|
fmt.Printf("%s: unknown field or method: %s.%s\n", pkg.Pkg.Path(), t, fieldName)
|
|
continue
|
|
}
|
|
if fieldName == "XMLName" {
|
|
if named, ok := field.Type().(*types.Named); ok && named.Obj().Pkg().Path() == "encoding/xml" {
|
|
continue
|
|
}
|
|
}
|
|
pos := program.Fset.Position(field.Pos())
|
|
issues = append(issues, Issue{
|
|
Pos: pos,
|
|
Type: types.TypeString(t, nil),
|
|
FieldName: fieldName,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return issues
|
|
}
|