Skip to content

Add exclude tags/tag query option#361

Open
awgymer wants to merge 10 commits into
askimed:mainfrom
awgymer:add-exclude-tags
Open

Add exclude tags/tag query option#361
awgymer wants to merge 10 commits into
askimed:mainfrom
awgymer:add-exclude-tags

Conversation

@awgymer

@awgymer awgymer commented Apr 10, 2026

Copy link
Copy Markdown

Started as --exclude-tag but I noticed that there had been some outstanding work on adding complex tag queries.

I implemented a simple query grammar:

  • !: NOT
  • &&: AND
  • ||: OR

For now I left the --tag and --exclude-tag as mutually exclusive to the new query system but in theory they could be easily dropped and the new --tag-query option used to cover those cases (grammar would just need to be extended to see , as || I think).

Would supersede #283
Closes #180
Closes #255
Closes #260

awgymer added 8 commits April 10, 2026 13:12
Adds an excludeTags field and two-list constructor. The isExcluded()
helper mirrors the existing recursive include logic so exclusion is
case-insensitive, name-based, and hierarchical. Exclusion is evaluated
before inclusion so --exclude-tag always wins.

Removes the varargs constructor which conflated include tags into a
single ambiguous list and would become misleading as TagQuery gains
more structure (upcoming AND-logic feature).
Mirrors the existing --tag option: comma-separated, multi-value,
case-insensitive. Wires the new excludeTags list into TagQuery
construction so the resolver applies exclusion automatically.
Migrates existing varargs TagQuery call sites to List.of() following
removal of the varargs constructor. Adds four new tests covering
exclude by tag, exclude by suite-level tag, exclude by name, and
combined include+exclude.
Introduces TagExpression interface and four implementations:
- TagNode: leaf node that matches a tag against a test's name, tags,
  and parent tags (case-insensitive, hierarchical)
- NotNode: negates its operand
- AndNode: short-circuiting logical AND of two expressions
- OrNode: short-circuiting logical OR of two expressions

Lays the groundwork for boolean tag query expressions such as
(!foo && bar) || baz.
Recursive descent parser for boolean tag query expressions supporting:
- Unquoted tags (letters, digits, underscores, hyphens)
- Quoted tags with single or double quotes (allows whitespace)
- Logical operators: ! (NOT), && (AND), || (OR), with () grouping
- Operator precedence: ! > && > ||

Parse errors are reported as TagQueryParseException which includes the
original query and a caret pointer to the exact position of the problem:

  Invalid tag query: foo & bar
                         ^
  Expected '&&' but got '&' — did you mean '&&'?
Tests cover:
- AST shape: operator precedence, parenthesis grouping, nesting
- Evaluation: single tag, name matching, case-insensitivity, NOT/AND/OR,
  complex expressions, parent tag inheritance, quoted multi-word tags
  (both single and double quotes)
- Errors: empty input, unmatched parens, unterminated strings, single
  & and |, trailing operators — all verified to throw
  TagQueryParseException at the correct position
Allows TagQuery to delegate matching to a TagExpression AST rather
than the internal include/exclude tag lists. When an expression is
set, matches() evaluates it directly, bypassing the list-based logic.
Accepts a boolean tag expression string (e.g. '(!tag1 && tag2) || tag3')
parsed by TagQueryParser. Cannot be combined with --tag or --exclude-tag.
Parse errors are reported with a caret pointer to the problem position
and return exit code 1 without a stack trace.
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.

In-verse tag selection [feature] Add an option that tests must match all tags supplied [feature] exclude tests by tag

1 participant