deadcode_test.go 1.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. package main
  2. import (
  3. "go/ast"
  4. "go/parser"
  5. "go/token"
  6. "path/filepath"
  7. "strings"
  8. "testing"
  9. )
  10. // TestNoDeadFunctions 用 AST 精确检查包内是否存在"定义了但从未被引用"的函数/方法。
  11. // 相比文本 grep:AST 只统计标识符,不会把字符串或注释里的同名文字误判为引用。
  12. // main / init 豁免(由运行时调用)。发现问题即测试失败,作为长期质量门禁。
  13. //
  14. // 注意:本文件刻意不用任何带反斜杠的字符串字面量(逐条 Errorf 代替拼接换行),
  15. // 以免"转义序列"在生成/复制过程中被破坏。
  16. func TestNoDeadFunctions(t *testing.T) {
  17. fset := token.NewFileSet()
  18. files, err := filepath.Glob("*.go")
  19. if err != nil {
  20. t.Fatalf("扫描源文件失败: %v", err)
  21. }
  22. defs := map[string]string{} // 函数名 -> 定义位置
  23. counts := map[string]int{} // 标识符出现次数(含测试文件)
  24. for _, path := range files {
  25. if strings.HasSuffix(path, "_test.go") {
  26. continue
  27. }
  28. file, err := parser.ParseFile(fset, path, nil, 0)
  29. if err != nil {
  30. t.Fatalf("解析 %s 失败: %v", path, err)
  31. }
  32. for _, decl := range file.Decls {
  33. fn, ok := decl.(*ast.FuncDecl)
  34. if !ok {
  35. continue
  36. }
  37. name := fn.Name.Name
  38. if name == "main" || name == "init" {
  39. continue
  40. }
  41. defs[name] = fset.Position(fn.Pos()).String()
  42. }
  43. }
  44. for _, path := range files {
  45. file, err := parser.ParseFile(fset, path, nil, 0)
  46. if err != nil {
  47. continue
  48. }
  49. ast.Inspect(file, func(n ast.Node) bool {
  50. if id, ok := n.(*ast.Ident); ok {
  51. counts[id.Name]++
  52. }
  53. return true
  54. })
  55. }
  56. deadCount := 0
  57. for name, where := range defs {
  58. if counts[name] <= 1 { // 仅定义处出现 => 从未被引用
  59. deadCount++
  60. t.Errorf("未被引用的函数: %s (%s)", name, where)
  61. }
  62. }
  63. if deadCount > 0 {
  64. t.Errorf("共 %d 个函数未被引用(建议删除,保持最小)", deadCount)
  65. }
  66. }