Skip to content

feat(task): support exclusion groups in task name wildcards#34506

Merged
bartlomieju merged 2 commits into
mainfrom
feat/task-wildcard-exclusion
May 30, 2026
Merged

feat(task): support exclusion groups in task name wildcards#34506
bartlomieju merged 2 commits into
mainfrom
feat/task-wildcard-exclusion

Conversation

@bartlomieju

Copy link
Copy Markdown
Member

Adds a (!a|b|c) exclusion syntax to deno task wildcard patterns,
modeled after concurrently. For example,
deno task "test:*(!e2e|interactive)" now matches test:unit and
test:integration but skips test:e2e and test:interactive.

The exclusion group is parsed off the end of the argument, and each
listed value is matched against what a * in the pattern captured. A
pattern without a * but with a (!...) group is rejected, since
there is nothing to exclude from.

This is the common case from #29355; more elaborate forms (multiple
exclusion groups, sub-wildcards inside the group) are intentionally
not handled.

Closes #29355.

Adds a `(!a|b|c)` exclusion syntax (modeled after `concurrently`) to
`deno task` wildcard patterns. For example, `deno task "test:*(!e2e|interactive)"`
now matches `test:unit` and `test:integration` but skips `test:e2e` and
`test:interactive`.

Closes #29355

@fibibot fibibot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

lint debug linux-x86_64 is red on this head — clippy collapsible_if at cli/tools/task.rs:1238. Collapse the nested if let Some(m) = caps.get(i) { if exclusions.iter()… } into a let-chain: if let Some(m) = caps.get(i) && exclusions.iter().any(|e| e == m.as_str()). Thats the only blocker.

One question, non-blocking: switching the anchor from ^{pat} to ^{pat}$ changes matching for non-trailing wildcards beyond the exclusion feature — e.g. *:test previously matched a:test:b (prefix) and now requires the name to end in :test. If that tightening is intended, fine; if not, the $ isnt needed for exclusions since the greedy (.*) capture already reaches the end. Worth a one-line confirmation.

  • nit: test:*(!) parses to a single empty-string exclusion, so it would drop names whose * captured the empty string. Harmless but slightly surprising.

- Collapse the nested `if let Some(m) = caps.get(i) { if exclusions… }`
  into a let-chain to fix the clippy `collapsible_if` lint that was
  failing CI.
- Drop the trailing `$` anchor from the generated regex. The exclusion
  feature only needs the capture group, and the original behavior
  matched names that started with the pattern (so `*:test` still
  matches `a:test:b`). Keeping the `$` would have been an unrelated
  tightening of wildcard matching.

@lunadogbot lunadogbot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Commit 613c5ca addresses both items from my prior REQUEST_CHANGES:

  • Clippy collapsible_if — fixed via the let-chain if let Some(m) = caps.get(i) && exclusions.iter().any(|e| e == m.as_str()). lint debug linux-x86_64 is now passing.
  • Trailing $ anchor — dropped. Regex is back to ^{pat} only, so the prefix-match semantics of *:test matching a:test:b are preserved and the exclusion change isn't piggybacking on an unrelated tightening of wildcard matching.

Feature scope is tight: trailing (!a|b|c) parsed off the end, captures from * checked against the exclusion list, exclusion-without-wildcard rejected with a clear error message. Tests cover the canonical case, single-token exclusion (*(!build)), sub-prefix matching (test:e2e:smoke vs test:e2e), and the no-wildcard error path.

The leftover nit (test:*(!) parses to a single empty-string exclusion) is unaddressed but was tagged as harmless and isn't worth blocking on.

CI is fully green.

LGTM.

@bartlomieju
bartlomieju merged commit 3c12aa9 into main May 30, 2026
136 checks passed
@bartlomieju
bartlomieju deleted the feat/task-wildcard-exclusion branch May 30, 2026 07:08
littledivy pushed a commit to crowlKats/deno that referenced this pull request Jun 10, 2026
…#34506)

Adds a `(!a|b|c)` exclusion syntax to `deno task` wildcard patterns,
modeled after [concurrently][cc]. For example,
`deno task "test:*(!e2e|interactive)"` now matches `test:unit` and
`test:integration` but skips `test:e2e` and `test:interactive`.

The exclusion group is parsed off the end of the argument, and each
listed value is matched against what a `*` in the pattern captured. A
pattern without a `*` but with a `(!...)` group is rejected, since
there is nothing to exclude from.

This is the common case from denoland#29355; more elaborate forms (multiple
exclusion groups, sub-wildcards inside the group) are intentionally
not handled.

Closes denoland#29355.
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.

Allow for exclusions in task wildcards

3 participants