@@ -26,29 +26,6 @@ import (
2626// internal and may be used via reflection or are
2727// genuinely file-scoped helpers.
2828
29- // DO NOT add entries here to make tests pass. New code must
30- // conform to the check. Widening requires a dedicated PR with
31- // justification for each entry.
32- //
33- // testOnlyExports lists exported symbols that exist
34- // solely for test usage. The dead-export scanner skips
35- // test files, so these would otherwise be false
36- // positives. Keep this list small: prefer eliminating
37- // the export over adding it here.
38- var testOnlyExports = map [string ]bool {
39- "github.com/ActiveMemory/ctx/internal/config/hook.CategoryCustomizable" : true ,
40- "github.com/ActiveMemory/ctx/internal/assets/hooks/messages.Hooks" : true ,
41- "github.com/ActiveMemory/ctx/internal/cli/pad/core/store.EnsureGitignore" : true ,
42- "github.com/ActiveMemory/ctx/internal/cli/system/core/state.SetDirForTest" : true ,
43- "github.com/ActiveMemory/ctx/internal/config/asset.DirReferences" : true ,
44- "github.com/ActiveMemory/ctx/internal/config/regex.Phase" : true ,
45- "github.com/ActiveMemory/ctx/internal/inspect.StartsWithCtxMarker" : true ,
46- "github.com/ActiveMemory/ctx/internal/journal/parser.Parser" : true ,
47- "github.com/ActiveMemory/ctx/internal/mcp/proto.InitializeParams" : true ,
48- "github.com/ActiveMemory/ctx/internal/mcp/proto.UnsubscribeParams" : true ,
49- "github.com/ActiveMemory/ctx/internal/rc.Reset" : true ,
50- }
51-
5229// DO NOT add entries here to make tests pass. New code must
5330// conform to the check. Widening requires a dedicated PR with
5431// justification for each entry.
@@ -148,9 +125,30 @@ func TestNoDeadExports(t *testing.T) {
148125 }
149126 }
150127
151- // Phase 3: remove test-only allowlist entries.
152- for key := range testOnlyExports {
153- delete (defs , key )
128+ // Phase 2.5: remove symbols used cross-package in
129+ // test files. If a test in package B imports a
130+ // symbol from package A, the symbol is test
131+ // infrastructure — not dead. Same-package test
132+ // usage does not count (those should be unexported).
133+ testPkgs := loadTestPackages (t )
134+ for _ , pkg := range testPkgs {
135+ for ident , obj := range pkg .TypesInfo .Uses {
136+ if obj == nil || obj .Pkg () == nil {
137+ continue
138+ }
139+ pos := pkg .Fset .Position (ident .Pos ())
140+ if ! isTestFile (pos .Filename ) {
141+ continue
142+ }
143+ // Cross-package: the test's package path
144+ // differs from the symbol's defining package.
145+ if pkg .PkgPath == obj .Pkg ().Path () {
146+ continue
147+ }
148+ key := obj .Pkg ().Path () + "." +
149+ obj .Name ()
150+ delete (defs , key )
151+ }
154152 }
155153
156154 // Phase 3b: remove Linux-only exports (used from
@@ -212,6 +210,30 @@ func loadCmdPackages(t *testing.T) []*packages.Package {
212210 return pkgs
213211}
214212
213+ // loadTestPackages loads internal/ packages WITH test
214+ // files for cross-package test usage detection.
215+ func loadTestPackages (
216+ t * testing.T ,
217+ ) []* packages.Package {
218+ t .Helper ()
219+ cfg := & packages.Config {
220+ Mode : packages .NeedName |
221+ packages .NeedFiles |
222+ packages .NeedSyntax |
223+ packages .NeedTypes |
224+ packages .NeedTypesInfo ,
225+ Tests : true ,
226+ }
227+ pkgs , loadErr := packages .Load (
228+ cfg ,
229+ "github.com/ActiveMemory/ctx/internal/..." ,
230+ )
231+ if loadErr != nil {
232+ t .Fatalf ("packages.Load tests: %v" , loadErr )
233+ }
234+ return pkgs
235+ }
236+
215237// isExported reports whether name starts with an
216238// uppercase letter.
217239func isExported (name string ) bool {
0 commit comments