Skip to content

fix(issue:4331) and keyword treated as function#4405

Closed
matthew-dean wants to merge 1 commit intoless:masterfrom
matthew-dean:pr-4332-rebased
Closed

fix(issue:4331) and keyword treated as function#4405
matthew-dean wants to merge 1 commit intoless:masterfrom
matthew-dean:pr-4332-rebased

Conversation

@matthew-dean
Copy link
Copy Markdown
Member

@matthew-dean matthew-dean commented Mar 9, 2026

Rebased version of #4332 by @puckowski. Resolves conflicts with current master.

Original PR: #4332
Fixes: #4331

Credit: @puckowski

Summary by CodeRabbit

  • New Features

    • Added a new "size" function to the Less function library for expanded functionality
  • Bug Fixes

    • Improved parser validation to prevent keywords from being incorrectly interpreted as function calls
  • Tests

    • Added responsive media query test cases for smaller screen sizes

* Fixes issue where and keyword was being treated as a function call.
* Adds definition for size function call.
@dosubot dosubot bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Mar 9, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 9, 2026

📝 Walkthrough

Walkthrough

Introduces a new "size" LESS function with parser validation to prevent keyword entities from being misinterpreted as unregistered function calls, and adds test cases for media query handling with responsive breakpoints.

Changes

Cohort / File(s) Summary
Size Function Implementation
packages/less/src/less/functions/size.js, packages/less/src/less/functions/index.js
Implements a new LESS size function that accepts variable arguments, validates at least one argument exists, evaluates the first argument as a Variable, and returns an Anonymous node with the evaluated CSS. Registers the function in the function registry.
Parser Validation
packages/less/src/less/parser/parser.js
Adds lookahead validation in declarationCall to check if a detected keyword entity is registered as a function. Prevents unregistered keyword-like tokens from being parsed as function calls by restoring parser state and returning early.
Media Query Test Data
packages/test-data/tests-unit/media/media.less, packages/test-data/tests-unit/media/media.css
Adds responsive media query test cases targeting screen widths up to 1280px with nested width adjustments for .form-process-table and its direct children.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A size function hops into the frame,
With parser guards to stake its claim,
Media queries respond with grace,
Responsive rules find their place,
Less compiles with newfound might! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The addition of the size function appears tangential to the media query 'and' keyword parsing fix. While the parser changes directly address #4331, the size function implementation is unrelated to the reported media query issue. Consider whether the size function should be in a separate PR or clarify its necessity for fixing the 'and' keyword parsing issue in media queries.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly addresses the main issue: the 'and' keyword was being incorrectly treated as a function, which directly caused the media query parsing problem referenced in issue #4331.
Linked Issues check ✅ Passed The PR fixes the core parsing issue (#4331) where 'and' keyword in media queries was treated as a function, breaking nested media query parsing. Changes include parser guard logic and test cases demonstrating the fix.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/less/src/less/functions/size.js`:
- Around line 1-2: The import for Anonymous is wrong: currently both Variable
and Anonymous are imported from '../tree/variable', causing new Anonymous(...)
in size() to actually construct a Variable node; change the Anonymous import to
import Anonymous from '../tree/anonymous' (keeping Variable from
'../tree/variable') so size() returns an actual Anonymous AST node and
evaluation/serialization behave correctly.
- Around line 10-14: The current implementation in size() incorrectly forces
only args[0] through new Variable(...) and drops other arguments; instead
evaluate all passed nodes and serialize them, e.g. replace the single-variable
construction with evaluating every arg (use each arg's eval(this.context)), then
map those evaluated nodes to toCSS(this.context) and join them before returning
the Anonymous(`size(${...})`); reference the Variable usage, entityList/args
mapping, and the Anonymous return in size() when making the change.

In `@packages/less/src/less/parser/parser.js`:
- Around line 477-489: The fallback path in mediaFeature() that reparses a
keyword + paren (using entities.keyword(), parserInput.$re(/^[\w]+\(/) and the
functionRegistry.get check) still emits "and(" instead of "and ("; fix it by
normalizing spacing when you build the fallback node that combines the
keywordEntity and the following '(' — e.g. when creating the combined token or
text for the fallback, ensure you insert a space between the keyword text and
'(' (or otherwise set the node's raw/spacing metadata so output becomes "keyword
("), so the same spacing rule applied by the earlier guard is enforced in the
fallback path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 13b82a0d-3956-4db2-89c4-7a36e5ad7b30

📥 Commits

Reviewing files that changed from the base of the PR and between 774f188 and b2c968a.

📒 Files selected for processing (5)
  • packages/less/src/less/functions/index.js
  • packages/less/src/less/functions/size.js
  • packages/less/src/less/parser/parser.js
  • packages/test-data/tests-unit/media/media.css
  • packages/test-data/tests-unit/media/media.less

Comment on lines +1 to +2
import Variable from '../tree/variable';
import Anonymous from '../tree/variable';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix the Anonymous import.

Line 2 imports Anonymous from ../tree/variable, so new Anonymous(...) is constructing the wrong node type. That makes size() return a Variable AST node instead of an Anonymous node and will break evaluation/serialization when this path is hit.

Suggested fix
 import Variable from '../tree/variable';
-import Anonymous from '../tree/variable';
+import Anonymous from '../tree/anonymous';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import Variable from '../tree/variable';
import Anonymous from '../tree/variable';
import Variable from '../tree/variable';
import Anonymous from '../tree/anonymous';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/less/src/less/functions/size.js` around lines 1 - 2, The import for
Anonymous is wrong: currently both Variable and Anonymous are imported from
'../tree/variable', causing new Anonymous(...) in size() to actually construct a
Variable node; change the Anonymous import to import Anonymous from
'../tree/anonymous' (keeping Variable from '../tree/variable') so size() returns
an actual Anonymous AST node and evaluation/serialization behave correctly.

Comment on lines +10 to +14
const entityList = [new Variable(args[0].value, this.index, this.currentFileInfo).eval(this.context)];

args = entityList.map(a => { return a.toCSS(this.context); }).join(this.context.compress ? ',' : ', ');

return new Anonymous(`size(${args})`);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't re-wrap size() args as a single variable.

Line 10 forces only args[0] through new Variable(...), and Lines 12-14 serialize only that one value. This means non-variable inputs are evaluated as variable lookups, and any extra arguments are silently dropped. Either constrain size() to one variable argument explicitly, or serialize all args as normal evaluated nodes.

Suggested direction
-    const entityList = [new Variable(args[0].value, this.index, this.currentFileInfo).eval(this.context)];
-       
-    args = entityList.map(a => { return a.toCSS(this.context); }).join(this.context.compress ? ',' : ', ');
-    
-    return new Anonymous(`size(${args})`);
+    const serializedArgs = args
+        .map(arg => (arg && arg.eval ? arg.eval(this.context) : arg))
+        .map(arg => arg.toCSS(this.context))
+        .join(this.context.compress ? ',' : ', ');
+
+    return new Anonymous(`size(${serializedArgs})`);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/less/src/less/functions/size.js` around lines 10 - 14, The current
implementation in size() incorrectly forces only args[0] through new
Variable(...) and drops other arguments; instead evaluate all passed nodes and
serialize them, e.g. replace the single-variable construction with evaluating
every arg (use each arg's eval(this.context)), then map those evaluated nodes to
toCSS(this.context) and join them before returning the
Anonymous(`size(${...})`); reference the Variable usage, entityList/args
mapping, and the Anonymous return in size() when making the change.

Comment on lines 477 to +489
parserInput.save();


let keywordEntity = this.entities.keyword();
parserInput.restore();
parserInput.save();

validCall = parserInput.$re(/^[\w]+\(/);
if (!validCall) {
parserInput.forget();
return;
} else if (validCall && keywordEntity && !functionRegistry.get(validCall.substring(0, validCall.length - 1))) {
parserInput.restore();
return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

This still leaves the and( formatting regression unfixed.

Lines 487-489 stop and( from being treated as a function, but the fallback path in mediaFeature() reparses it as keyword + paren and still emits and( instead of and (. The failing media test is already showing that, so this guard needs a matching spacing normalization on the fallback path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/less/src/less/parser/parser.js` around lines 477 - 489, The fallback
path in mediaFeature() that reparses a keyword + paren (using
entities.keyword(), parserInput.$re(/^[\w]+\(/) and the functionRegistry.get
check) still emits "and(" instead of "and ("; fix it by normalizing spacing when
you build the fallback node that combines the keywordEntity and the following
'(' — e.g. when creating the combined token or text for the fallback, ensure you
insert a space between the keyword text and '(' (or otherwise set the node's
raw/spacing metadata so output becomes "keyword ("), so the same spacing rule
applied by the earlier guard is enforced in the fallback path.

@matthew-dean
Copy link
Copy Markdown
Member Author

Closing - replacing with a cleaner implementation. The current approach has side effects on space handling (3 test failures) and uses a wasteful save/restore/save pattern.

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.

If the child component uses the media query style, it will not be loaded. Why

2 participants