Skip to content

feat(pattern)!: expand * in command-name patterns as a glob#343

Merged
fohte merged 4 commits into
mainfrom
fohte/pattern-cmd-glob
May 6, 2026
Merged

feat(pattern)!: expand * in command-name patterns as a glob#343
fohte merged 4 commits into
mainfrom
fohte/pattern-cmd-glob

Conversation

@fohte
Copy link
Copy Markdown
Owner

@fohte fohte commented May 6, 2026

Purpose

  • Support partial-glob patterns in command position, such as deny: '/* *' and allow: 'pre-* --help'
    • Today only a standalone * is a wildcard in command position; partial patterns like /* or pre-* are compared as literal strings, so a single rule cannot cover commands invoked by absolute path (e.g. /usr/local/bin/foo) or commands sharing a name prefix
    • Glob * inside literal tokens already works for argument tokens (fix(pattern): apply glob matching to literal tokens containing *); only command position was left behind

Approach

  • Route CommandPattern::matches through the same literal_matches used in token position, so * glob and \* escape semantics apply identically in command position
    • Re-export pattern_matcher::token_matching::literal_matches from the pattern_matcher module as pub(crate) so pattern_parser can call it
  • Each alternation alternative (e.g. the b* in a|b*) is glob-matched independently under the same rules
# Deny commands invoked by absolute path
- deny: '/* *'

# Allow `--help` only for commands whose name starts with `pre-`
- allow: 'pre-* --help'
Design decisions

Where the glob logic lives

Decision Design Pros Cons
Chosen Reuse token_matching::literal_matches via a pub(crate) re-export * / \* behaviour stays in lockstep between token position and command position by construction Adds a parser → matcher call edge (not a cycle)
Rejected Duplicate glob_match / unescape_and_match privately inside pattern_parser Keeps the dependency direction one-way (parser → matcher only) Same logic lives in two places and will drift when the token-position copy is changed

Breaking changes

  • Any existing rule whose command-name part contains a * other than a standalone * now matches strictly more commands than before
    • A rule like deny: 'g* *' used to be dead (no command is literally named g*); it now denies every command whose name starts with g
    • A quoted '*' in command position (e.g. '*' --help) used to only match a command literally named *; it now matches any single-token command name
  • Bare * --help (Wildcard) still expands across multi-token command names like docker compose --help, while '*' --help (Literal("*")) consumes only one token — that asymmetry is unchanged
  • To keep the previous literal-* behaviour, escape it: \* works the same in command position as in token position

fohte added 3 commits May 6, 2026 00:25
intent(pattern): let rules like `deny: '/* *'` or `allow: 'pre-* --help'` match
  by command-name prefix instead of being silently dead. Previously only a
  standalone `*` was a wildcard in command position; partial patterns were
  compared as literal strings.
decision(pattern): re-export `token_matching::literal_matches` as `pub(crate)`
  from `pattern_matcher::mod` and call it from `CommandPattern::matches`, so
  command-name and argument-token literals share the same `*`/`\*` semantics.
rejected(pattern): duplicating `glob_match`/`unescape_and_match` inside
  `pattern_parser` — would diverge from the token-position implementation as
  it evolves.
intent(releases): correct a misleading line that said `'*' --help` and
  `* --help` parse to the same rule — bare `*` (Wildcard) expands across
  multiple command-name tokens (e.g. `docker compose --help`) while quoted
  `'*'` (Literal) only consumes a single token.
intent(releases): move the entry into Highlights and frame it as breaking
  because rules whose command-name part contains a non-standalone `*` (e.g.
  `deny: 'g* *'`) silently change from dead to matching every command with
  that prefix, which can flip an evaluation from `allow` to `deny` for users
  who already had such patterns.
@fohte fohte changed the title feat(pattern): expand * in command-name patterns as a glob feat(pattern)!: expand * in command-name patterns as a glob May 6, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 6, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.05%. Comparing base (0c45b90) to head (8d9ad30).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #343      +/-   ##
==========================================
- Coverage   89.06%   89.05%   -0.01%     
==========================================
  Files          53       53              
  Lines       12657    12657              
==========================================
- Hits        11273    11272       -1     
- Misses       1384     1385       +1     
Flag Coverage Δ
Linux 88.88% <100.00%> (-0.01%) ⬇️
macOS 90.18% <100.00%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request enables '*' glob expansion for command names in patterns, allowing for more flexible rule definitions. Documentation and release notes have been updated to reflect this change. The reviewer suggests consolidating the newly added unit tests in src/rules/pattern_parser.rs into a single parameterized rstest function to improve maintainability and adhere to the project's style guide.

Comment thread src/rules/pattern_parser.rs
…st fn

intent(pattern): the literal and alternation cases share identical assertion
  logic — collapse them into a single parameterized function so adding a new
  case does not require deciding which sibling table to extend.
@fohte fohte merged commit d6655af into main May 6, 2026
10 checks passed
@fohte fohte deleted the fohte/pattern-cmd-glob branch May 6, 2026 07:42
@fohte-bot fohte-bot Bot mentioned this pull request May 6, 2026
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.

1 participant