fix(analyzer): tighten business logic fact context
- commit
- 6954c9ed29af5c82723d43eb98866cd71c35bcda
- author
- brice · · 2026-05-30 13:15 UTC
- parent
- dee20a2
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +25 -23
M analyzer/business_logic.go +24 -22
| @@ -40,14 +40,14 @@ func (r runner) checkBusinessLogic(file *ast.File, filePath string, current conf | |||
| 40 | 40 | if *packageDiagnostics >= *r.cfg.Heuristics.BusinessLogicMaxDiagnosticsPerPackage { | |
| 41 | 41 | return | |
| 42 | 42 | } | |
| 43 | - | if r.isSuspiciousBusinessFunction(fn, file, filePath) { | |
| 43 | + | if r.isSuspiciousBusinessFunction(fn, file, filePath, current.Role) { | |
| 44 | 44 | *packageDiagnostics++ | |
| 45 | 45 | } | |
| 46 | 46 | } | |
| 47 | 47 | } | |
| 48 | 48 | ||
| 49 | - | func (r runner) isSuspiciousBusinessFunction(fn *ast.FuncDecl, file *ast.File, filePath string) bool { | |
| 50 | - | facts := r.collectBusinessFunctionFacts(fn, file) | |
| 49 | + | func (r runner) isSuspiciousBusinessFunction(fn *ast.FuncDecl, file *ast.File, filePath string, fileRole config.Role) bool { | |
| 50 | + | facts := r.collectBusinessFunctionFacts(fn, fileRole, file) | |
| 51 | 51 | if facts.nodeCount > *r.cfg.Heuristics.BusinessLogicMaxFunctionNodes { | |
| 52 | 52 | return false | |
| 53 | 53 | } | |
| @@ -62,8 +62,11 @@ func (r runner) isSuspiciousBusinessFunction(fn *ast.FuncDecl, file *ast.File, f | |||
| 62 | 62 | return false | |
| 63 | 63 | } | |
| 64 | 64 | ||
| 65 | - | func (r runner) collectBusinessFunctionFacts(fn *ast.FuncDecl, file ...*ast.File) businessFunctionFacts { | |
| 66 | - | facts := businessFunctionFacts{runner: r, fn: fn, imports: map[string]bool{}} | |
| 65 | + | func (r runner) collectBusinessFunctionFacts(fn *ast.FuncDecl, fileRole config.Role, file ...*ast.File) businessFunctionFacts { | |
| 66 | + | if fileRole == "" { | |
| 67 | + | fileRole = config.RoleAdapter | |
| 68 | + | } | |
| 69 | + | facts := businessFunctionFacts{runner: r, fn: fn, fileRole: fileRole, imports: map[string]bool{}} | |
| 67 | 70 | if len(file) > 0 && file[0] != nil { | |
| 68 | 71 | for _, imp := range file[0].Imports { | |
| 69 | 72 | facts.imports[strings.Trim(imp.Path.Value, "\"")] = true | |
| @@ -74,8 +77,9 @@ func (r runner) collectBusinessFunctionFacts(fn *ast.FuncDecl, file ...*ast.File | |||
| 74 | 77 | } | |
| 75 | 78 | ||
| 76 | 79 | type businessFunctionFacts struct { | |
| 77 | - | runner runner | |
| 78 | - | fn *ast.FuncDecl | |
| 80 | + | runner runner | |
| 81 | + | fn *ast.FuncDecl | |
| 82 | + | fileRole config.Role | |
| 79 | 83 | ||
| 80 | 84 | nodeCount int | |
| 81 | 85 | branchCount int | |
| @@ -159,21 +163,24 @@ func (f *businessFunctionFacts) inspectTechnicalCall(call *ast.CallExpr) { | |||
| 159 | 163 | return | |
| 160 | 164 | } | |
| 161 | 165 | ident, ok := sel.X.(*ast.Ident) | |
| 162 | - | if !ok { | |
| 166 | + | if !ok || f.runner.pass == nil || f.runner.pass.TypesInfo == nil { | |
| 167 | + | return | |
| 168 | + | } | |
| 169 | + | pkgName, ok := f.runner.pass.TypesInfo.ObjectOf(ident).(*types.PkgName) | |
| 170 | + | if !ok || pkgName.Imported() == nil { | |
| 163 | 171 | return | |
| 164 | 172 | } | |
| 165 | - | if _, ok := map[string]bool{"os": true, "filepath": true, "strconv": true, "bufio": true}[ident.Name]; ok && f.imports[technicalImportPath(ident.Name)] { | |
| 173 | + | path := pkgName.Imported().Path() | |
| 174 | + | if technicalImportPaths[path] && f.imports[path] { | |
| 166 | 175 | f.usesTechnicalPackage = true | |
| 167 | 176 | } | |
| 168 | 177 | } | |
| 169 | 178 | ||
| 170 | - | func technicalImportPath(name string) string { | |
| 171 | - | switch name { | |
| 172 | - | case "filepath": | |
| 173 | - | return "path/filepath" | |
| 174 | - | default: | |
| 175 | - | return name | |
| 176 | - | } | |
| 179 | + | var technicalImportPaths = map[string]bool{ | |
| 180 | + | "os": true, | |
| 181 | + | "path/filepath": true, | |
| 182 | + | "strconv": true, | |
| 183 | + | "bufio": true, | |
| 177 | 184 | } | |
| 178 | 185 | ||
| 179 | 186 | func collaboratorKey(info *types.Info, expr ast.Expr) string { | |
| @@ -231,12 +238,7 @@ func (f *businessFunctionFacts) isTechnicalDetectionOnly() bool { | |||
| 231 | 238 | } | |
| 232 | 239 | ||
| 233 | 240 | func (f *businessFunctionFacts) componentRole() config.Role { | |
| 234 | - | pkgRel := f.runner.relImportPath(f.runner.pass.Pkg.Path()) | |
| 235 | - | current, ok := f.runner.cfg.ComponentForPath(pkgRel) | |
| 236 | - | if !ok { | |
| 237 | - | return config.RoleAdapter | |
| 238 | - | } | |
| 239 | - | return current.Role | |
| 241 | + | return f.fileRole | |
| 240 | 242 | } | |
| 241 | 243 | ||
| 242 | 244 | func (f *businessFunctionFacts) classify() ([]businessLogicSignal, []businessLogicSignal) { | |
| @@ -40,14 +40,14 @@ func (r runner) checkBusinessLogic(file *ast.File, filePath string, current conf | |||||
| 40 | if *packageDiagnostics >= *r.cfg.Heuristics.BusinessLogicMaxDiagnosticsPerPackage { | 40 | if *packageDiagnostics >= *r.cfg.Heuristics.BusinessLogicMaxDiagnosticsPerPackage { | ||
| 41 | return | 41 | return | ||
| 42 | } | 42 | } | ||
| 43 | - | if r.isSuspiciousBusinessFunction(fn, file, filePath) { | 43 | + | if r.isSuspiciousBusinessFunction(fn, file, filePath, current.Role) { |
| 44 | *packageDiagnostics++ | 44 | *packageDiagnostics++ | ||
| 45 | } | 45 | } | ||
| 46 | } | 46 | } | ||
| 47 | } | 47 | } | ||
| 48 | 48 | ||||
| 49 | - | func (r runner) isSuspiciousBusinessFunction(fn *ast.FuncDecl, file *ast.File, filePath string) bool { | 49 | + | func (r runner) isSuspiciousBusinessFunction(fn *ast.FuncDecl, file *ast.File, filePath string, fileRole config.Role) bool { |
| 50 | - | facts := r.collectBusinessFunctionFacts(fn, file) | 50 | + | facts := r.collectBusinessFunctionFacts(fn, fileRole, file) |
| 51 | if facts.nodeCount > *r.cfg.Heuristics.BusinessLogicMaxFunctionNodes { | 51 | if facts.nodeCount > *r.cfg.Heuristics.BusinessLogicMaxFunctionNodes { | ||
| 52 | return false | 52 | return false | ||
| 53 | } | 53 | } | ||
| @@ -62,8 +62,11 @@ func (r runner) isSuspiciousBusinessFunction(fn *ast.FuncDecl, file *ast.File, f | |||||
| 62 | return false | 62 | return false | ||
| 63 | } | 63 | } | ||
| 64 | 64 | ||||
| 65 | - | func (r runner) collectBusinessFunctionFacts(fn *ast.FuncDecl, file ...*ast.File) businessFunctionFacts { | 65 | + | func (r runner) collectBusinessFunctionFacts(fn *ast.FuncDecl, fileRole config.Role, file ...*ast.File) businessFunctionFacts { |
| 66 | - | facts := businessFunctionFacts{runner: r, fn: fn, imports: map[string]bool{}} | 66 | + | if fileRole == "" { |
| 67 | + | fileRole = config.RoleAdapter | |||
| 68 | + | } | |||
| 69 | + | facts := businessFunctionFacts{runner: r, fn: fn, fileRole: fileRole, imports: map[string]bool{}} | |||
| 67 | if len(file) > 0 && file[0] != nil { | 70 | if len(file) > 0 && file[0] != nil { | ||
| 68 | for _, imp := range file[0].Imports { | 71 | for _, imp := range file[0].Imports { | ||
| 69 | facts.imports[strings.Trim(imp.Path.Value, "\"")] = true | 72 | facts.imports[strings.Trim(imp.Path.Value, "\"")] = true | ||
| @@ -74,8 +77,9 @@ func (r runner) collectBusinessFunctionFacts(fn *ast.FuncDecl, file ...*ast.File | |||||
| 74 | } | 77 | } | ||
| 75 | 78 | ||||
| 76 | type businessFunctionFacts struct { | 79 | type businessFunctionFacts struct { | ||
| 77 | - | runner runner | 80 | + | runner runner |
| 78 | - | fn *ast.FuncDecl | 81 | + | fn *ast.FuncDecl |
| 82 | + | fileRole config.Role | |||
| 79 | 83 | ||||
| 80 | nodeCount int | 84 | nodeCount int | ||
| 81 | branchCount int | 85 | branchCount int | ||
| @@ -159,21 +163,24 @@ func (f *businessFunctionFacts) inspectTechnicalCall(call *ast.CallExpr) { | |||||
| 159 | return | 163 | return | ||
| 160 | } | 164 | } | ||
| 161 | ident, ok := sel.X.(*ast.Ident) | 165 | ident, ok := sel.X.(*ast.Ident) | ||
| 162 | - | if !ok { | 166 | + | if !ok || f.runner.pass == nil || f.runner.pass.TypesInfo == nil { |
| 167 | + | return | |||
| 168 | + | } | |||
| 169 | + | pkgName, ok := f.runner.pass.TypesInfo.ObjectOf(ident).(*types.PkgName) | |||
| 170 | + | if !ok || pkgName.Imported() == nil { | |||
| 163 | return | 171 | return | ||
| 164 | } | 172 | } | ||
| 165 | - | if _, ok := map[string]bool{"os": true, "filepath": true, "strconv": true, "bufio": true}[ident.Name]; ok && f.imports[technicalImportPath(ident.Name)] { | 173 | + | path := pkgName.Imported().Path() |
| 174 | + | if technicalImportPaths[path] && f.imports[path] { | |||
| 166 | f.usesTechnicalPackage = true | 175 | f.usesTechnicalPackage = true | ||
| 167 | } | 176 | } | ||
| 168 | } | 177 | } | ||
| 169 | 178 | ||||
| 170 | - | func technicalImportPath(name string) string { | 179 | + | var technicalImportPaths = map[string]bool{ |
| 171 | - | switch name { | 180 | + | "os": true, |
| 172 | - | case "filepath": | 181 | + | "path/filepath": true, |
| 173 | - | return "path/filepath" | 182 | + | "strconv": true, |
| 174 | - | default: | 183 | + | "bufio": true, |
| 175 | - | return name | |||
| 176 | - | } | |||
| 177 | } | 184 | } | ||
| 178 | 185 | ||||
| 179 | func collaboratorKey(info *types.Info, expr ast.Expr) string { | 186 | func collaboratorKey(info *types.Info, expr ast.Expr) string { | ||
| @@ -231,12 +238,7 @@ func (f *businessFunctionFacts) isTechnicalDetectionOnly() bool { | |||||
| 231 | } | 238 | } | ||
| 232 | 239 | ||||
| 233 | func (f *businessFunctionFacts) componentRole() config.Role { | 240 | func (f *businessFunctionFacts) componentRole() config.Role { | ||
| 234 | - | pkgRel := f.runner.relImportPath(f.runner.pass.Pkg.Path()) | 241 | + | return f.fileRole |
| 235 | - | current, ok := f.runner.cfg.ComponentForPath(pkgRel) | |||
| 236 | - | if !ok { | |||
| 237 | - | return config.RoleAdapter | |||
| 238 | - | } | |||
| 239 | - | return current.Role | |||
| 240 | } | 242 | } | ||
| 241 | 243 | ||||
| 242 | func (f *businessFunctionFacts) classify() ([]businessLogicSignal, []businessLogicSignal) { | 244 | func (f *businessFunctionFacts) classify() ([]businessLogicSignal, []businessLogicSignal) { | ||
M analyzer/business_logic_facts_test.go +1 -1
| @@ -25,7 +25,7 @@ func TestCollectBusinessFunctionFactsInspectsFunctionBody(t *testing.T) { | |||
| 25 | 25 | pass: &analysis.Pass{TypesInfo: &types.Info{Types: map[ast.Expr]types.TypeAndValue{}}}, | |
| 26 | 26 | } | |
| 27 | 27 | ||
| 28 | - | facts := r.collectBusinessFunctionFacts(fn) | |
| 28 | + | facts := r.collectBusinessFunctionFacts(fn, config.RoleAdapter) | |
| 29 | 29 | ||
| 30 | 30 | if facts.fn != fn { | |
| 31 | 31 | t.Fatalf("collectBusinessFunctionFacts() fn = %p, want %p", facts.fn, fn) | |
| @@ -25,7 +25,7 @@ func TestCollectBusinessFunctionFactsInspectsFunctionBody(t *testing.T) { | |||||
| 25 | pass: &analysis.Pass{TypesInfo: &types.Info{Types: map[ast.Expr]types.TypeAndValue{}}}, | 25 | pass: &analysis.Pass{TypesInfo: &types.Info{Types: map[ast.Expr]types.TypeAndValue{}}}, | ||
| 26 | } | 26 | } | ||
| 27 | 27 | ||||
| 28 | - | facts := r.collectBusinessFunctionFacts(fn) | 28 | + | facts := r.collectBusinessFunctionFacts(fn, config.RoleAdapter) |
| 29 | 29 | ||||
| 30 | if facts.fn != fn { | 30 | if facts.fn != fn { | ||
| 31 | t.Fatalf("collectBusinessFunctionFacts() fn = %p, want %p", facts.fn, fn) | 31 | t.Fatalf("collectBusinessFunctionFacts() fn = %p, want %p", facts.fn, fn) | ||