refactor(analyzer): extract business logic facts collection
- commit
- 9ad764fa8c56706fcd595e268856beb39c279866
- author
- brice · · 2026-05-30 10:36 UTC
- parent
- 9b450a9
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +46 -2
M analyzer/business_logic.go +7 -2
| @@ -38,8 +38,7 @@ func (r runner) checkBusinessLogic(file *ast.File, filePath string, current conf | |||
| 38 | 38 | } | |
| 39 | 39 | ||
| 40 | 40 | func (r runner) isSuspiciousBusinessFunction(fn *ast.FuncDecl, filePath string) bool { | |
| 41 | - | facts := businessFunctionFacts{runner: r, fn: fn} | |
| 42 | - | ast.Inspect(fn.Body, facts.inspect) | |
| 41 | + | facts := r.collectBusinessFunctionFacts(fn) | |
| 43 | 42 | if facts.nodeCount > *r.cfg.Heuristics.BusinessLogicMaxFunctionNodes { | |
| 44 | 43 | return false | |
| 45 | 44 | } | |
| @@ -50,6 +49,12 @@ func (r runner) isSuspiciousBusinessFunction(fn *ast.FuncDecl, filePath string) | |||
| 50 | 49 | return false | |
| 51 | 50 | } | |
| 52 | 51 | ||
| 52 | + | func (r runner) collectBusinessFunctionFacts(fn *ast.FuncDecl) businessFunctionFacts { | |
| 53 | + | facts := businessFunctionFacts{runner: r, fn: fn} | |
| 54 | + | ast.Inspect(fn.Body, facts.inspect) | |
| 55 | + | return facts | |
| 56 | + | } | |
| 57 | + | ||
| 53 | 58 | type businessFunctionFacts struct { | |
| 54 | 59 | runner runner | |
| 55 | 60 | fn *ast.FuncDecl | |
| @@ -38,8 +38,7 @@ func (r runner) checkBusinessLogic(file *ast.File, filePath string, current conf | |||||
| 38 | } | 38 | } | ||
| 39 | 39 | ||||
| 40 | func (r runner) isSuspiciousBusinessFunction(fn *ast.FuncDecl, filePath string) bool { | 40 | func (r runner) isSuspiciousBusinessFunction(fn *ast.FuncDecl, filePath string) bool { | ||
| 41 | - | facts := businessFunctionFacts{runner: r, fn: fn} | 41 | + | facts := r.collectBusinessFunctionFacts(fn) |
| 42 | - | ast.Inspect(fn.Body, facts.inspect) | |||
| 43 | if facts.nodeCount > *r.cfg.Heuristics.BusinessLogicMaxFunctionNodes { | 42 | if facts.nodeCount > *r.cfg.Heuristics.BusinessLogicMaxFunctionNodes { | ||
| 44 | return false | 43 | return false | ||
| 45 | } | 44 | } | ||
| @@ -50,6 +49,12 @@ func (r runner) isSuspiciousBusinessFunction(fn *ast.FuncDecl, filePath string) | |||||
| 50 | return false | 49 | return false | ||
| 51 | } | 50 | } | ||
| 52 | 51 | ||||
| 52 | + | func (r runner) collectBusinessFunctionFacts(fn *ast.FuncDecl) businessFunctionFacts { | |||
| 53 | + | facts := businessFunctionFacts{runner: r, fn: fn} | |||
| 54 | + | ast.Inspect(fn.Body, facts.inspect) | |||
| 55 | + | return facts | |||
| 56 | + | } | |||
| 57 | + | ||||
| 53 | type businessFunctionFacts struct { | 58 | type businessFunctionFacts struct { | ||
| 54 | runner runner | 59 | runner runner | ||
| 55 | fn *ast.FuncDecl | 60 | fn *ast.FuncDecl | ||
A analyzer/business_logic_facts_test.go +39 -0
| @@ -0,0 +1,39 @@ | |||
| 1 | + | package analyzer | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "go/ast" | |
| 5 | + | "go/types" | |
| 6 | + | "testing" | |
| 7 | + | ||
| 8 | + | "github.com/bnema/hexcheck/config" | |
| 9 | + | "golang.org/x/tools/go/analysis" | |
| 10 | + | ) | |
| 11 | + | ||
| 12 | + | func TestCollectBusinessFunctionFactsInspectsFunctionBody(t *testing.T) { | |
| 13 | + | fn := &ast.FuncDecl{ | |
| 14 | + | Name: ast.NewIdent("ResolveMode"), | |
| 15 | + | Type: &ast.FuncType{}, | |
| 16 | + | Body: &ast.BlockStmt{List: []ast.Stmt{ | |
| 17 | + | &ast.IfStmt{ | |
| 18 | + | Cond: ast.NewIdent("ok"), | |
| 19 | + | Body: &ast.BlockStmt{}, | |
| 20 | + | }, | |
| 21 | + | }}, | |
| 22 | + | } | |
| 23 | + | r := runner{ | |
| 24 | + | cfg: &config.Config{Heuristics: config.Heuristics{BusinessLogicMaxFunctionNodes: testIntPtr(2000)}}, | |
| 25 | + | pass: &analysis.Pass{TypesInfo: &types.Info{Types: map[ast.Expr]types.TypeAndValue{}}}, | |
| 26 | + | } | |
| 27 | + | ||
| 28 | + | facts := r.collectBusinessFunctionFacts(fn) | |
| 29 | + | ||
| 30 | + | if facts.fn != fn { | |
| 31 | + | t.Fatalf("collectBusinessFunctionFacts() fn = %p, want %p", facts.fn, fn) | |
| 32 | + | } | |
| 33 | + | if facts.branchCount != 1 { | |
| 34 | + | t.Fatalf("collectBusinessFunctionFacts() branchCount = %d, want 1", facts.branchCount) | |
| 35 | + | } | |
| 36 | + | if facts.nodeCount == 0 { | |
| 37 | + | t.Fatal("collectBusinessFunctionFacts() nodeCount = 0, want inspected nodes") | |
| 38 | + | } | |
| 39 | + | } | |
| @@ -0,0 +1,39 @@ | |||||
| 1 | + | package analyzer | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "go/ast" | |||
| 5 | + | "go/types" | |||
| 6 | + | "testing" | |||
| 7 | + | ||||
| 8 | + | "github.com/bnema/hexcheck/config" | |||
| 9 | + | "golang.org/x/tools/go/analysis" | |||
| 10 | + | ) | |||
| 11 | + | ||||
| 12 | + | func TestCollectBusinessFunctionFactsInspectsFunctionBody(t *testing.T) { | |||
| 13 | + | fn := &ast.FuncDecl{ | |||
| 14 | + | Name: ast.NewIdent("ResolveMode"), | |||
| 15 | + | Type: &ast.FuncType{}, | |||
| 16 | + | Body: &ast.BlockStmt{List: []ast.Stmt{ | |||
| 17 | + | &ast.IfStmt{ | |||
| 18 | + | Cond: ast.NewIdent("ok"), | |||
| 19 | + | Body: &ast.BlockStmt{}, | |||
| 20 | + | }, | |||
| 21 | + | }}, | |||
| 22 | + | } | |||
| 23 | + | r := runner{ | |||
| 24 | + | cfg: &config.Config{Heuristics: config.Heuristics{BusinessLogicMaxFunctionNodes: testIntPtr(2000)}}, | |||
| 25 | + | pass: &analysis.Pass{TypesInfo: &types.Info{Types: map[ast.Expr]types.TypeAndValue{}}}, | |||
| 26 | + | } | |||
| 27 | + | ||||
| 28 | + | facts := r.collectBusinessFunctionFacts(fn) | |||
| 29 | + | ||||
| 30 | + | if facts.fn != fn { | |||
| 31 | + | t.Fatalf("collectBusinessFunctionFacts() fn = %p, want %p", facts.fn, fn) | |||
| 32 | + | } | |||
| 33 | + | if facts.branchCount != 1 { | |||
| 34 | + | t.Fatalf("collectBusinessFunctionFacts() branchCount = %d, want 1", facts.branchCount) | |||
| 35 | + | } | |||
| 36 | + | if facts.nodeCount == 0 { | |||
| 37 | + | t.Fatal("collectBusinessFunctionFacts() nodeCount = 0, want inspected nodes") | |||
| 38 | + | } | |||
| 39 | + | } | |||