Skip to content

Fix #4460: parse comparison/range syntax in container style() queries#4461

Open
dweep-js wants to merge 2 commits into
less:masterfrom
dweep-js:fix-container-style-range-syntax
Open

Fix #4460: parse comparison/range syntax in container style() queries#4461
dweep-js wants to merge 2 commits into
less:masterfrom
dweep-js:fix-container-style-range-syntax

Conversation

@dweep-js

@dweep-js dweep-js commented Jul 4, 2026

Copy link
Copy Markdown

Fixes #4460

What:
mediaFeature's lookahead regex for range/comparison syntax inside
@container/@media parentheses only matched a bare lowercase
identifier before the operator (=, >, <, >=, <=). It failed
whenever the left-hand operand was a function call, such as
var(--n) or calc(6/2), because parentheses aren't in the
character class the regex used for its lookahead. This caused a
"Missing closing ')'" parse error for CSS container style queries
using range syntax with custom-property/calc operands, e.g.:

@container style(var(--n) = 3) { ... }
@container style(calc(6 / 2) = var(--n)) { ... }
@container style(var(--size) > 1lh) { ... }

Why:
This is valid CSS per the container queries spec, and the existing
comparison-parsing machinery (condition() / atomicCondition() /
operand()) already supports function-call operands via
entities.call() — the only broken piece was the lookahead regex
deciding whether to even attempt the comparison branch. Widening it
to tolerate a single level of balanced parens ((?:[^()]|\([^()]*\))*)
before the operator fixes all three cases in the issue without
touching any other parsing path.

Checklist:

  • Added/updated unit tests
  • Code complete
  • Documentation (N/A — internal parser fix, no public API/docs change)

Summary by CodeRabbit

  • Bug Fixes

    • Improved recognition of comparison operators within parentheses for media feature parsing, improving support for more complex query syntax.
  • Tests

    • Added regression coverage for @container style(...) queries using equality and greater-than comparisons, including cases with calc(...) expressions and custom properties.

…ries

The mediaFeature lookahead regex only matched a bare identifier
before a comparison operator (=, >, <, >=, <=), so it failed
whenever the operand was a function call, e.g. var(--n) or
calc(6/2). Widened the regex to also match a single level of
balanced parens before the operator.

Added regression tests covering:
  @container style(var(--n) = 3)
  @container style(calc(6 / 2) = var(--n))
  @container style(var(--size) > 1lh)
@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Jul 4, 2026
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The parser now recognizes more @container style(...) range-syntax comparisons inside parentheses, and the test fixtures add coverage for var(), calc(), and unit-based comparison cases.

Changes

Container Style Query Parsing

Layer / File(s) Summary
Broaden queryInParens comparison regex
packages/less/lib/less/parser/parser.js
The comparison-detection regex in mediaFeature(...) is broadened to match more general parenthesized expressions before the operator.
Add container style query fixtures
packages/test-data/tests-unit/container/container.less, packages/test-data/tests-unit/container/container.css
New @container style(...) blocks add equality and greater-than cases for var(...), calc(...), and lh-based comparisons, with matching expected output.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • less/less.js#4409: Touches the same query-in-parens parsing path and related evaluation logic.
  • less/less.js#4427: Also changes parenthesis handling in mediaFeature(...) parsing.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main fix for container style() comparison/range parsing and references the linked issue.
Linked Issues check ✅ Passed The regex change and new regression tests address #4460 by allowing valid function-call operands in container style query comparisons.
Out of Scope Changes check ✅ Passed The only added files are regression tests that directly support the parser fix, with no unrelated changes evident.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/less/lib/less/parser/parser.js (1)

1903-1903: 🎯 Functional Correctness | 🔵 Trivial

Lookahead only supports one level of paren nesting.

The new pattern (?:[^()]|\([^()]*\))*\s*([<>]=|<=|>=|[<>]|=) correctly handles single-level function calls like var(--n) and calc(6 / 2), matching the PR's test cases. However, \([^()]*\) cannot match a nested group (e.g. calc((1 + 1) / 2) = var(--n)), so doubly-nested operands would fail this lookahead and fall through to the else branch — likely reproducing the original "Missing closing ')'" error (issue #4460) for that narrower case.

Since function-call operands with internal parens (nested calc(), min()/max() inside calc(), etc.) are plausible in real container queries, consider whether the lookahead should support arbitrary nesting depth (e.g., a small recursive/manual balanced-paren scan) rather than exactly one level.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/less/lib/less/parser/parser.js` at line 1903, The lookahead in
parserInput.$re only handles one level of parentheses, so nested container-query
operands can still miss the comparison operator and fall through incorrectly.
Update the queryInParens handling in parser.js to support balanced/nested paren
scanning instead of the current single-level regex, keeping the logic in the
same parser branch that checks syntaxOptions.queryInParens before deciding
between the query path and the fallback.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/less/lib/less/parser/parser.js`:
- Line 1903: The lookahead in parserInput.$re only handles one level of
parentheses, so nested container-query operands can still miss the comparison
operator and fall through incorrectly. Update the queryInParens handling in
parser.js to support balanced/nested paren scanning instead of the current
single-level regex, keeping the logic in the same parser branch that checks
syntaxOptions.queryInParens before deciding between the query path and the
fallback.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bb970b68-1f4e-4d26-9333-2c74b64688cc

📥 Commits

Reviewing files that changed from the base of the PR and between 8ae2cc3 and 2fd99ed.

📒 Files selected for processing (3)
  • packages/less/lib/less/parser/parser.js
  • packages/test-data/tests-unit/container/container.css
  • packages/test-data/tests-unit/container/container.less

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/less/lib/less/parser/parser.js (1)

1903-1903: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Lookahead only handles one level of nested parens — deeper nesting still fails.

The broadened regex (?:[^()]|\([^()]*\))*\s*([<>]=|<=|>=|[<>]|=) correctly handles var(--n) = 3 and calc(6 / 2) = var(--n), but a doubly-nested operand like calc(var(--x) + 1) or min(var(--a), var(--b)) still can't be matched, since \([^()]*\) disallows inner parens. Such inputs would fall through to the non-comparison branch and likely reproduce the same "Missing closing ')'" failure this PR fixes, just one level deeper.

Since the PR explicitly scopes the fix to "a single level of balanced parentheses," this may be an accepted limitation, but it's worth confirming whether nested calc()/min() expressions are a realistic near-term need (they're common in container queries), given the fix could be extended to arbitrary nesting depth via a small counting-based scan instead of a fixed-depth regex.

♻️ Example of a depth-agnostic balanced-paren scan
-        if (!p && syntaxOptions.queryInParens && parserInput.$re(/^(?:[^()]|\([^()]*\))*\s*([<>]=|<=|>=|[<>]|=)/)) {
+        if (!p && syntaxOptions.queryInParens && hasComparisonOperatorAhead()) {

where hasComparisonOperatorAhead() scans forward tracking paren depth and only tests for the operator when depth is 0.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/less/lib/less/parser/parser.js` at line 1903, The lookahead in
parserInput.$re within parser.js only supports one level of balanced
parentheses, so deeper nested expressions like calc() or min() still miss the
comparison branch. Update the query-in-parens detection used in the parser logic
around syntaxOptions.queryInParens to handle arbitrary nested balanced
parentheses instead of the current fixed-depth regex, ideally by scanning ahead
with a paren-depth counter and checking for the comparison operator only at
depth 0. Keep the existing behavior for simple cases like var(--n) and calc(6 /
2) while ensuring nested operands are matched correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/less/lib/less/parser/parser.js`:
- Line 1903: The lookahead in parserInput.$re within parser.js only supports one
level of balanced parentheses, so deeper nested expressions like calc() or min()
still miss the comparison branch. Update the query-in-parens detection used in
the parser logic around syntaxOptions.queryInParens to handle arbitrary nested
balanced parentheses instead of the current fixed-depth regex, ideally by
scanning ahead with a paren-depth counter and checking for the comparison
operator only at depth 0. Keep the existing behavior for simple cases like
var(--n) and calc(6 / 2) while ensuring nested operands are matched correctly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d96e7a3b-941e-45a2-93be-32fa6a37566b

📥 Commits

Reviewing files that changed from the base of the PR and between 2fd99ed and e776b3f.

📒 Files selected for processing (1)
  • packages/less/lib/less/parser/parser.js

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

Labels

size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Container style queries with range syntax

1 participant