Skip to content

feat: support [...] lookups in @{...} interpolation - #4488

Merged
matthew-dean merged 9 commits into
less:masterfrom
matthew-dean:feat/lookup-in-interpolation
Jul 30, 2026
Merged

feat: support [...] lookups in @{...} interpolation#4488
matthew-dean merged 9 commits into
less:masterfrom
matthew-dean:feat/lookup-in-interpolation

Conversation

@matthew-dean

@matthew-dean matthew-dean commented Jul 30, 2026

Copy link
Copy Markdown
Member

What:

Backports the Less 5.x behaviour of allowing [...] lookups inside @{...} interpolation, plus fixes found alongside it.

Lookups in interpolation

  • @{map[key]} now resolves anywhere @{name} does — selectors, property names, at-rule preludes/names, quoted strings, url(), @import paths. Chained @{map[a][b]}, variable @{map[@k]}, indirect @{map[@@k]} and empty @{map[]} keys all work.
  • Inconsistent before: bare @map[key] already worked in value positions, and 5.x accepts the interpolated form everywhere.
  • Also clears a dead end in the 4.8.x deprecation — @media @q[tablet] warned "use @{...}", but @media @{q[tablet]} was a SyntaxError.

Defects fixed

  • Bare lookups escaped the variable-in-at-rule-prelude deprecation: @map[key] parses to NamespaceValue, but both warning sites tested only for Variable. Value positions correctly stay undeprecated.
  • @supports (width: @map[key]) rendered as (width: [key]), dropping the variable. That condition is unparseable, so the block was dead in every browser; it now resolves.
  • Unquoted url(@{path}/x.png) never interpolated — its body was raw text in an Anonymous node.
  • Quoted.eval couldn't round-trip an unquoted body: it re-derived the quote from quote + value + quote, so an empty quote picked up the value's first character and read as quoted, suppressing rootpath escaping in URL.eval.

The first two produced wrong CSS with no error and no warning.

Test suite

Fixtures were being globbed and then silently discarded, because the glob branch admitted a .less file only when a sibling .css existed. Sets with their own verifyFunction keep expectations elsewhere, so they never ran. Test count 136 → 279.

  • Error fixtures (tests-error/parse, tests-error/eval, js-type-errors, no-js-errors) declare a .txt — 96 fixtures, none running.
  • Sourcemap fixtures declare a .json under test/basic, custom-props, disable-annotation, variable-selector and its vars, none running. Two of the verifiers also resolved the wrong path: fixture names gained a tests-config/ prefix when the suite moved to packages/test-data, while the expectations stayed put.
  • Verified both ways — breaking an expectation used to leave the run green; it now fails with exit 1.

Re-enabling surfaced only stale metadata, never a behaviour change:

  • property-undefined / recursive-property: a trailing blank line err.toString() does not produce. Stock 4.8.1 emits identical text.
  • basic / custom-props / variable-selector: configs had drifted to sourceMap: true, which skips the harness's testweb/ defaults (they only apply to an object), and expectations carried pre-monorepo paths. Configs restored to {}, expectations regenerated — mappings unchanged in every case.
  • Dropped the pre-Node-16.9 type-error fixture (unreachable at engines.node >=18) and the now-unused semver dep.

New coverage: tests-unit/lookup-interpolation, bare-lookup cases in at-rule-variable-deprecated, tests-config/rootpath-escape-interpolation (rootpath with (, ) and a space, so quoted and unquoted cannot look alike), tests-config/sourcemaps/url-interpolation, and a ${name[key]} parse fixture.

CI

  • Oldest job tracked lts/-3, which drifts upward as new LTS lines ship. Pinned to the declared floor, 18.

Implementation

  • The parser gains no lookup regex — variableCurly delegates the chain to parsers.mixin.ruleLookups(), the same production the bare form uses, so both spellings share one grammar.
  • The two eval-time string paths (Quoted, inline JS) now share a single pattern definition instead of three hand-maintained copies — that drift is what caused the silent pass-through.
  • ${...} stays lookup-free: properties have no lookup grammar, and prop: { … } is a parse error in every scope.

Compatibility

  • A/B against 4.8.1: 12 preservation cases byte-identical (recursive interpolation, data-URIs, plain @{name}, ${prop}, bare lookups in value positions). Only intended cases changed.
  • The @supports fix switches on feature-query blocks that never executed in any browser — may warrant a minor rather than a patch release.

Checklist:

  • Documentation — N/A here; docs PR to follow in less-docs
  • Added/updated unit tests
  • Code complete

Backports the Less 5.x behaviour of allowing a lookup chain inside variable
interpolation, so `@{map[key]}` resolves wherever `@{name}` already does:
selectors, property names, at-rule preludes and names, quoted strings, `url()`
and `@import` paths. Chained (`@{map[a][b]}`), variable (`@{map[@k]}`),
indirect (`@{map[@@k]}`) and empty (`@{map[]}`) keys are all supported.

Previously these either failed to parse or — inside strings and `url()` —
were emitted verbatim into the output with no error and no warning.

The parser does not gain a lookup regex: `entities.variableCurly` now matches
`@{` plus the name and delegates the chain to `parsers.mixin.ruleLookups()`,
the same production the bare `@map[key]` form uses. Only the two eval-time
string paths (`Quoted`, inline JS) need a pattern, and they share one
definition rather than three hand-maintained copies — that duplication is what
let `quoted.js` drift from `lookupValue` and cause the silent pass-through.

`${...}` is deliberately left narrow. Properties have no lookup grammar
(`entities.property` parses `$name` with no chaining) and nothing can hold a
ruleset to look into, since `prop: { ... }` is a parse error in every scope.

Also fixes three related defects:

* A bare lookup in a structural position was exempt from the
  `variable-in-at-rule-prelude` deprecation. `entities.variable()` parses
  `@map[key]` into a `NamespaceValue`, and both warning sites tested only for
  `Variable`, so `@keyframes @Map[key]`, `@supports @Map[key]` and
  `@layer @Map[key]` warned for a plain `@var` but stayed silent for a lookup.
  A lookup in a value position — `@supports (width: @Map[key])` — correctly
  remains undeprecated.

* `@supports (width: @Map[key])` rendered as `(width: [key])`, dropping the
  variable. Unknown at-rule preludes are scanned as text, so a bare lookup
  reached the permissive regex, which matched only `@map` and left `[key]`
  behind. The resulting condition never parsed, so the block was dead in every
  browser; it now resolves.

* Interpolation inside an unquoted `url(@{path}/x.png)` was never substituted.
  The body was raw text in an `Anonymous` node; text containing interpolation
  is now handed to an escaped `Quoted`, matching the quoted spelling.

The `variable-in-unknown-value` notice is now tested against the text with
interpolations stripped, so a variable key inside `@{map[@key]}` is no longer
misreported as a bare use of the syntax the notice recommends adopting.
@dosubot dosubot Bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Jul 30, 2026
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Less now shares lookup grammar and interpolation resolvers across parser and evaluation paths. Lookup chains work in variable interpolation, URLs, property names, quoted values, JavaScript expressions, at-rules, selectors, custom properties, and associated warning and error fixtures.

Changes

Lookup interpolation

Layer / File(s) Summary
Shared lookup grammar and resolvers
packages/less/lib/less/parser/lookup-pattern.js, packages/less/lib/less/tree/interpolated-variable.js
Adds shared lookup patterns, lookup splitting, and AST resolvers for variable and property interpolation.
Parser lookup integration
packages/less/lib/less/parser/parser.js
Parses chained interpolated lookups, resolves property particles and URLs, and updates structural bare-variable warning detection.
Evaluation consumers
packages/less/lib/less/tree/quoted.js, packages/less/lib/less/tree/js-eval-node.js
Routes quoted strings and inline JavaScript through the shared interpolation patterns and resolvers.
Warning, interpolation, and error fixtures
packages/less/test/less-test.js, packages/test-data/tests-unit/*, packages/test-data/tests-config/*, packages/test-data/tests-error/*, packages/less/test/sourcemaps/*
Adds coverage for structural warnings, chained lookups, URLs, selectors, at-rules, custom properties, recursive interpolation, URL escaping, sourcemaps, and expected parse/evaluation errors.

CI runtime and fixture loading

Layer / File(s) Summary
Pinned Node test runtime
.github/workflows/ci.yml, packages/less/package.json, packages/less/test/less-test.js
Pins the compatibility job to Node 18, removes semver, and uses fixed .txt error expectations while recognizing CSS or text fixtures.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant LessParser
  participant InterpolatedVariable
  participant AST
  participant QuotedOrJsEval
  LessParser->>InterpolatedVariable: parse lookup interpolation
  InterpolatedVariable->>AST: create Variable or NamespaceValue
  QuotedOrJsEval->>InterpolatedVariable: resolve interpolation
  InterpolatedVariable->>AST: evaluate resolved node
  AST-->>QuotedOrJsEval: return substituted value
Loading

Possibly related PRs

  • less/less.js#4462: Adjusts parser detection and deprecation warnings for bare variable references.
  • less/less.js#4469: Overlaps in parser and warning handling for bare variable-like at-rule preludes.
  • less/less.js#4475: Modifies parser detection and warning behavior for interpolated and bare variable references.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding bracket lookup support inside @{...} interpolation.
✨ 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.

Comment thread packages/less/lib/less/parser/parser.js
Comment thread packages/less/lib/less/parser/parser.js Outdated
@greptile-apps

greptile-apps Bot commented Jul 30, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds lookup chains to @{...} interpolation and repairs related parsing, URL interpolation, warning, and test-harness behavior.

  • Shares lookup parsing and interpolation resolution across parser, quoted-string, and inline-JavaScript paths.
  • Preserves unquoted URL semantics while resolving interpolation and applying rootpath escaping.
  • Keeps ${...} property interpolation lookup-free and adds parse-error coverage.
  • Restores error and sourcemap fixture execution, updates expectations, removes an obsolete dependency, and pins CI to Node 18.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Reviews (9): Last reviewed commit: "fix(test): resolve sourcemap expectation..." | Re-trigger Greptile

@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.

Actionable comments posted: 1

🤖 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.

Inline comments:
In `@packages/less/lib/less/parser/parser.js`:
- Line 26: Update RULE_PROPERTY_PARTICLE so lookup chains are permitted only for
@{...} interpolations, while ${...} accepts only the lookup-free variable form.
Preserve the existing plain word/property matching and ensure
resolveInterpolatedProperty() cannot receive map[key] through the ${...} path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 57057712-f69a-4a8f-b778-56a04c1e18ec

📥 Commits

Reviewing files that changed from the base of the PR and between 89c33e0 and 9e5ed11.

📒 Files selected for processing (10)
  • packages/less/lib/less/parser/lookup-pattern.js
  • packages/less/lib/less/parser/parser.js
  • packages/less/lib/less/tree/interpolated-variable.js
  • packages/less/lib/less/tree/js-eval-node.js
  • packages/less/lib/less/tree/quoted.js
  • packages/less/test/less-test.js
  • packages/test-data/tests-unit/at-rule-variable-deprecated/at-rule-variable-deprecated.css
  • packages/test-data/tests-unit/at-rule-variable-deprecated/at-rule-variable-deprecated.less
  • packages/test-data/tests-unit/lookup-interpolation/lookup-interpolation.css
  • packages/test-data/tests-unit/lookup-interpolation/lookup-interpolation.less

Comment thread packages/less/lib/less/parser/parser.js Outdated
Addresses two review findings.

`URL.eval` escapes a rewritten rootpath only when the value is unquoted, so
wrapping an interpolated unquoted `url()` body in a `Quoted` carrying a real
quote character suppressed that escaping. With a rootpath containing `(`, `)`
or whitespace it emitted `url(a(b)/x.png)` — malformed — where the
non-interpolated spelling correctly produced `url(a\(b\)/x.png)`.

The body is now built with an empty quote string, which reads as unquoted while
still resolving interpolation. That alone was not enough: `Quoted.eval`
rebuilt its result as `this.quote + value + this.quote` and re-derived the
quote from that string, so an empty quote picked up the first character of the
substituted value and read as quoted again. The quote is now carried across
explicitly — a no-op for real quote characters, where the two already agree.

`RULE_PROPERTY_PARTICLE` shared `[@$]` across both sigils, so `${name[key]}`
was accepted as a property-name particle and reached
`resolveInterpolatedProperty` with `name[key]`, producing a misleading
undefined-property error for syntax the language does not define. The two
sigils are now spelled out separately: lookup chains for `@{...}` only.

Adds `tests-config/rootpath-escape-interpolation`, whose rootpath deliberately
contains `(`, `)` and a space so quoted and unquoted forms cannot look alike,
covering plain, interpolated and lookup bodies in both spellings.
The glob branch of `runTestSetInternal` admitted a `.less` file only when a
sibling `.css` existed. Error fixtures declare their expectation as a `.txt`
and never produce CSS, so every one of them was silently skipped — the file was
globbed, failed the existence check, and was dropped without being counted.

That covered 96 fixtures: 27 in tests-error/parse, 67 in tests-error/eval, and
the js-type-errors and no-js-errors sets. The suite reported 136 passing tests
and stayed green even when an expectation was edited to something impossible,
so parse and eval error regressions went uncaught entirely.

The check now accepts either expectation form. Verified both directions:
breaking an expectation now fails the run (exit 1 with a named FAIL), where
before it passed silently.

Re-enabling the suite surfaced two stale expectations, both pre-existing drift
rather than behaviour changes — `property-undefined` and `recursive-property`
carried a trailing blank line that `err.toString()` does not produce. Stock
4.8.1 emits the same text as this branch for both, confirming the fixtures had
drifted while dormant. Their trailing newline is corrected here.

Also adds the parse fixture for `${name[key]}`, which is now enforceable.

Test count goes from 136 to 233.

@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.

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (2)
packages/less/lib/less/parser/parser.js (2)

131-145: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Restrict NamespaceValue to variable lookups.

mixin.call(true) also returns NamespaceValue for mixin lookups (Lines 1144-1169), so this helper incorrectly emits a “bare @variable” warning for structural mixin lookups. Check the wrapped node is a VariableCall before classifying it as a variable reference.

Proposed fix
-        return e.type === 'Variable' || e.type === 'VariableCall' || e.type === 'NamespaceValue';
+        return e.type === 'Variable' ||
+            e.type === 'VariableCall' ||
+            (e.type === 'NamespaceValue' && e.value && e.value.type === 'VariableCall');
🤖 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` around lines 131 - 145, Update
isBareVariableReference to classify NamespaceValue only when its wrapped node is
a VariableCall, while continuing to accept direct Variable and VariableCall
nodes. Do not treat NamespaceValue results from mixin.call(true) as bare
variable references.

851-852: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Mark lookup interpolation as a dynamic selector.

variableCurly() now returns NamespaceValue for @{map[key]}, but element() still sets isVariable only for e instanceof tree.Variable (Line 1554). The evaluator uses that flag to reparse evaluated selectors; lookup values containing combinators or compound selectors can therefore remain a single element instead of following normal interpolation behavior. (fossies.org)

Proposed fix
-                if (e) { return new(tree.Element)(c, e, e instanceof tree.Variable, index + currentIndex, fileInfo); }
+                if (e) {
+                    const isVariable = e instanceof tree.Variable || e instanceof tree.NamespaceValue;
+                    return new(tree.Element)(c, e, isVariable, index + currentIndex, fileInfo);
+                }
🤖 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` around lines 851 - 852, Update the
selector element handling in element() so NamespaceValue results produced by
variableCurly(), including @&`#123`;map[key]&`#125`;, set isVariable like
tree.Variable results. Preserve the existing dynamic-selector reparse path so
evaluated lookup values containing combinators or compound selectors are
interpreted normally.
🤖 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.

Inline comments:
In `@packages/less/lib/less/parser/parser.js`:
- Around line 768-769: Update the synthetic tree.Quoted construction in the
interpolated string branch to store the parser’s currentIndex-adjusted position,
matching entities.quoted() and preserving correct diagnostics and source
locations when parseNode() uses a non-zero offset.

---

Outside diff comments:
In `@packages/less/lib/less/parser/parser.js`:
- Around line 131-145: Update isBareVariableReference to classify NamespaceValue
only when its wrapped node is a VariableCall, while continuing to accept direct
Variable and VariableCall nodes. Do not treat NamespaceValue results from
mixin.call(true) as bare variable references.
- Around line 851-852: Update the selector element handling in element() so
NamespaceValue results produced by variableCurly(), including
@&`#123`;map[key]&`#125`;, set isVariable like tree.Variable results. Preserve the
existing dynamic-selector reparse path so evaluated lookup values containing
combinators or compound selectors are interpreted normally.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b5e11f4e-e62a-4005-b905-9525de46b7b9

📥 Commits

Reviewing files that changed from the base of the PR and between 9e5ed11 and c42ea87.

📒 Files selected for processing (5)
  • packages/less/lib/less/parser/parser.js
  • packages/less/lib/less/tree/quoted.js
  • packages/test-data/tests-config/rootpath-escape-interpolation/rootpath-escape-interpolation.css
  • packages/test-data/tests-config/rootpath-escape-interpolation/rootpath-escape-interpolation.less
  • packages/test-data/tests-config/rootpath-escape-interpolation/styles.config.cjs

Comment thread packages/less/lib/less/parser/parser.js Outdated
`packages/less` declares `engines.node: >=18`, but the oldest CI job tracked
`lts/-3`. A relative selector drifts upward as new LTS lines ship, so the
declared minimum stops being exercised the moment another line reaches LTS —
silently, since nothing ties the matrix to the engines field.

Pins that job to `18` so the supported floor is actually tested.
`testTypeErrors` selected between two expectations with
`semver.gte(process.version, 'v16.9.0')`. The two differ only in V8's wording
change at 16.9 — `Cannot read property 'x' of undefined` became
`Cannot read properties of undefined (reading 'x')` — so with
`engines.node: >=18` the older `.txt` was unreachable on every supported
runtime.

The `-2` variant becomes the only expectation and the branch goes away. That
leaves `semver` unused, so it is dropped from devDependencies too.

Verified the fixture is genuinely enforced after the rename: editing its
expectation fails the run, where the whole set was skipped entirely before the
preceding commit re-enabled it.
…appings

`entities.quoted()` and the `URL` node both build with `index + currentIndex`,
but the synthetic `Quoted` for an interpolated unquoted body stored only the
local `index`. Aligned for consistency.

This is currently unobservable: `currentIndex` is non-zero only inside
`parseNode`, and none of its three callers can reach `entities.url()` — two
parse `['selector']`/`['selectors']`, and the third re-parses declaration
values that were stored as `Anonymous`, which `anonymousValue` cannot produce
for text containing `(`. The offset is correct regardless, and stops the node
from being the odd one out if another caller appears.

Adds a sourcemap fixture for url() values, which is the part that could have
regressed: interpolated unquoted bodies now build a `Quoted` where they used to
build an `Anonymous`, and the two differ in `genCSS` — `Anonymous` passes
fileInfo and index to `output.add`, an escaped `Quoted` does not. Measured
before adding it, the mapping structure is unchanged (an escaped `Quoted`
contributes no segment of its own, and the enclosing declaration already
carries the position). The fixture pins that, covering literal, interpolated,
lookup and quoted-interpolated bodies side by side; the harness validates all
four mappings against source.
`sourcemaps/basic` and `sourcemaps/custom-props` were skipped for the same
reason the error fixtures were: the glob branch required a sibling `.css`, and
these keep their expectation in `test/sourcemaps/*.json` via the set's
`getFilename`. Neither had run since that gate was introduced.

Generalises the previous fix rather than extending it — a fixture opts in by
declaring an expectation in any of the three supported ways: a sibling `.css`
for the default compile-and-diff, a sibling `.txt` for the error sets, or a
`getFilename` that resolves one elsewhere.

Both fixtures then failed on stale metadata, with mappings byte-identical:

- Their config had drifted to `sourceMap: true`. The harness only fills in
  `sourceMapRootpath`/`sourceMapOutputFilename` when `sourceMap` is an object,
  so the `testweb/` prefix the expectations were written against disappeared.
  Restored to `{}`, which also matches the sibling sourcemaps-* sets.
- The expectations still carried pre-monorepo paths, missing the `tests-config/`
  segment added when fixtures moved to `packages/test-data`. Regenerated; only
  `sources` and `file` change, the mappings are unchanged.

`basic` validates 52 mappings and `custom-props` 1.

Two fixtures remain skipped and are left alone: sourcemaps-disable-annotation
and sourcemaps-variable-selector read `test/<name>.json`, a path layout that no
longer exists, and both are named `basic.less`, so they would collide under the
`test/sourcemaps/` convention. Fixing them needs a rename.

@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.

Caution

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

⚠️ Outside diff range comments (1)
packages/less/lib/less/parser/parser.js (1)

142-145: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not classify every NamespaceValue as a variable reference.

mixin.call() also returns NamespaceValue (around Lines 1168-1169), so structural mixin lookups will now incorrectly emit the bare-variable deprecation warning. Restrict this check to NamespaceValue instances whose wrapped value is a VariableCall.

Proposed fix
 function isBareVariableReference(e) {
     if (!e) { return false; }
-    return e.type === 'Variable' || e.type === 'VariableCall' || e.type === 'NamespaceValue';
+    return e.type === 'Variable' ||
+        e.type === 'VariableCall' ||
+        (e.type === 'NamespaceValue' && e.value?.type === 'VariableCall');
 }

Also applies to: 1912-1914, 2347-2349

🤖 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` around lines 142 - 145, Update
isBareVariableReference to classify NamespaceValue only when its wrapped value
is a VariableCall, while retaining direct Variable and VariableCall matches.
Apply the same narrowed condition wherever this helper’s equivalent logic
appears in the additional affected locations, without treating structural
mixin.call() NamespaceValue results as bare variables.
🤖 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.

Outside diff comments:
In `@packages/less/lib/less/parser/parser.js`:
- Around line 142-145: Update isBareVariableReference to classify NamespaceValue
only when its wrapped value is a VariableCall, while retaining direct Variable
and VariableCall matches. Apply the same narrowed condition wherever this
helper’s equivalent logic appears in the additional affected locations, without
treating structural mixin.call() NamespaceValue results as bare variables.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0944f1b1-c9a3-4207-bcbf-8e14e2ac697e

📥 Commits

Reviewing files that changed from the base of the PR and between 53457bf and 56e2b37.

📒 Files selected for processing (5)
  • packages/less/lib/less/parser/parser.js
  • packages/less/test/sourcemaps/url-interpolation.json
  • packages/test-data/tests-config/sourcemaps/url-interpolation/styles.config.cjs
  • packages/test-data/tests-config/sourcemaps/url-interpolation/url-interpolation.css
  • packages/test-data/tests-config/sourcemaps/url-interpolation/url-interpolation.less

Correcting the previous commit's note: the expectations for
sourcemaps-disable-annotation and sourcemaps-variable-selector were not missing,
and no rename was needed. They sit in their own directories under
`packages/less/test/`, so there was never a collision — I had looked in
`test/*.json` and `test/tests-config/` but not `test/sourcemaps-*/`.

The real fault was path resolution. Both verifiers read
`path.join('test/', name)`, but fixture names gained a leading `tests-config/`
when the suite moved to packages/test-data, while the expectations stayed put.
Resolution now strips that segment via a shared helper.

With the paths fixed, the gate no longer needs to special-case them: a set with
its own verifyFunction is trusted to locate and report its own expectation, so
the `.css` requirement applies only to the default compile-and-diff.

Both then failed on the same stale metadata as basic/custom-props, mappings
byte-identical: `sourcemaps-variable-selector` had drifted to `sourceMap: true`,
which skips the harness's `testweb/` defaults, and both expectations carried
pre-monorepo paths. Config restored to `{}` and expectations regenerated —
`sources` and `file` change, mappings do not.

This also picks up the sourcemaps-variable-selector `vars` fixture.
`name` is assembled from `path.relative`, so its separators are platform
native. Stripping the `tests-config/` prefix with a forward-slash-only pattern
left it in place on Windows, and the two fixtures looked for an expectation
under a path that has never existed — reported as an empty expected value
rather than a missing file.

Separators are normalised before the prefix is stripped. Verified against both
spellings, including the mixed form `path.relative` actually produces there
(`tests-config\set/basic`).
@matthew-dean
matthew-dean merged commit c303718 into less:master Jul 30, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant