Add staticcheck tool

This commit is contained in:
dwrz
2026-06-06 01:17:20 +00:00
parent 6fb63c8c90
commit ad58cd78ff
315 changed files with 56337 additions and 4 deletions

View File

@@ -0,0 +1,193 @@
package s1016
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"go/version"
"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
"honnef.co/go/tools/analysis/facts/generated"
"honnef.co/go/tools/analysis/lint"
"honnef.co/go/tools/analysis/report"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/inspector"
)
var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{
Analyzer: &analysis.Analyzer{
Name: "S1016",
Run: run,
Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
},
Doc: &lint.RawDocumentation{
Title: `Use a type conversion instead of manually copying struct fields`,
Text: `Two struct types with identical fields can be converted between each
other. In older versions of Go, the fields had to have identical
struct tags. Since Go 1.8, however, struct tags are ignored during
conversions. It is thus not necessary to manually copy every field
individually.`,
Before: `
var x T1
y := T2{
Field1: x.Field1,
Field2: x.Field2,
}`,
After: `
var x T1
y := T2(x)`,
Since: "2017.1",
MergeIf: lint.MergeIfAll,
},
})
var Analyzer = SCAnalyzer.Analyzer
func run(pass *analysis.Pass) (any, error) {
// TODO(dh): support conversions between type parameters
fn := func(c inspector.Cursor) {
node := c.Node()
if unary, ok := c.Parent().Node().(*ast.UnaryExpr); ok && unary.Op == token.AND {
// Do not suggest type conversion between pointers
return
}
lit := node.(*ast.CompositeLit)
var typ1 types.Type
var named1 *types.Named
switch typ := pass.TypesInfo.TypeOf(lit.Type).(type) {
case *types.Named:
typ1 = typ
named1 = typ
case *types.Alias:
ua := types.Unalias(typ)
if n, ok := ua.(*types.Named); ok {
typ1 = typ
named1 = n
}
}
if typ1 == nil {
return
}
s1, ok := typ1.Underlying().(*types.Struct)
if !ok {
return
}
var typ2 types.Type
var named2 *types.Named
var ident *ast.Ident
getSelType := func(expr ast.Expr) (types.Type, *ast.Ident, bool) {
sel, ok := expr.(*ast.SelectorExpr)
if !ok {
return nil, nil, false
}
ident, ok := sel.X.(*ast.Ident)
if !ok {
return nil, nil, false
}
typ := pass.TypesInfo.TypeOf(sel.X)
return typ, ident, typ != nil
}
if len(lit.Elts) == 0 {
return
}
if s1.NumFields() != len(lit.Elts) {
return
}
for i, elt := range lit.Elts {
var t types.Type
var id *ast.Ident
var ok bool
switch elt := elt.(type) {
case *ast.SelectorExpr:
t, id, ok = getSelType(elt)
if !ok {
return
}
if i >= s1.NumFields() || s1.Field(i).Name() != elt.Sel.Name {
return
}
case *ast.KeyValueExpr:
var sel *ast.SelectorExpr
sel, ok = elt.Value.(*ast.SelectorExpr)
if !ok {
return
}
if elt.Key.(*ast.Ident).Name != sel.Sel.Name {
return
}
t, id, ok = getSelType(elt.Value)
}
if !ok {
return
}
// All fields must be initialized from the same object
if ident != nil && pass.TypesInfo.ObjectOf(ident) != pass.TypesInfo.ObjectOf(id) {
return
}
switch t := t.(type) {
case *types.Named:
typ2 = t
named2 = t
case *types.Alias:
if n, ok := types.Unalias(t).(*types.Named); ok {
typ2 = t
named2 = n
}
}
if typ2 == nil {
return
}
ident = id
}
if typ2 == nil {
return
}
if named1.Obj().Pkg() != named2.Obj().Pkg() {
// Do not suggest type conversions between different
// packages. Types in different packages might only match
// by coincidence. Furthermore, if the dependency ever
// adds more fields to its type, it could break the code
// that relies on the type conversion to work.
return
}
s2, ok := typ2.Underlying().(*types.Struct)
if !ok {
return
}
if typ1 == typ2 {
return
}
if version.Compare(code.LanguageVersion(pass, node), "go1.8") >= 0 {
if !types.IdenticalIgnoreTags(s1, s2) {
return
}
} else {
if !types.Identical(s1, s2) {
return
}
}
r := &ast.CallExpr{
Fun: lit.Type,
Args: []ast.Expr{ident},
}
report.Report(pass, node,
fmt.Sprintf("should convert %s (type %s) to %s instead of using struct literal", ident.Name, types.TypeString(typ2, types.RelativeTo(pass.Pkg)), types.TypeString(typ1, types.RelativeTo(pass.Pkg))),
report.FilterGenerated(),
report.Fixes(edit.Fix("Use type conversion", edit.ReplaceWithNode(pass.Fset, node, r))))
}
for c := range code.Cursor(pass).Preorder((*ast.CompositeLit)(nil)) {
fn(c)
}
return nil, nil
}