Skip to content

Commit 649fdeb

Browse files
committed
fix: Documenting index logic better
1 parent 9cb74c6 commit 649fdeb

File tree

3 files changed

+64
-13
lines changed

3 files changed

+64
-13
lines changed

internal/discovery/discovery_test.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ func TestCandidacyClassifier_ClassifyComponent(t *testing.T) {
125125
filterStrings []string
126126
expectStatus filter.ClassificationStatus
127127
expectReason filter.CandidacyReason
128+
expectIndex int
128129
}{
129130
{
130131
name: "no filters - include by default",
@@ -133,6 +134,7 @@ func TestCandidacyClassifier_ClassifyComponent(t *testing.T) {
133134
workingDir: "/project",
134135
expectStatus: filter.StatusDiscovered,
135136
expectReason: filter.CandidacyReasonNone,
137+
expectIndex: -1,
136138
},
137139
{
138140
name: "matching path filter",
@@ -141,6 +143,7 @@ func TestCandidacyClassifier_ClassifyComponent(t *testing.T) {
141143
workingDir: "/project",
142144
expectStatus: filter.StatusDiscovered,
143145
expectReason: filter.CandidacyReasonNone,
146+
expectIndex: -1,
144147
},
145148
{
146149
name: "non-matching path filter - exclude by default",
@@ -149,6 +152,7 @@ func TestCandidacyClassifier_ClassifyComponent(t *testing.T) {
149152
workingDir: "/project",
150153
expectStatus: filter.StatusExcluded,
151154
expectReason: filter.CandidacyReasonNone,
155+
expectIndex: -1,
152156
},
153157
{
154158
name: "negated filter only - exclude component",
@@ -157,6 +161,7 @@ func TestCandidacyClassifier_ClassifyComponent(t *testing.T) {
157161
workingDir: "/project",
158162
expectStatus: filter.StatusExcluded,
159163
expectReason: filter.CandidacyReasonNone,
164+
expectIndex: -1,
160165
},
161166
{
162167
name: "negated filter only - include other",
@@ -165,6 +170,7 @@ func TestCandidacyClassifier_ClassifyComponent(t *testing.T) {
165170
workingDir: "/project",
166171
expectStatus: filter.StatusDiscovered,
167172
expectReason: filter.CandidacyReasonNone,
173+
expectIndex: -1,
168174
},
169175
{
170176
name: "graph expression target - candidate",
@@ -173,6 +179,7 @@ func TestCandidacyClassifier_ClassifyComponent(t *testing.T) {
173179
workingDir: "/project",
174180
expectStatus: filter.StatusCandidate,
175181
expectReason: filter.CandidacyReasonGraphTarget,
182+
expectIndex: 0,
176183
},
177184
{
178185
name: "parse required filter - candidate",
@@ -181,6 +188,7 @@ func TestCandidacyClassifier_ClassifyComponent(t *testing.T) {
181188
workingDir: "/project",
182189
expectStatus: filter.StatusCandidate,
183190
expectReason: filter.CandidacyReasonRequiresParse,
191+
expectIndex: -1,
184192
},
185193
}
186194

@@ -203,10 +211,11 @@ func TestCandidacyClassifier_ClassifyComponent(t *testing.T) {
203211
})
204212

205213
ctx := filter.ClassificationContext{}
206-
status, reason, _ := classifier.Classify(c, ctx)
214+
status, reason, index := classifier.Classify(c, ctx)
207215

208216
assert.Equal(t, tt.expectStatus, status, "status mismatch")
209217
assert.Equal(t, tt.expectReason, reason, "reason mismatch")
218+
assert.Equal(t, tt.expectIndex, index, "index mismatch")
210219
})
211220
}
212221
}

internal/discovery/doc.go

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,24 @@
1212
// This dual-channel approach enables lazy evaluation. Components are only parsed or
1313
// graph-traversed when necessary for filter evaluation.
1414
//
15+
// # Constructors
16+
//
17+
// The package provides several constructors for different use cases:
18+
//
19+
// - [NewDiscovery]: Creates a Discovery with sensible defaults including CPU-aware worker
20+
// count (scales with runtime.NumCPU, min 4, max 8) and pre-initialized [component.DiscoveryContext].
21+
// This is the recommended constructor for most use cases.
22+
//
23+
// - [NewForDiscoveryCommand]: Creates a Discovery configured for discovery commands (find/list)
24+
// with parse error suppression and cycle breaking enabled.
25+
//
26+
// - [NewForHCLCommand]: Creates a Discovery for HCL commands (validate/format).
27+
//
28+
// - [NewForStackGenerate]: Creates a Discovery for stack generate commands.
29+
//
1530
// # Classification Rules
1631
//
17-
// The [CandidacyClassifier] analyzes all filter expressions upfront and classifies
32+
// The [filter.Classifier] analyzes all filter expressions upfront and classifies
1833
// each component into one of three statuses:
1934
//
2035
// - [StatusDiscovered]: Matches a positive filter (path, attribute, or git expression)
@@ -29,7 +44,7 @@
2944
// The discovery process executes in the following phases:
3045
//
3146
// 1. Filesystem + Worktree Discovery (concurrent)
32-
// - [PhaseFilesystem]: Walk directories recursively, classify components via [CandidacyClassifier]
47+
// - [PhaseFilesystem]: Walk directories recursively, classify components via [filter.Classifier]
3348
// - [PhaseWorktree]: For Git filters [ref...ref], discover components in temporary worktrees
3449
// and detect added/removed/modified components via SHA256 comparison
3550
//
@@ -51,7 +66,7 @@
5166
//
5267
// 5. Final Phase
5368
// - [PhaseFinal]: Merge all discovered, deduplicate by path, apply final filter evaluation
54-
// - Cycle detection and removal if configured via WithBreakCycles
69+
// - Cycle detection and removal if configured via [Discovery.WithBreakCycles]
5570
//
5671
// # Filter Expressions
5772
//
@@ -63,11 +78,34 @@
6378
// - Git expressions: [main...develop] (changes between refs)
6479
// - Negated expressions: !./internal (exclusion)
6580
//
81+
// # Configuration Methods
82+
//
83+
// Discovery uses a fluent builder pattern. Available configuration methods include:
84+
//
85+
// - [Discovery.WithFilters]: Set filter queries for component selection
86+
// - [Discovery.WithRelationships]: Enable relationship discovery for execution ordering
87+
// - [Discovery.WithMaxDependencyDepth]: Set maximum dependency traversal depth (default 1000)
88+
// - [Discovery.WithNumWorkers]: Set concurrent worker count (default 4, max 8)
89+
// - [Discovery.WithBreakCycles]: Enable cycle detection and removal
90+
// - [Discovery.WithNoHidden]: Exclude hidden directories from discovery
91+
// - [Discovery.WithRequiresParse]: Force parsing of all Terragrunt configurations
92+
// - [Discovery.WithSuppressParseErrors]: Continue discovery despite parse errors
93+
// - [Discovery.WithParseExclude]: Parse exclude configurations
94+
// - [Discovery.WithParseIncludes]: Parse include configurations
95+
// - [Discovery.WithReadFiles]: Parse for file reading information
96+
// - [Discovery.WithDiscoveryContext]: Set the discovery context
97+
// - [Discovery.WithWorktrees]: Set worktrees for Git-based filters
98+
// - [Discovery.WithConfigFilenames]: Set custom config filenames to discover
99+
// - [Discovery.WithParserOptions]: Set custom HCL parser options
100+
// - [Discovery.WithGitRoot]: Set git root for dependent discovery boundary
101+
// - [Discovery.WithGraphTarget]: Set graph target for pruning results
102+
// - [Discovery.WithReport]: Set report for recording excluded dependencies
103+
// - [Discovery.WithOptions]: Ingest runner options for parser and graph settings
104+
//
66105
// # Example Usage
67106
//
68-
// d := discovery.New(workingDir).
107+
// d := NewDiscovery(workingDir).
69108
// WithFilters(filters).
70-
// WithDiscoveryContext(discoveryContext).
71109
// WithRelationships().
72110
// WithMaxDependencyDepth(10)
73111
//

internal/filter/classifier.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -193,15 +193,19 @@ func (c *Classifier) analyzeExpression(expr Expression, filterIndex int) {
193193
// Classify determines whether a component should be discovered, is a candidate,
194194
// or should be excluded based on the analyzed filters.
195195
//
196+
// Returns the classification status, the reason for candidacy (if applicable),
197+
// and the index of the matching graph expression (-1 if not a graph target match).
198+
//
196199
// Classification algorithm:
197200
// 1. Check if component ONLY matches negated filters -> EXCLUDED
198-
// 2. Check if component matches any positive filesystem filter -> DISCOVERED
199-
// 3. Check if component matches any graph expression target -> CANDIDATE (GraphTarget)
200-
// 4. Check if parse expressions exist and component not yet classified -> CANDIDATE (RequiresParse)
201-
// 5. Check if dependent filters exist (component might be a dependent) -> CANDIDATE (PotentialDependent)
202-
// 6. If negated expressions exist and component doesn't match any -> DISCOVERED (negation acts as inclusion)
203-
// 7. If positive filters exist but no match -> EXCLUDED (exclude-by-default)
204-
// 8. If no positive filters exist -> DISCOVERED (include-by-default)
201+
// 2. Check if parse expressions exist and parse data unavailable -> CANDIDATE (RequiresParse)
202+
// 3. Check if component matches any positive filesystem filter -> DISCOVERED
203+
// 4. Check if component matches any git expression -> DISCOVERED
204+
// 5. Check if component matches any graph expression target -> CANDIDATE (GraphTarget, returns index)
205+
// 6. Check if dependent filters exist and parse data unavailable -> CANDIDATE (PotentialDependent)
206+
// 7. If negated expressions exist and component doesn't match any -> DISCOVERED (negation acts as inclusion)
207+
// 8. If positive filters exist but no match -> EXCLUDED (exclude-by-default)
208+
// 9. If no positive filters exist -> DISCOVERED (include-by-default)
205209
func (c *Classifier) Classify(comp component.Component, ctx ClassificationContext) (ClassificationStatus, CandidacyReason, int) {
206210
hasNegativeMatch := c.matchesAnyNegated(comp)
207211
hasPositiveMatch := c.matchesAnyPositive(comp, ctx)

0 commit comments

Comments
 (0)