detached 6954c9e

fix(analyzer): tighten business logic fact context

commit
6954c9ed29af5c82723d43eb98866cd71c35bcda
author
brice · · 2026-05-30 13:15 UTC
parent
dee20a2
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · 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
4040 if *packageDiagnostics >= *r.cfg.Heuristics.BusinessLogicMaxDiagnosticsPerPackage {
4141 return
4242 }
43- if r.isSuspiciousBusinessFunction(fn, file, filePath) {
43+ if r.isSuspiciousBusinessFunction(fn, file, filePath, current.Role) {
4444 *packageDiagnostics++
4545 }
4646 }
4747 }
4848
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)
5151 if facts.nodeCount > *r.cfg.Heuristics.BusinessLogicMaxFunctionNodes {
5252 return false
5353 }
@@ -62,8 +62,11 @@ func (r runner) isSuspiciousBusinessFunction(fn *ast.FuncDecl, file *ast.File, f
6262 return false
6363 }
6464
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{}}
6770 if len(file) > 0 && file[0] != nil {
6871 for _, imp := range file[0].Imports {
6972 facts.imports[strings.Trim(imp.Path.Value, "\"")] = true
@@ -74,8 +77,9 @@ func (r runner) collectBusinessFunctionFacts(fn *ast.FuncDecl, file ...*ast.File
7477 }
7578
7679 type businessFunctionFacts struct {
77- runner runner
78- fn *ast.FuncDecl
80+ runner runner
81+ fn *ast.FuncDecl
82+ fileRole config.Role
7983
8084 nodeCount int
8185 branchCount int
@@ -159,21 +163,24 @@ func (f *businessFunctionFacts) inspectTechnicalCall(call *ast.CallExpr) {
159163 return
160164 }
161165 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 {
163171 return
164172 }
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] {
166175 f.usesTechnicalPackage = true
167176 }
168177 }
169178
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,
177184 }
178185
179186 func collaboratorKey(info *types.Info, expr ast.Expr) string {
@@ -231,12 +238,7 @@ func (f *businessFunctionFacts) isTechnicalDetectionOnly() bool {
231238 }
232239
233240 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
240242 }
241243
242244 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 return41 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 false52 return false
53 }53 }
@@ -62,8 +62,11 @@ func (r runner) isSuspiciousBusinessFunction(fn *ast.FuncDecl, file *ast.File, f
62 return false62 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, "\"")] = true72 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 runner80+ runner runner
78- fn *ast.FuncDecl81+ fn *ast.FuncDecl
82+ fileRole config.Role
79 83
80 nodeCount int84 nodeCount int
81 branchCount int85 branchCount int
@@ -159,21 +163,24 @@ func (f *businessFunctionFacts) inspectTechnicalCall(call *ast.CallExpr) {
159 return163 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 return171 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 = true175 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) {
2525 pass: &analysis.Pass{TypesInfo: &types.Info{Types: map[ast.Expr]types.TypeAndValue{}}},
2626 }
2727
28- facts := r.collectBusinessFunctionFacts(fn)
28+ facts := r.collectBusinessFunctionFacts(fn, config.RoleAdapter)
2929
3030 if facts.fn != fn {
3131 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)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 5

↑↓ moveenter openesc close