From 8ca86eb458cad2b3a74d0e4758009ec8158ec106 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 23:13:04 +0000 Subject: [PATCH 1/3] Initial plan From 571365fe22820595e49ffaead85e88f120cec04f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 23:32:59 +0000 Subject: [PATCH 2/3] Fix: not keyword now works without parentheses in guard conditions Previously, `boolean(not false)` silently ignored the `not` keyword while `boolean(not (false))` worked correctly. The `negatedCondition` parser function consumed the `not` keyword but only tried `parenthesisCondition`, which requires `(`. With no parens, it returned undefined with `not` already consumed, causing silent skip. Fix: fall back to `atomicCondition` when `parenthesisCondition` fails, allowing both `not false` and `not (false)` to work consistently. Co-authored-by: matthew-dean <414752+matthew-dean@users.noreply.github.com> --- packages/less/lib/less/parser/parser.js | 2 +- packages/test-data/tests-unit/functions/functions.css | 5 +++++ .../test-data/tests-unit/functions/functions.less | 7 +++++++ .../tests-unit/mixins-guards/mixins-guards.css | 6 ++++++ .../tests-unit/mixins-guards/mixins-guards.less | 11 +++++++++++ 5 files changed, 30 insertions(+), 1 deletion(-) diff --git a/packages/less/lib/less/parser/parser.js b/packages/less/lib/less/parser/parser.js index fb6b3603b..97145214b 100644 --- a/packages/less/lib/less/parser/parser.js +++ b/packages/less/lib/less/parser/parser.js @@ -2423,7 +2423,7 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) { }, negatedCondition: function (needsParens) { if (parserInput.$str('not')) { - const result = this.parenthesisCondition(needsParens); + const result = this.parenthesisCondition(needsParens) || this.atomicCondition(needsParens); if (result) { result.negate = !result.negate; } diff --git a/packages/test-data/tests-unit/functions/functions.css b/packages/test-data/tests-unit/functions/functions.css index 4876f5874..970f8d80e 100644 --- a/packages/test-data/tests-unit/functions/functions.css +++ b/packages/test-data/tests-unit/functions/functions.css @@ -224,6 +224,9 @@ html { a: true; b: false; c: false; + d: true; + e: false; + f: true; } #if { a: 1; @@ -236,6 +239,8 @@ html { i: 6; j: 8; k: 1; + m: 1; + n: 2; l: black; /* results in void */ color: green; diff --git a/packages/test-data/tests-unit/functions/functions.less b/packages/test-data/tests-unit/functions/functions.less index bc476b6e2..1fe8c56de 100644 --- a/packages/test-data/tests-unit/functions/functions.less +++ b/packages/test-data/tests-unit/functions/functions.less @@ -256,6 +256,10 @@ html { a: boolean(not(2 < 1)); b: boolean(not(2 > 1) and (true)); c: boolean(not(boolean(true))); + // not without parentheses (should behave the same as with parentheses) + d: boolean(not false); + e: boolean(not true); + f: boolean(not 2 < 1); } #if { @@ -271,6 +275,9 @@ html { i: if(true and isnumber(6), 6, 8); j: if(not(true) and true, 6, 8); k: if(true or true, 1); + // not without parentheses + m: if(not false, 1, 2); + n: if(not true, 1, 2); // see: https://github.com/less/less.js/issues/3371 @some: foo; diff --git a/packages/test-data/tests-unit/mixins-guards/mixins-guards.css b/packages/test-data/tests-unit/mixins-guards/mixins-guards.css index c54eca77e..fa164dd18 100644 --- a/packages/test-data/tests-unit/mixins-guards/mixins-guards.css +++ b/packages/test-data/tests-unit/mixins-guards/mixins-guards.css @@ -209,3 +209,9 @@ no-parenthesis: evaluated true 4; with-parenthesis: evaluated true; } +.test-not-noparens1 { + content: "not without parens true."; +} +.test-not-noparens2 { + content: "not without parens false."; +} diff --git a/packages/test-data/tests-unit/mixins-guards/mixins-guards.less b/packages/test-data/tests-unit/mixins-guards/mixins-guards.less index 834c57d2c..c52749acb 100644 --- a/packages/test-data/tests-unit/mixins-guards/mixins-guards.less +++ b/packages/test-data/tests-unit/mixins-guards/mixins-guards.less @@ -356,3 +356,14 @@ .orderOfEvaluation(true, true, false); } +// not without parentheses should work the same as not with parentheses +.test-not-noparens (@a) when not @a { + content: "not without parens false."; +} +.test-not-noparens (@a) when (@a) { + content: "not without parens true."; +} + +.test-not-noparens1 { .test-not-noparens(true) } +.test-not-noparens2 { .test-not-noparens(false) } + From 071488eceb26537f2879460c041b1b37e3105a08 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Mar 2026 20:11:47 +0000 Subject: [PATCH 3/3] Restrict not-without-parens to simple values only (keywords/variables) Complex conditions like `not 2 < 1` still require parentheses, keeping alignment with CSS media query syntax. Only simple bare values (keywords, variables, quoted strings) are allowed after `not` without parens: `not false`, `not @var`. Remove the `boolean(not 2 < 1)` test case that relied on the broader atomicCondition fallback. Co-authored-by: matthew-dean <414752+matthew-dean@users.noreply.github.com> --- packages/less/lib/less/parser/parser.js | 14 ++++++++++++-- .../test-data/tests-unit/functions/functions.css | 1 - .../test-data/tests-unit/functions/functions.less | 1 - 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/less/lib/less/parser/parser.js b/packages/less/lib/less/parser/parser.js index 97145214b..b43d5a938 100644 --- a/packages/less/lib/less/parser/parser.js +++ b/packages/less/lib/less/parser/parser.js @@ -2423,11 +2423,21 @@ const Parser = function Parser(context, imports, fileInfo, currentIndex) { }, negatedCondition: function (needsParens) { if (parserInput.$str('not')) { - const result = this.parenthesisCondition(needsParens) || this.atomicCondition(needsParens); + const result = this.parenthesisCondition(needsParens); if (result) { result.negate = !result.negate; + return result; + } + + // Allow simple bare values (keyword/variable) without parens, + // e.g., `not false` or `not @var`. + // Complex conditions (comparisons, function calls) require parentheses. + const entities = this.entities; + const index = parserInput.i; + const a = entities.keyword() || entities.variable() || entities.quoted() || entities.mixinLookup(); + if (a) { + return new(tree.Condition)('=', a, new(tree.Keyword)('true'), index + currentIndex, true); } - return result; } }, parenthesisCondition: function (needsParens) { diff --git a/packages/test-data/tests-unit/functions/functions.css b/packages/test-data/tests-unit/functions/functions.css index 970f8d80e..17a990259 100644 --- a/packages/test-data/tests-unit/functions/functions.css +++ b/packages/test-data/tests-unit/functions/functions.css @@ -226,7 +226,6 @@ html { c: false; d: true; e: false; - f: true; } #if { a: 1; diff --git a/packages/test-data/tests-unit/functions/functions.less b/packages/test-data/tests-unit/functions/functions.less index 1fe8c56de..f11b756d2 100644 --- a/packages/test-data/tests-unit/functions/functions.less +++ b/packages/test-data/tests-unit/functions/functions.less @@ -259,7 +259,6 @@ html { // not without parentheses (should behave the same as with parentheses) d: boolean(not false); e: boolean(not true); - f: boolean(not 2 < 1); } #if {