Skip to content

Conversation

@sofianedjerbi
Copy link

@sofianedjerbi sofianedjerbi commented Dec 20, 2025

Description

Fixes #5267.

Adds depth-limited traversal syntax by placing the depth number outside the ellipsis:

foo...1      # foo + direct dependencies only
foo...2      # foo + dependencies up to 2 levels
1...foo      # foo + direct dependents only
2...foo      # foo + dependents up to 2 levels
1...foo...2  # depth 1 dependents + foo + depth 2 dependencies
1...^foo     # direct dependents of foo, excluding foo

Existing ... syntax stays unchanged (unlimited traversal).

Numeric directory disambiguation

Purely numeric tokens adjacent to ... are interpreted as depths. For numeric directory names, use escape hatches:

1...1        # depth 1 dependents of dir "1" (first number is depth)
{1}...1      # dir "1" with dependency depth 1 (braced path)
name=1...1   # name=1 with dependency depth 1 (explicit attribute)

TODOs

Read the Gruntwork contribution guidelines.

  • I authored this code entirely myself
  • I am submitting code based on open source software (e.g. MIT, MPL-2.0, Apache)
  • I am adding or upgrading a dependency or adapted code and confirm it has a compatible open source license
  • Update the docs.
  • Run the relevant tests successfully, including pre-commit checks.
  • Include release notes. If this PR is backward incompatible, include a migration guide.

Release Notes (draft)

Added depth-limited graph traversal syntax for filter expressions: N...target...N.

Summary by CodeRabbit

  • New Features

    • Graph traversal operators support explicit depth limits for dependents and dependencies (e.g., "1...foo", "foo...2") and show depths in string form.
    • Parser and lexer enhanced to reliably recognize ellipses, depth tokens, and refined path/name disambiguation.
  • Documentation

    • Added detailed guide for traversal operators, depth-limiting semantics, numeric-token disambiguation, and braced-path syntax.
  • Tests

    • Extensive new and expanded tests covering depth-limited parsing, tokenization, evaluation, and stringification.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Dec 20, 2025

@sofianedjerbi is attempting to deploy a commit to the Gruntwork Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 20, 2025

📝 Walkthrough

Walkthrough

Adds numeric depth syntax for graph traversal: lexer recognizes ellipses and numeric tokens adjacent to them; parser records per-direction depth into GraphExpression; evaluator enforces and propagates depth limits during traversal; docs and tests updated to exercise new syntax and behaviors.

Changes

Cohort / File(s) Summary
AST
internal/filter/ast.go
Added DependentDepth and DependencyDepth int fields to GraphExpression; updated String() to render prefix/suffix depths and updated docs for depth syntax.
Lexer & tests
internal/filter/lexer.go, internal/filter/lexer_test.go
Ellipsis detection (...) added in tokenization; readers stop at ellipsis boundaries; tests added for depth vs path disambiguation and many ellipsis/depth token cases.
Parser & tests
internal/filter/parser.go, internal/filter/parser_test.go, internal/filter/complex_test.go
Parse prefix/postfix numeric depths (N...target, target...N), added isPurelyNumeric/parseDepth (clamped to MaxTraversalDepth), populate AST depth fields, exposed ParseExpression() entry (returns (Expression, error)), and added extensive parsing/stringification and error tests.
Evaluator & tests
internal/filter/evaluator.go, internal/filter/evaluator_test.go
Use AST depth fields per-direction (fallback to MaxTraversalDepth when unspecified), traverse functions accept and propagate originalDepth, adjust logging conditions, and added depth-bounded/unlimited traversal tests (including duplicated suite).
Documentation
internal/filter/doc.go
Added "Graph Traversal Operators" section describing prefix/postfix depth forms, braced-path syntax, numeric disambiguation, examples, and precedence/semantics notes.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Lexer
    participant Parser
    participant AST as GraphExpression
    participant Evaluator
    participant Traversal as GraphTraversal

    rect rgb(240,248,255)
    Lexer->>Parser: Tokenize input (IDENT / NUMBER / ELLIPSIS / PATH ...)
    end

    rect rgb(245,255,240)
    Parser->>Parser: Detect depth tokens (N... / ...N), paths, carets
    Parser->>AST: Construct GraphExpression (IncludeDependents/IncludeDependencies, DependentDepth, DependencyDepth)
    Parser-->>Evaluator: Return parsed Expression
    end

    rect rgb(255,250,240)
    Evaluator->>AST: Inspect depth fields
    alt Dependency traversal requested
        Evaluator->>Traversal: traverseDependencies(maxDepth=DependencyDepth||Max, originalDepth)
    else Dependent traversal requested
        Evaluator->>Traversal: traverseDependents(maxDepth=DependentDepth||Max, originalDepth)
    end
    Traversal->>Traversal: Recurse with (currentDepth-1, originalDepth) until limit or unlimited
    Traversal-->>Evaluator: Return visited nodes
    Evaluator-->>Parser: Return filtered result set
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • ThisGuyCodes
  • denis256
  • yhakbar

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: adding depth control for dependency traversal in filter expressions.
Description check ✅ Passed The description covers the main objectives, examples, and syntax details. However, it lacks a specific issue reference in 'Fixes #000' section and some TODOs are incomplete.
Linked Issues check ✅ Passed The PR successfully implements all requirements from #5267: lexer/parser support for depth syntax, depth fields in GraphExpression, evaluator depth limit handling, backward compatibility, and comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing depth-limited graph traversal. No unrelated modifications detected beyond the stated objectives.
✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1102530 and f7dde26.

📒 Files selected for processing (9)
  • internal/filter/ast.go
  • internal/filter/complex_test.go
  • internal/filter/doc.go
  • internal/filter/evaluator.go
  • internal/filter/evaluator_test.go
  • internal/filter/lexer.go
  • internal/filter/lexer_test.go
  • internal/filter/parser.go
  • internal/filter/parser_test.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • internal/filter/complex_test.go
  • internal/filter/lexer_test.go
  • internal/filter/doc.go
  • internal/filter/ast.go
  • internal/filter/evaluator_test.go
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

⚙️ CodeRabbit configuration file

Review the Go code for quality and correctness. Make sure that the Go code follows best practices, is performant, and is easy to understand and maintain.

Files:

  • internal/filter/evaluator.go
  • internal/filter/parser_test.go
  • internal/filter/parser.go
  • internal/filter/lexer.go
🧠 Learnings (1)
📚 Learning: 2025-08-19T16:05:54.723Z
Learnt from: Resonance1584
Repo: gruntwork-io/terragrunt PR: 4683
File: go.mod:86-90
Timestamp: 2025-08-19T16:05:54.723Z
Learning: When analyzing Go module dependencies for removal, always check for both direct imports and API usage across all Go files in the repository, not just a quick search. The github.com/mattn/go-zglob library is used for filesystem walking and glob expansion in multiple Terragrunt files including util/file.go, format commands, and AWS provider patch functionality.

Applied to files:

  • internal/filter/lexer.go
🧬 Code graph analysis (2)
internal/filter/parser_test.go (2)
internal/filter/ast.go (3)
  • GraphExpression (211-218)
  • AttributeExpression (63-69)
  • PathExpression (31-36)
internal/filter/evaluator.go (1)
  • MaxTraversalDepth (26-26)
internal/filter/parser.go (2)
internal/filter/token.go (1)
  • ELLIPSIS (31-31)
internal/filter/evaluator.go (1)
  • MaxTraversalDepth (26-26)
🔇 Additional comments (19)
internal/filter/lexer.go (4)

63-75: LGTM on ellipsis detection logic.

The three-dot ellipsis detection correctly identifies ... sequences and emits them as distinct ELLIPSIS tokens. The bounds checking (l.readPosition+1 < len(l.input)) properly prevents out-of-bounds access.


169-178: LGTM on ellipsis boundary handling in readIdentifier.

The ellipsis boundary check correctly stops identifier parsing when encountering ..., ensuring depth tokens can be properly recognized by the parser.


191-200: LGTM on ellipsis boundary handling in readAttributeValue.

Consistent with readIdentifier, this properly stops attribute value parsing at ellipsis boundaries.


212-221: LGTM on ellipsis boundary handling in readPath.

Consistent boundary handling across all read functions ensures the lexer correctly tokenizes depth-limited expressions like ./path...1.

internal/filter/evaluator.go (6)

254-262: Depth handling for dependencies looks correct.

The logic correctly falls back to MaxTraversalDepth when DependencyDepth is 0 (unset), and passes maxDepth as both current and original depth parameters.


267-275: Depth handling for dependents mirrors dependencies correctly.

Consistent implementation for the dependent direction with proper fallback to unlimited traversal.


314-323: Warning suppression for user-specified depths is appropriate.

The condition originalDepth == MaxTraversalDepth ensures warnings only appear when the system-imposed limit is reached during unlimited traversal, not when users intentionally specify a depth limit.


337-337: Recursive call correctly propagates originalDepth.

The pattern maxDepth-1, originalDepth correctly decrements the remaining depth while preserving the original value for logging decisions.


350-359: Consistent warning suppression in traverseDependents.

Same pattern as traverseDependencies - warnings only for unlimited traversal hitting the system cap.


373-373: Recursive call in traverseDependents follows the same correct pattern.

internal/filter/parser_test.go (4)

444-473: String representation tests for depth syntax look good.

Tests verify that depth values are correctly rendered in the string output (e.g., 1...name=foo, name=foo...1, 2...name=foo...3).


662-873: Comprehensive test coverage for depth-limited graph expressions.

The tests cover:

  • Single-direction depths (1...foo, foo...1)
  • Bidirectional depths (2...foo...3)
  • Depth with caret (1...^foo...2)
  • Multi-digit depths (10...foo...25)
  • Clamping to MaxTraversalDepth for large values
  • Overflow fallback to 0 (unlimited)
  • Numeric directory disambiguation with escape hatches ({1}...1, name=1...1)

This provides excellent coverage for the new syntax.


893-894: Test assertions updated to verify new depth fields.

The assertions for DependentDepth and DependencyDepth ensure the parser correctly populates these fields.


1026-1042: Error case tests for depth syntax are appropriate.

Tests verify that 1... (depth without target) and 1......2 (double ellipsis with no target) correctly produce errors.

internal/filter/parser.go (5)

75-89: Prefix depth parsing correctly handles N...foo syntax.

The logic properly distinguishes between a numeric depth followed by ellipsis versus a regular identifier. Using isPurelyNumeric ensures only pure digit tokens are treated as depths, preventing misinterpretation of alphanumeric names like 1foo.


139-152: Postfix depth parsing correctly handles foo...N syntax.

After consuming the ellipsis, checking for a numeric token and parsing it as depth follows the same pattern as prefix handling.


162-163: GraphExpression construction includes new depth fields.

The depth values are correctly propagated to the AST node.


181-194: isPurelyNumeric helper is straightforward and correct.

The function correctly identifies strings containing only ASCII digits. This is necessary to distinguish depth tokens from identifiers like 1foo or foo1.


196-209: parseDepth handles edge cases appropriately.

The function:

  • Returns 0 (treated as unlimited) on parse failure, including integer overflow
  • Clamps large but parseable values to MaxTraversalDepth
  • Handles negative values by returning 0 (though isPurelyNumeric should prevent negative strings)

One minor observation: strconv.Atoi can only return negative values if the string contains a minus sign, which isPurelyNumeric would reject. The depth < 0 check is defensive but harmless.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/filter/evaluator.go (1)

313-320: Warning messages reference wrong depth value.

The warning messages in both traverseDependencies (line 316-317) and traverseDependents (line 350-351) format the message with MaxTraversalDepth constant instead of the actual maxDepth parameter being used. When users specify a custom depth limit (e.g., foo..2), the warning will incorrectly report 1000000 instead of 2.

🔎 Proposed fix
 	if maxDepth <= 0 {
 		if l != nil {
 			l.Warnf(
 				"Maximum dependency traversal depth (%d) reached for component %s during filtering. Some dependencies may have been excluded from results.",
-				MaxTraversalDepth,
+				maxDepth,
 				c.Path(),
 			)
 		}

Apply the same fix to traverseDependents (line 350-351):

 	if maxDepth <= 0 {
 		if l != nil {
 			l.Warnf(
 				"Maximum dependent traversal depth (%d) reached for component %s during filtering. Some dependents may have been excluded from results.",
-				MaxTraversalDepth,
+				maxDepth,
 				c.Path(),
 			)
 		}

Also applies to: 348-355

🧹 Nitpick comments (1)
internal/filter/ast.go (1)

220-233: Consider extending NewGraphExpression to accept depth parameters.

The constructor initializes the struct without the new DependentDepth and DependencyDepth fields. While the zero-value default (unlimited traversal) is correct, callers now need to modify the struct directly after construction to set depth limits.

For API consistency and discoverability, consider adding depth parameters to the constructor or creating an alternative constructor:

🔎 Possible constructor signature
func NewGraphExpressionWithDepth(
	target Expression,
	includeDependents bool,
	includeDependencies bool,
	excludeTarget bool,
	dependentDepth int,
	dependencyDepth int,
) *GraphExpression

Alternatively, keep the current constructor for backward compatibility and have the parser set the fields directly as it currently does.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 889c9ca and 4afa933.

📒 Files selected for processing (9)
  • internal/filter/ast.go (3 hunks)
  • internal/filter/doc.go (1 hunks)
  • internal/filter/evaluator.go (1 hunks)
  • internal/filter/evaluator_test.go (1 hunks)
  • internal/filter/lexer.go (5 hunks)
  • internal/filter/lexer_test.go (1 hunks)
  • internal/filter/parser.go (6 hunks)
  • internal/filter/parser_test.go (2 hunks)
  • internal/filter/token.go (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

⚙️ CodeRabbit configuration file

Review the Go code for quality and correctness. Make sure that the Go code follows best practices, is performant, and is easy to understand and maintain.

Files:

  • internal/filter/doc.go
  • internal/filter/parser_test.go
  • internal/filter/ast.go
  • internal/filter/evaluator_test.go
  • internal/filter/lexer_test.go
  • internal/filter/token.go
  • internal/filter/evaluator.go
  • internal/filter/lexer.go
  • internal/filter/parser.go
🧠 Learnings (1)
📚 Learning: 2025-08-19T16:05:54.723Z
Learnt from: Resonance1584
Repo: gruntwork-io/terragrunt PR: 4683
File: go.mod:86-90
Timestamp: 2025-08-19T16:05:54.723Z
Learning: When analyzing Go module dependencies for removal, always check for both direct imports and API usage across all Go files in the repository, not just a quick search. The github.com/mattn/go-zglob library is used for filesystem walking and glob expansion in multiple Terragrunt files including util/file.go, format commands, and AWS provider patch functionality.

Applied to files:

  • internal/filter/ast.go
🧬 Code graph analysis (5)
internal/filter/parser_test.go (2)
internal/filter/ast.go (2)
  • GraphExpression (211-218)
  • AttributeExpression (63-69)
internal/filter/evaluator.go (1)
  • MaxTraversalDepth (26-26)
internal/filter/evaluator_test.go (4)
internal/component/unit.go (1)
  • NewUnit (46-54)
internal/component/component.go (1)
  • Component (35-59)
internal/filter/ast.go (2)
  • GraphExpression (211-218)
  • AttributeExpression (63-69)
internal/filter/evaluator.go (1)
  • Evaluate (40-61)
internal/filter/lexer_test.go (1)
internal/filter/token.go (6)
  • Token (73-77)
  • ELLIPSIS (31-31)
  • EOF (11-11)
  • ELLIPSIS_DEPTH (32-32)
  • PATH (17-17)
  • IDENT (14-14)
internal/filter/lexer.go (1)
internal/filter/token.go (2)
  • NewToken (80-86)
  • ELLIPSIS_DEPTH (32-32)
internal/filter/parser.go (2)
internal/filter/token.go (14)
  • ELLIPSIS (31-31)
  • ELLIPSIS_DEPTH (32-32)
  • PIPE (21-21)
  • EQUAL (22-22)
  • RBRACE (26-26)
  • RBRACKET (28-28)
  • CARET (33-33)
  • EOF (11-11)
  • ILLEGAL (8-8)
  • IDENT (14-14)
  • PATH (17-17)
  • BANG (20-20)
  • LBRACE (25-25)
  • LBRACKET (27-27)
internal/filter/evaluator.go (1)
  • MaxTraversalDepth (26-26)
🔇 Additional comments (17)
internal/filter/lexer_test.go (1)

130-196: Comprehensive test coverage for new ellipsis depth tokens.

The new test cases thoroughly cover the ELLIPSIS and ELLIPSIS_DEPTH token types including:

  • Basic ellipsis and depth-annotated ellipsis tokens
  • Edge cases like parent directory paths (../foo) not being confused with depth syntax
  • Token boundary detection (foo..1 correctly splits into IDENT + ELLIPSIS_DEPTH)
  • Precedence rules (... takes precedence over ..N)

The tests are well-structured and follow the existing patterns in the file.

internal/filter/doc.go (1)

73-92: Clear and comprehensive documentation for depth-limited traversal.

The documentation effectively explains:

  • Basic graph traversal operators (...)
  • New depth-limited syntax (..N)
  • Combined usage patterns (..1foo..2)
  • Default unlimited traversal when depth is 0 or unspecified

The examples are practical and align with the implementation.

internal/filter/ast.go (1)

236-262: String() method correctly renders depth-limited expressions.

The logic properly handles both depth-limited (..N) and unlimited (...) forms for both dependent and dependency directions. The conditional rendering ensures round-trip consistency between parsing and string representation.

internal/filter/token.go (2)

31-33: LGTM!

The new ELLIPSIS_DEPTH token is properly positioned with the other graph operators and follows the existing naming conventions.


63-64: Appropriate string representation for debugging.

Using "..N" as the string representation is clear for debugging purposes, while the actual depth value (e.g., "..1", "..25") is preserved in Token.Literal.

internal/filter/parser_test.go (3)

632-704: Thorough test coverage for depth-limited graph expressions.

The new test cases comprehensively cover:

  • Single-direction depth limits (prefix and postfix)
  • Bidirectional depth limits with different values
  • Depth combined with caret (exclude target)
  • Multi-digit depth values

The tests properly assert both DependentDepth and DependencyDepth fields are populated correctly.


705-732: Good edge case coverage for depth overflow handling.

The tests verify two important edge cases:

  1. Very large depth clamped to max (line 706-718): Values exceeding MaxTraversalDepth are clamped rather than causing errors
  2. Integer overflow falls back to unlimited (line 719-732): Values that overflow int parsing result in DependentDepth=0 (unlimited traversal)

The overflow-to-unlimited fallback is a reasonable choice for robustness, though it may be worth documenting this behavior so users understand that extremely large values like ..99999999999999999999999 don't error but instead behave as unlimited.

Please confirm the overflow fallback behavior (using unlimited traversal when depth parsing fails) is the intended design. If a user typos a very large number, they might not realize they're getting unlimited traversal instead of an error.


748-753: Assertions updated to verify new depth fields.

The assertions for DependentDepth and DependencyDepth are properly added to validate the parsing of depth-limited expressions.

internal/filter/evaluator_test.go (1)

978-1078: LGTM! Comprehensive test coverage for depth-limited graph traversal.

The test suite thoroughly validates the new depth-control feature across both dependency and dependent directions, as well as the unlimited-depth case. The linear graph structure (a → b → c → d) provides clear, predictable traversal paths for verification.

internal/filter/lexer.go (3)

76-88: LGTM! Depth ellipsis token recognition is correctly implemented.

The logic properly distinguishes between ... (unlimited) and ..N (depth-limited) patterns, reading all consecutive digits and emitting the appropriate ELLIPSIS_DEPTH token.


183-190: LGTM! Consistent ellipsis boundary detection across all reading methods.

The updates to readIdentifier, readAttributeValue, and readPath consistently stop at both ... and ..N boundaries, ensuring proper token separation.

Also applies to: 208-215, 232-239


298-301: LGTM! Simple and correct digit detection helper.

internal/filter/evaluator.go (1)

255-258: LGTM! Depth computation logic is correct.

The evaluator properly uses DependencyDepth and DependentDepth when specified, falling back to MaxTraversalDepth for unlimited traversal (depth = 0).

Also applies to: 268-271

internal/filter/parser.go (4)

75-86: LGTM! Prefix ellipsis depth parsing is correct.

The parser properly handles both unlimited (...) and depth-limited (..N) dependent traversal prefixes, extracting the depth value and storing it for GraphExpression construction.


136-147: LGTM! Postfix ellipsis depth parsing is correct.

The parser properly handles both unlimited (...) and depth-limited (..N) dependency traversal postfixes, extracting the depth value and storing it for GraphExpression construction.


150-159: LGTM! Depth fields correctly propagated to GraphExpression.

The parsed depth values are properly set in the GraphExpression, enabling depth-controlled traversal in the evaluator.


175-192: LGTM! Robust depth extraction with appropriate bounds checking.

The parseEllipsisDepth function correctly:

  • Validates minimum literal length
  • Parses the numeric portion after ".."
  • Returns 0 (unlimited) for invalid input or negative values
  • Clamps excessively large values to MaxTraversalDepth

Edge cases are well-handled.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/filter/parser.go (1)

194-207: Inconsistent behavior between large and overflowing depth values.

The function clamps values greater than MaxTraversalDepth to MaxTraversalDepth, but returns 0 (unlimited) for values that cause strconv.Atoi to overflow. This creates inconsistent behavior:

  • 999999999...foo → depth clamped to MaxTraversalDepth
  • 99999999999999999999999...foo → depth = 0 (unlimited)

Consider either clamping overflow to MaxTraversalDepth or returning an error. One approach:

🔎 Suggested fix for consistent clamping
 func parseDepth(literal string) int {
 	depth, err := strconv.Atoi(literal)
-	if err != nil || depth < 0 {
-		return 0
+	if err != nil {
+		// Overflow or invalid - clamp to max
+		return MaxTraversalDepth
+	}
+
+	if depth < 0 {
+		return 0
 	}
 
 	if depth > MaxTraversalDepth {
 		return MaxTraversalDepth
 	}
 
 	return depth
 }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c362af and 72211ce.

📒 Files selected for processing (7)
  • internal/filter/ast.go (4 hunks)
  • internal/filter/complex_test.go (1 hunks)
  • internal/filter/doc.go (1 hunks)
  • internal/filter/lexer.go (5 hunks)
  • internal/filter/lexer_test.go (1 hunks)
  • internal/filter/parser.go (5 hunks)
  • internal/filter/parser_test.go (4 hunks)
✅ Files skipped from review due to trivial changes (1)
  • internal/filter/doc.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/filter/ast.go
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

⚙️ CodeRabbit configuration file

Review the Go code for quality and correctness. Make sure that the Go code follows best practices, is performant, and is easy to understand and maintain.

Files:

  • internal/filter/complex_test.go
  • internal/filter/lexer.go
  • internal/filter/parser_test.go
  • internal/filter/lexer_test.go
  • internal/filter/parser.go
🧬 Code graph analysis (3)
internal/filter/complex_test.go (3)
internal/filter/lexer.go (1)
  • NewLexer (18-23)
internal/filter/parser.go (1)
  • NewParser (30-41)
internal/discovery/discovery.go (1)
  • String (325-327)
internal/filter/parser_test.go (2)
internal/filter/ast.go (3)
  • GraphExpression (211-218)
  • AttributeExpression (63-69)
  • PathExpression (31-36)
internal/filter/evaluator.go (1)
  • MaxTraversalDepth (26-26)
internal/filter/lexer_test.go (1)
internal/filter/token.go (5)
  • Token (70-74)
  • ELLIPSIS (31-31)
  • EOF (11-11)
  • IDENT (14-14)
  • PATH (17-17)
🔇 Additional comments (10)
internal/filter/lexer.go (1)

170-175: Ellipsis boundary detection is consistent across token readers.

The logic correctly stops at ... (three consecutive dots) in readIdentifier, readAttributeValue, and readPath, while allowing .. patterns (like ..1 or foo..bar) to be captured as identifiers. This aligns with the PR objective where ..N syntax is handled at the parser level, not the lexer level.

Also applies to: 192-197, 213-218

internal/filter/complex_test.go (2)

79-83: Clarify intended behavior for parentheses in expressions.

The test expects 1...(foo | bar) to parse as 1...name=(foo | name=bar), treating (foo as the name value. Is this the intended behavior? Parentheses are not typically valid in filter names, and users might expect grouping semantics. Consider whether this should be an error case or if documentation should clarify this limitation.


10-102: Good test coverage for complex depth expressions.

The test suite covers a comprehensive set of scenarios including intersections, negations, mixed unlimited/limited depth, paths, braces, attributes, and carets. The parallel test execution and use of table-driven tests follows Go best practices.

internal/filter/lexer_test.go (1)

130-226: Comprehensive lexer tests for depth and ellipsis tokenization.

The new test cases thoroughly cover the tokenization rules for depth syntax:

  • Ellipsis (...) correctly produces ELLIPSIS token
  • Double dots with digits (..1, ..25) remain as IDENT
  • Parent directory paths (../foo) correctly produce PATH
  • Mixed patterns (foo..bar, foo..1) stay as single IDENT
  • Depth syntax patterns (1...foo, foo...1, 1...foo...2) correctly split into separate tokens

This provides good regression coverage for the new feature.

internal/filter/parser_test.go (3)

749-762: Verify overflow behavior is intentional.

The test expects that an overflowing depth value (99999999999999999999999) results in DependentDepth: 0 (unlimited). This silent fallback could mask user input errors. Consider whether this should instead:

  1. Return an error for unparseable depth values
  2. Clamp to MaxTraversalDepth like other large values
  3. Document this as intentional "unlimited" fallback behavior

662-873: Thorough test coverage for depth-limited graph expressions.

The tests comprehensively cover:

  • Basic depth prefixes and postfixes
  • Multi-digit depths and clamping to MaxTraversalDepth
  • Numeric directory disambiguation with escape hatches ({1}, name=1)
  • Alphanumeric identifiers correctly distinguished from depth values

This provides strong regression protection for the new depth feature.


1026-1042: Good error case coverage for incomplete depth expressions.

The new error cases properly verify that malformed depth expressions (e.g., 1... without target, 1... with trailing space, 1......2 with no target between) are rejected with errors.

internal/filter/parser.go (3)

75-88: Prefix depth parsing logic is correct.

The logic properly handles both forms:

  1. N...foo - numeric prefix followed by ellipsis
  2. ...foo - simple ellipsis prefix

The isPurelyNumeric check ensures alphanumeric identifiers like foo1... aren't mistakenly treated as depth prefixes.


179-192: LGTM - Simple and efficient numeric check.

The isPurelyNumeric function correctly handles the empty string case and efficiently checks for ASCII digits without allocations. This is appropriate for the use case of detecting depth prefixes.


138-162: Postfix depth parsing and GraphExpression construction look correct.

The logic properly:

  1. Detects postfix ellipsis and sets includeDependencies
  2. Optionally parses trailing numeric depth
  3. Constructs GraphExpression with both DependentDepth and DependencyDepth

The conditional wrapping in GraphExpression only occurs when graph operators are present, preserving backward compatibility.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
internal/filter/lexer.go (1)

170-175: Consider extracting the ellipsis boundary check into a helper function.

The same ellipsis detection logic is repeated in readIdentifier, readAttributeValue, and readPath. Extracting this into a helper like isLookingAtEllipsis() would reduce duplication and centralize the logic.

🔎 Proposed refactor
+// isLookingAtEllipsis returns true if the lexer is positioned at the start of "...".
+func (l *Lexer) isLookingAtEllipsis() bool {
+	return l.ch == '.' && l.peekChar() == '.' &&
+		l.readPosition+1 < len(l.input) && l.input[l.readPosition+1] == '.'
+}
+
 // readIdentifier reads an identifier from the input.
 // Identifiers can contain letters, numbers, underscores, hyphens, dots, and other non-special chars.
 // This includes hidden files starting with a dot like .gitignore
 // Trailing whitespace is trimmed.
 func (l *Lexer) readIdentifier() string {
 	position := l.position
 	for isIdentifierChar(l.ch) {
-		// stop at ellipsis (...)
-		if l.ch == '.' && l.peekChar() == '.' {
-			if l.readPosition+1 < len(l.input) && l.input[l.readPosition+1] == '.' {
-				break
-			}
+		if l.isLookingAtEllipsis() {
+			break
 		}
 
 		l.readChar()
 	}

Apply the same simplification to readAttributeValue and readPath.

Also applies to: 192-197, 213-218

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 72211ce and a559fec.

📒 Files selected for processing (1)
  • internal/filter/lexer.go (4 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

⚙️ CodeRabbit configuration file

Review the Go code for quality and correctness. Make sure that the Go code follows best practices, is performant, and is easy to understand and maintain.

Files:

  • internal/filter/lexer.go
🧠 Learnings (1)
📚 Learning: 2025-08-19T16:05:54.723Z
Learnt from: Resonance1584
Repo: gruntwork-io/terragrunt PR: 4683
File: go.mod:86-90
Timestamp: 2025-08-19T16:05:54.723Z
Learning: When analyzing Go module dependencies for removal, always check for both direct imports and API usage across all Go files in the repository, not just a quick search. The github.com/mattn/go-zglob library is used for filesystem walking and glob expansion in multiple Terragrunt files including util/file.go, format commands, and AWS provider patch functionality.

Applied to files:

  • internal/filter/lexer.go
🔇 Additional comments (2)
internal/filter/lexer.go (2)

63-75: LGTM!

The ellipsis detection correctly identifies "..." sequences and emits an ELLIPSIS token. The boundary check and fallthrough logic for ".." patterns (parent directory) are properly handled.


260-274: LGTM!

The helper functions isSpecialChar, isPathSeparator, isIdentifierChar, isAttributeValueChar, and isPathChar correctly define the character classification logic for the lexer. The previously flagged unused isDigit function has been removed.

@denis256
Copy link
Member

Failed on lint

internal/filter/complex_test.go:1:9: package should be `filter_test` instead of `filter` (testpackage)
package filter
        ^
internal/filter/parser.go:87:3: missing whitespace above this line (no shared variables above expr) (wsl_v5)
		p.nextToken()
		^
internal/filter/parser.go:144:3: missing whitespace above this line (no shared variables above expr) (wsl_v5)
		p.nextToken()
		^
3 issues:
* testpackage: 1
* wsl_v5: 2

@sofianedjerbi
Copy link
Author

Failed on lint

internal/filter/complex_test.go:1:9: package should be `filter_test` instead of `filter` (testpackage)
package filter
        ^
internal/filter/parser.go:87:3: missing whitespace above this line (no shared variables above expr) (wsl_v5)
		p.nextToken()
		^
internal/filter/parser.go:144:3: missing whitespace above this line (no shared variables above expr) (wsl_v5)
		p.nextToken()
		^
3 issues:
* testpackage: 1
* wsl_v5: 2

Fixed in 1102530

Adds ..N syntax to limit dependency/dependent traversal depth.
For example, foo..1 only includes direct dependencies.
Move depth numbers outside the ellipsis for clearer parsing.
This avoids ambiguity with numeric directory names like "1" or "123".

  Before: ..1foo..2  (is ..11 depth 11 or depth 1 + dir "1"?)
  After:  1...foo...2 (unambiguous, ellipsis is always ...)

For numeric dirs, use braces or explicit attributes: {1}...1 or name=1...1
- Change complex_test.go package to filter_test (testpackage rule)
- Add blank lines before statements in parser.go and evaluator_test.go
@sofianedjerbi sofianedjerbi force-pushed the feature/filter-depth-control branch from 1102530 to f7dde26 Compare January 2, 2026 00:24
@sofianedjerbi
Copy link
Author

Just updated PR to fix CI

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(filter): add depth control for dependency traversal

2 participants