This commit is contained in:
Tomas Dvorak
2026-02-24 10:33:59 +01:00
parent 409acd2e08
commit 898a3c303f
1374 changed files with 290409 additions and 29187 deletions
@@ -58,7 +58,10 @@ func (d *SingleUseDetector) Detect(ctx context.Context, path string, config *qua
switch obj := obj.(type) {
case *types.Func:
key := obj.Pkg().Path() + "." + obj.Name()
key, ok := functionKey(obj)
if !ok {
continue
}
callCounts[key]++
case *types.TypeName:
if obj.Pkg() != nil {
@@ -75,17 +78,18 @@ func (d *SingleUseDetector) Detect(ctx context.Context, path string, config *qua
switch obj := obj.(type) {
case *types.Func:
if obj.Pkg() != nil {
key := obj.Pkg().Path() + "." + obj.Name()
pos := pkg.Fset.Position(obj.Pos())
funcDefs[key] = FuncDef{
Name: obj.Name(),
File: pos.Filename,
Line: pos.Line,
Package: obj.Pkg().Path(),
Exported: obj.Exported(),
Signature: obj.Type().String(),
}
key, ok := functionKey(obj)
if !ok {
continue
}
pos := pkg.Fset.Position(obj.Pos())
funcDefs[key] = FuncDef{
Name: obj.Name(),
File: pos.Filename,
Line: pos.Line,
Package: obj.Pkg().Path(),
Exported: obj.Exported(),
Signature: obj.Type().String(),
}
case *types.TypeName:
if obj.Pkg() != nil {
@@ -109,6 +113,9 @@ func (d *SingleUseDetector) Detect(ctx context.Context, path string, config *qua
var findings []quality.Finding
for key, def := range funcDefs {
if def.Exported || isLikelyEntrypointFile(def.File) {
continue
}
if strings.HasSuffix(def.Name, "Test") || strings.HasPrefix(def.Name, "Test") {
continue
}
@@ -143,9 +150,18 @@ func (d *SingleUseDetector) Detect(ctx context.Context, path string, config *qua
}
for key, def := range typeDefs {
if def.Exported || isLikelyEntrypointFile(def.File) {
continue
}
if strings.HasSuffix(def.Name, "Error") || strings.HasSuffix(def.Name, "Options") {
continue
}
if strings.HasSuffix(def.Name, "Config") || strings.HasSuffix(def.Name, "Params") {
continue
}
if !strings.Contains(def.Underlying, "struct") && !strings.Contains(def.Underlying, "interface") {
continue
}
count := typeUsages[key]
if count == 1 {
@@ -242,6 +258,22 @@ func (d *SingleUseDetector) getFuncLOC(file string, startLine int) (int, error)
return loc, nil
}
func functionKey(fn *types.Func) (string, bool) {
if fn == nil || fn.Pkg() == nil {
return "", false
}
sig, ok := fn.Type().(*types.Signature)
if ok && sig.Recv() != nil {
return "", false
}
return fn.Pkg().Path() + "." + fn.Name(), true
}
func isLikelyEntrypointFile(path string) bool {
p := filepath.ToSlash(path)
return strings.HasPrefix(p, "cmd/") || strings.Contains(p, "/cmd/") || strings.HasSuffix(p, "/main.go") || strings.HasSuffix(p, "_test.go")
}
type FuncDef struct {
Name string
File string
@@ -471,33 +503,36 @@ func (d *EnhancedDeadCodeDetector) Detect(ctx context.Context, path string, conf
switch o := obj.(type) {
case *types.Func:
defs[key] = ObjInfo{
Name: obj.Name(),
Type: "function",
File: pos.Filename,
Line: pos.Line,
Package: obj.Pkg().Path(),
Exported: obj.Exported(),
Signature: o.Type().String(),
Name: obj.Name(),
Type: "function",
File: pos.Filename,
Line: pos.Line,
Package: obj.Pkg().Path(),
PackageName: pkg.Name,
Exported: obj.Exported(),
Signature: o.Type().String(),
}
case *types.TypeName:
defs[key] = ObjInfo{
Name: obj.Name(),
Type: "type",
File: pos.Filename,
Line: pos.Line,
Package: obj.Pkg().Path(),
Exported: obj.Exported(),
Underlying: o.Type().Underlying().String(),
Name: obj.Name(),
Type: "type",
File: pos.Filename,
Line: pos.Line,
Package: obj.Pkg().Path(),
PackageName: pkg.Name,
Exported: obj.Exported(),
Underlying: o.Type().Underlying().String(),
}
case *types.Var:
if obj.Exported() {
if obj.Exported() && !o.IsField() {
defs[key] = ObjInfo{
Name: obj.Name(),
Type: "variable",
File: pos.Filename,
Line: pos.Line,
Package: obj.Pkg().Path(),
Exported: obj.Exported(),
Name: obj.Name(),
Type: "variable",
File: pos.Filename,
Line: pos.Line,
Package: obj.Pkg().Path(),
PackageName: pkg.Name,
Exported: obj.Exported(),
}
}
}
@@ -521,10 +556,22 @@ func (d *EnhancedDeadCodeDetector) Detect(ctx context.Context, path string, conf
if entryPoints[key] {
continue
}
if !strings.Contains(def.Package, "/internal/") || def.PackageName == "main" {
continue
}
if isLikelyEntrypointFile(def.File) {
continue
}
if strings.HasPrefix(def.Name, "Test") || strings.HasPrefix(def.Name, "Benchmark") || strings.HasPrefix(def.Name, "Fuzz") {
continue
}
if def.Type == "function" && strings.HasPrefix(def.Name, "New") {
continue
}
if def.Type == "type" && (strings.HasSuffix(def.Name, "Config") || strings.HasSuffix(def.Name, "Options")) {
continue
}
if strings.HasSuffix(def.Name, "Error") && def.Type == "type" {
continue
@@ -573,12 +620,13 @@ func (d *EnhancedDeadCodeDetector) Detect(ctx context.Context, path string, conf
}
type ObjInfo struct {
Name string
Type string
File string
Line int
Package string
Exported bool
Signature string
Underlying string
Name string
Type string
File string
Line int
Package string
PackageName string
Exported bool
Signature string
Underlying string
}
@@ -172,8 +172,7 @@ func (d *UnusedImportDetector) analyzeFile(path string) ([]quality.Finding, erro
if imp.Name != nil {
name = imp.Name.Name
} else {
parts := strings.Split(pkgPath, "/")
name = parts[len(parts)-1]
name = inferImportName(pkgPath)
}
imports[pkgPath] = name
}
@@ -191,8 +190,7 @@ func (d *UnusedImportDetector) analyzeFile(path string) ([]quality.Finding, erro
if imp.Name != nil {
name = imp.Name.Name
} else {
parts := strings.Split(pkgPath, "/")
name = parts[len(parts)-1]
name = inferImportName(pkgPath)
}
if name == "_" || name == "." {
@@ -224,6 +222,42 @@ func (d *UnusedImportDetector) analyzeFile(path string) ([]quality.Finding, erro
return findings, nil
}
func inferImportName(pkgPath string) string {
parts := strings.Split(pkgPath, "/")
if len(parts) == 0 {
return pkgPath
}
last := parts[len(parts)-1]
if isSemverSegment(last) && len(parts) >= 2 {
last = parts[len(parts)-2]
}
if idx := strings.Index(last, ".v"); idx > 0 && isDigits(last[idx+2:]) {
last = last[:idx]
}
return last
}
func isSemverSegment(segment string) bool {
if len(segment) < 2 || segment[0] != 'v' {
return false
}
return isDigits(segment[1:])
}
func isDigits(value string) bool {
if value == "" {
return false
}
for _, r := range value {
if r < '0' || r > '9' {
return false
}
}
return true
}
type CycleDetector struct {
*quality.BaseDetector
}
@@ -0,0 +1,22 @@
package analyzers
import "testing"
func TestInferImportName(t *testing.T) {
tests := []struct {
path string
want string
}{
{path: "fmt", want: "fmt"},
{path: "gopkg.in/yaml.v3", want: "yaml"},
{path: "github.com/gocolly/colly/v2", want: "colly"},
{path: "golang.org/x/tools/go/packages", want: "packages"},
}
for _, tt := range tests {
got := inferImportName(tt.path)
if got != tt.want {
t.Fatalf("inferImportName(%q) = %q, want %q", tt.path, got, tt.want)
}
}
}
@@ -240,6 +240,10 @@ func (d *DebugLogDetector) analyzeFile(path string) []quality.Finding {
if err != nil {
return nil
}
normPath := filepath.ToSlash(path)
if strings.Contains(normPath, "internal/ui/") || strings.Contains(normPath, "examples/") {
return nil
}
debugPatterns := []string{
"log.Print",
@@ -267,7 +271,7 @@ func (d *DebugLogDetector) analyzeFile(path string) []quality.Finding {
for _, pattern := range debugPatterns {
if callStr == pattern || strings.HasPrefix(callStr, pattern) {
if strings.Contains(path, "_test.go") {
if strings.HasSuffix(normPath, "_test.go") || strings.HasPrefix(normPath, "cmd/") || strings.Contains(normPath, "/cmd/") {
return true
}
@@ -291,7 +295,7 @@ func (d *DebugLogDetector) analyzeFile(path string) []quality.Finding {
}
}
if strings.Contains(path, "/cmd/") {
if strings.HasPrefix(normPath, "cmd/") || strings.Contains(normPath, "/cmd/") {
return true
}
-1
View File
@@ -42,7 +42,6 @@ func (p *GoPlugin) DefaultSrcDir() string {
func (p *GoPlugin) CreateDetectors(finder quality.FileFinder) []quality.Detector {
return []quality.Detector{
analyzers.NewDeadCodeDetector(finder),
analyzers.NewEnhancedDeadCodeDetector(finder),
analyzers.NewUnusedImportDetector(finder),
analyzers.NewCycleDetector(finder),