From 8ee19643ffa5d80068a645aebc0f08cd3a594ed0 Mon Sep 17 00:00:00 2001 From: Matthew Dean <414752+matthew-dean@users.noreply.github.com> Date: Wed, 7 Oct 2026 15:43:36 -0700 Subject: [PATCH 01/12] test(test-data): keep one tests-unit styles.config, with the math the goldens use tests-unit/ had two root configs that disagreed. styles.config.ts set mathMode: 'always'; styles.config.cjs carried the options the Less 4.x runner used for `_main/` (relativeUrls, silent, javascriptEnabled). The Jess fixture runner reads the .ts first and test/alpha-fixtures.mjs reads the .cjs first, so the settings covering tests-unit depended on the runner. The goldens settle which settings are right: - calc/calc.css keeps `50vh / 2`, and operations/operations-advanced.css keeps `10px / 2` and `10 / 5`: a division outside parens is not evaluated, which is the default `parens-division` math. Under 'always' both fixtures change. The Less 4.x outputs in legacy/ agree: Less 4.x matches operations-advanced and color-functions/modern-syntax only under parens-division. - urls/urls.css keeps urls from imported files as written. With relativeUrls: true, Less 4.x rewrites eight of its lines. - javascriptEnabled matters only to javascript-REMOVED/legacy, which has its own config, and silent does not change output. - Flat output already comes from the corpus root styles.config.ts. Keep styles.config.cjs, which both runners load in full, set it to mathMode: 'parens-division', and delete styles.config.ts. --- packages/test-data/tests-unit/styles.config.cjs | 17 +++++++++++------ packages/test-data/tests-unit/styles.config.ts | 8 -------- 2 files changed, 11 insertions(+), 14 deletions(-) delete mode 100644 packages/test-data/tests-unit/styles.config.ts diff --git a/packages/test-data/tests-unit/styles.config.cjs b/packages/test-data/tests-unit/styles.config.cjs index 85865102e5..7c22bc1e65 100644 --- a/packages/test-data/tests-unit/styles.config.cjs +++ b/packages/test-data/tests-unit/styles.config.cjs @@ -1,9 +1,14 @@ +// Settings for every fixture under tests-unit/. A fixture folder's own +// styles.config overrides them. Flat output (collapseNesting: true) comes +// from the corpus root styles.config.ts. +// +// URL rewriting stays off: urls/urls.css keeps urls from imported files as +// they are written. module.exports = { - language: { - less: { - "relativeUrls": true, - "silent": true, - "javascriptEnabled": true - } + compile: { + // The goldens leave a division outside parens unevaluated: calc/calc.css + // keeps `50vh / 2`, and operations/operations-advanced.css keeps + // `10px / 2` and `10 / 5`, as its Less 4.x output in legacy/ does. + mathMode: 'parens-division' } }; diff --git a/packages/test-data/tests-unit/styles.config.ts b/packages/test-data/tests-unit/styles.config.ts deleted file mode 100644 index cc02850313..0000000000 --- a/packages/test-data/tests-unit/styles.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -export default { - compile: { - mathMode: 'always' - }, - output: { - collapseNesting: true, - }, -}; \ No newline at end of file From 3820b3c262944bceaf5695ef0544964ae4f6e4f6 Mon Sep 17 00:00:00 2001 From: Matthew Dean <414752+matthew-dean@users.noreply.github.com> Date: Wed, 14 Oct 2026 17:42:51 -0700 Subject: [PATCH 02/12] test(test-data): keep extenders of different specificity out of one :is() `:is()` takes the specificity of its most specific argument, so grouping extenders of different specificity raises the weaker ones. An extend group now holds only extenders of equal specificity; the others are written as their own selectors: - extend-chaining: `.g` (0,1,0) and `:is(.i, .k).j` (0,2,0) are no longer grouped: `.g.h, :is(.i, .k).j.h`. - extend-nest: `.sidebar, .sidebar2` (0,1,0) split from `.type1 .sidebar3, .type2.sidebar4` (0,2,0). - extend, extend-selector: `.ext1 .ext2` (0,2,0) leaves the `.foo, .ext3, .ext4` (0,1,0) group. extend-nest also writes a leading `&` with its parent in place: `.amp-test-f&` under a multi-compound parent joins `.amp-test-f` to the parent's last compound instead of wrapping the parent in `:is()`. The `&` after `+` still refers to the whole parent and keeps its `:is()`. --- .../tests-unit/extend-chaining/extend-chaining.css | 3 ++- packages/test-data/tests-unit/extend-nest/extend-nest.css | 5 +++-- .../tests-unit/extend-selector/extend-selector.css | 6 ++++-- packages/test-data/tests-unit/extend/extend.css | 6 ++++-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/test-data/tests-unit/extend-chaining/extend-chaining.css b/packages/test-data/tests-unit/extend-chaining/extend-chaining.css index 5e97b8db44..2a10244c58 100644 --- a/packages/test-data/tests-unit/extend-chaining/extend-chaining.css +++ b/packages/test-data/tests-unit/extend-chaining/extend-chaining.css @@ -8,7 +8,8 @@ .d { color: black; } -:is(.g, :is(.i, .k).j).h { +.g.h, +:is(.i, .k).j.h { color: black; } :is(.i, .k).j { diff --git a/packages/test-data/tests-unit/extend-nest/extend-nest.css b/packages/test-data/tests-unit/extend-nest/extend-nest.css index 432b47ea1f..ac7cfe6bd8 100644 --- a/packages/test-data/tests-unit/extend-nest/extend-nest.css +++ b/packages/test-data/tests-unit/extend-nest/extend-nest.css @@ -5,7 +5,8 @@ width: 300px; background: red; } -:is(.sidebar, .sidebar2, .type1 .sidebar3, .type2.sidebar4) .box { +:is(.sidebar, .sidebar2) .box, +:is(.type1 .sidebar3, .type2.sidebar4) .box { background: #FFF; border: 1px solid #000; margin: 10px 0; @@ -33,6 +34,6 @@ notnested: black; } .amp-test-h, -.amp-test-f:is(.amp-test-c :is(.amp-test-a, .amp-test-b).amp-test-d:is(.amp-test-a, .amp-test-b).amp-test-e) + :is(.amp-test-c :is(.amp-test-a, .amp-test-b).amp-test-d:is(.amp-test-a, .amp-test-b).amp-test-e).amp-test-g { +.amp-test-c :is(.amp-test-a, .amp-test-b).amp-test-d:is(.amp-test-a, .amp-test-b).amp-test-e.amp-test-f + :is(.amp-test-c :is(.amp-test-a, .amp-test-b).amp-test-d:is(.amp-test-a, .amp-test-b).amp-test-e).amp-test-g { test: extended by masses of selectors; } diff --git a/packages/test-data/tests-unit/extend-selector/extend-selector.css b/packages/test-data/tests-unit/extend-selector/extend-selector.css index 960fe1a860..1d86418790 100644 --- a/packages/test-data/tests-unit/extend-selector/extend-selector.css +++ b/packages/test-data/tests-unit/extend-selector/extend-selector.css @@ -1,5 +1,7 @@ -:is(.foo, .ext1 .ext2, .ext3, .ext4) .bar, -:is(.foo, .ext1 .ext2, .ext3, .ext4) .baz { +:is(.foo, .ext3, .ext4) .bar, +.ext1 .ext2 .bar, +:is(.foo, .ext3, .ext4) .baz, +.ext1 .ext2 .baz { display: none; } div:is(.ext5, .ext7), diff --git a/packages/test-data/tests-unit/extend/extend.css b/packages/test-data/tests-unit/extend/extend.css index 3e69b5ca62..def3a7ad27 100644 --- a/packages/test-data/tests-unit/extend/extend.css +++ b/packages/test-data/tests-unit/extend/extend.css @@ -13,8 +13,10 @@ .badError { border-width: 3px; } -:is(.foo, .ext1 .ext2, .ext3, .ext4) :is(.bar, .ext3, .ext4), -:is(.foo, .ext1 .ext2, .ext3, .ext4) .baz { +:is(.foo, .ext3, .ext4) :is(.bar, .ext3, .ext4), +.ext1 .ext2 :is(.bar, .ext3, .ext4), +:is(.foo, .ext3, .ext4) .baz, +.ext1 .ext2 .baz { display: none; } div:is(.ext5, .ext7), From c331175647ec14fd73b852f46004b0bae7d10688 Mon Sep 17 00:00:00 2001 From: Matthew Dean <414752+matthew-dean@users.noreply.github.com> Date: Wed, 14 Oct 2026 17:42:51 -0700 Subject: [PATCH 03/12] test(test-data): write a parent reference and escaped selector text in place selectors: a compound written before `&` (`.qux&`, `.active&`, `.tst&:hover`) joins the last compound of the parent selector. `.qux&` under `.foo .bar, .foo .baz` means `.qux:is(.foo .bar, .foo .baz)`, so it is written `.foo .bar.qux, .foo .baz.qux`; the old golden put `.qux` on the ancestor (`.qux.foo .bar`), which matches different elements. Likewise `.first-level .second-level.active` and `.foo:not(.only-nested .level2.tst:hover)`. parse-interpolation: escaped text (`~'.a, .b, .c'`) interpolated into a selector prints exactly as written rather than being reparsed into an `:is()` group, so `.q@{classes-quoted}&:hover` gives `.q.a, .b, .c&:hover` and `@{c-quoted}@{d-quoted}` gives `.a, .b.c, .d`. The header's own comma before `baz-quoted` is a real selector-list separator, so that branch goes on its own line like every other nested selector list. --- .../parse-interpolation/parse-interpolation.css | 5 +++-- packages/test-data/tests-unit/selectors/selectors.css | 10 +++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/test-data/tests-unit/parse-interpolation/parse-interpolation.css b/packages/test-data/tests-unit/parse-interpolation/parse-interpolation.css index 2ad602d034..5599e29187 100644 --- a/packages/test-data/tests-unit/parse-interpolation/parse-interpolation.css +++ b/packages/test-data/tests-unit/parse-interpolation/parse-interpolation.css @@ -29,7 +29,8 @@ input[type=text], input[type=email], input[type=password], textarea { } } .bar2 { - .q:is(.a, .b, .c)&:hover, baz-quoted { + .q.a, .b, .c&:hover, + baz-quoted { color: green; } } @@ -38,7 +39,7 @@ input[type=text], input[type=email], input[type=password], textarea { foo: bar; } } -:is(.a, .b):is(.c, .d) { +.a, .b.c, .d { + .e-quoted { foo: bar; } diff --git a/packages/test-data/tests-unit/selectors/selectors.css b/packages/test-data/tests-unit/selectors/selectors.css index 2eda595425..66de8cf758 100644 --- a/packages/test-data/tests-unit/selectors/selectors.css +++ b/packages/test-data/tests-unit/selectors/selectors.css @@ -44,8 +44,8 @@ p a span { .qux :is(.foo .bar, .foo .baz) { display: inline; } -.qux.foo .bar, -.qux.foo .baz { +.foo .bar.qux, +.foo .baz.qux { display: inline-block; } .qux :is(.foo .bar, .foo .baz) .biz { @@ -140,7 +140,7 @@ blank blank blank blank blank blank blank blank blank blank blank blank blank bl .blood { color: red; } -.foo:not(.tst.only-nested .level2:hover) { +.foo:not(.only-nested .level2.tst:hover) { test: only-nested; } .foo.nestend-and-non-nested:not(.tst.nestend-and-non-nested:hover) { @@ -150,8 +150,8 @@ blank blank blank blank blank blank blank blank blank blank blank blank blank bl test: global scope; } .extend-this, -.active:is(.first-level .second-level), -:is(.first-level .second-level).active2 { +.first-level .second-level.active, +.first-level .second-level.active2 { content: '\2661'; } .x:is(.x.a) { From b201840eae0e0544e8b3f19b0a4390818dcf85fa Mon Sep 17 00:00:00 2001 From: Matthew Dean <414752+matthew-dean@users.noreply.github.com> Date: Wed, 14 Oct 2026 17:43:10 -0700 Subject: [PATCH 04/12] test(test-data): bring the bootstrap4 golden up to date Five kinds of change, all in the expected output only: - Extenders from another imported file: the `.input-group-sm/lg > ...` selectors that extend `.form-control-plaintext.form-control-lg` and `select.form-control-sm/lg:not([size]):not([multiple])` live in a different imported sheet. The golden was generated while those cross-import extenders were being dropped, so it lacked them. - `color-yiq()` text colour: on the `.btn-warning`/`.btn-light` hover and open-dropdown states the darkened background is still light, so the contrasting text colour is `#212529`, not `#fff`. The golden was generated while the darkened colour was read as black. - Nested rules under a multi-branch parent: a child selector without `&` combines with every branch of its parent. The golden kept only the first branch, dropping `.btn-group-toggle > .btn-group > .btn input[...]` and the `.input-group > .custom-select/.custom-file + ...` selectors. They are now written with the parent list in `:is()`. - Native nesting output folds a run of sibling children with equal specificity under one ancestor into `:is()`: `.table th, .table td` becomes `.table :is(th, td)`. The specificity is unchanged because every member is equal. - Extend groups keep specificity: `.btn-sm` (0,1,0) and `.btn-group-sm > .btn` (0,2,0), and each `.bs-tooltip-*`/`.bs-popover-*` pair (0,1,0 vs 0,2,0), are no longer joined in one `:is()`, which would raise the weaker member to the stronger specificity; each is written as its own selector. `&::before` under the two-compound parent ` .arrow` writes the parent in place rather than in a one-item `:is()`. --- .../tests-config/3rd-party/bootstrap4.css | 220 ++++++++++-------- 1 file changed, 119 insertions(+), 101 deletions(-) diff --git a/packages/test-data/tests-config/3rd-party/bootstrap4.css b/packages/test-data/tests-config/3rd-party/bootstrap4.css index bc744882b9..e997c06c4e 100644 --- a/packages/test-data/tests-config/3rd-party/bootstrap4.css +++ b/packages/test-data/tests-config/3rd-party/bootstrap4.css @@ -1315,8 +1315,7 @@ pre code { margin-bottom: 1rem; background-color: transparent; } -.table th, -.table td { +.table :is(th, td) { padding: 0.75rem; vertical-align: top; border-top: 1px solid #dee2e6; @@ -1331,23 +1330,19 @@ pre code { .table .table { background-color: #fff; } -.table-sm th, -.table-sm td { +.table-sm :is(th, td) { padding: 0.3rem; } .table-bordered { border: 1px solid #dee2e6; } -.table-bordered th, -.table-bordered td { +.table-bordered :is(th, td) { border: 1px solid #dee2e6; } -.table-bordered thead th, -.table-bordered thead td { +.table-bordered thead :is(th, td) { border-bottom-width: 2px; } -.table-borderless th, -.table-borderless td, +.table-borderless :is(th, td), .table-borderless thead th, .table-borderless tbody + tbody { border: 0; @@ -1480,8 +1475,7 @@ pre code { color: #fff; background-color: #212529; } -.table-dark th, -.table-dark td, +.table-dark :is(th, td), .table-dark thead th { border-color: #32383e; } @@ -1634,7 +1628,17 @@ select.form-control:focus::-ms-value { border-width: 1px 0; } .form-control-plaintext.form-control-sm, -.form-control-plaintext.form-control-lg { +.form-control-plaintext.form-control-lg, +.input-group-sm > .form-control-plaintext.form-control, +.input-group-sm > .input-group-prepend > .form-control-plaintext.input-group-text, +.input-group-sm > .input-group-append > .form-control-plaintext.input-group-text, +.input-group-sm > .input-group-prepend > .form-control-plaintext.btn, +.input-group-sm > .input-group-append > .form-control-plaintext.btn, +.input-group-lg > .form-control-plaintext.form-control, +.input-group-lg > .input-group-prepend > .form-control-plaintext.input-group-text, +.input-group-lg > .input-group-append > .form-control-plaintext.input-group-text, +.input-group-lg > .input-group-prepend > .form-control-plaintext.btn, +.input-group-lg > .input-group-append > .form-control-plaintext.btn { padding-right: 0; padding-left: 0; } @@ -1649,7 +1653,12 @@ select.form-control:focus::-ms-value { line-height: 1.5; border-radius: 0.2rem; } -select.form-control-sm:not([size]):not([multiple]) { +select.form-control-sm:not([size]):not([multiple]), +.input-group-sm > select.form-control:not([size]):not([multiple]), +.input-group-sm > .input-group-prepend > select.input-group-text:not([size]):not([multiple]), +.input-group-sm > .input-group-append > select.input-group-text:not([size]):not([multiple]), +.input-group-sm > .input-group-prepend > select.btn:not([size]):not([multiple]), +.input-group-sm > .input-group-append > select.btn:not([size]):not([multiple]) { height: calc(1.8125rem + 2px); } .form-control-lg, @@ -1663,7 +1672,12 @@ select.form-control-sm:not([size]):not([multiple]) { line-height: 1.5; border-radius: 0.3rem; } -select.form-control-lg:not([size]):not([multiple]) { +select.form-control-lg:not([size]):not([multiple]), +.input-group-lg > select.form-control:not([size]):not([multiple]), +.input-group-lg > .input-group-prepend > select.input-group-text:not([size]):not([multiple]), +.input-group-lg > .input-group-append > select.input-group-text:not([size]):not([multiple]), +.input-group-lg > .input-group-prepend > select.btn:not([size]):not([multiple]), +.input-group-lg > .input-group-append > select.btn:not([size]):not([multiple]) { height: calc(2.875rem + 2px); } .form-group { @@ -1888,8 +1902,7 @@ select.form-control-lg:not([size]):not([multiple]) { .form-inline .form-control-plaintext { display: inline-block; } - .form-inline .input-group, - .form-inline .custom-select { + .form-inline :is(.input-group, .custom-select) { width: auto; } .form-inline .form-check { @@ -2082,7 +2095,7 @@ fieldset:disabled a.btn { border-color: #ffc107; } .btn-warning:hover { - color: #fff; + color: #212529; background-color: #e0a800; border-color: #d39e00; } @@ -2099,7 +2112,7 @@ fieldset:disabled a.btn { .btn-warning:not(:disabled):not(.disabled):active, .btn-warning:not(:disabled):not(.disabled).active, .show > .btn-warning.dropdown-toggle { - color: #fff; + color: #212529; background-color: #d39e00; border-color: #c69500; } @@ -2142,7 +2155,7 @@ fieldset:disabled a.btn { border-color: #f8f9fa; } .btn-light:hover { - color: #fff; + color: #212529; background-color: #e2e6ea; border-color: #dae0e5; } @@ -2159,7 +2172,7 @@ fieldset:disabled a.btn { .btn-light:not(:disabled):not(.disabled):active, .btn-light:not(:disabled):not(.disabled).active, .show > .btn-light.dropdown-toggle { - color: #fff; + color: #212529; background-color: #dae0e5; border-color: #d3d9df; } @@ -2745,11 +2758,13 @@ fieldset:disabled a.btn { .dropleft .dropdown-toggle-split::before { margin-right: 0; } -:is(.btn-sm, .btn-group-sm > .btn) + .dropdown-toggle-split { +.btn-sm + .dropdown-toggle-split, +.btn-group-sm > .btn + .dropdown-toggle-split { padding-right: 0.375rem; padding-left: 0.375rem; } -:is(.btn-lg, .btn-group-lg > .btn) + .dropdown-toggle-split { +.btn-lg + .dropdown-toggle-split, +.btn-group-lg > .btn + .dropdown-toggle-split { padding-right: 0.75rem; padding-left: 0.75rem; } @@ -2758,8 +2773,7 @@ fieldset:disabled a.btn { align-items: flex-start; justify-content: center; } -.btn-group-vertical .btn, -.btn-group-vertical .btn-group { +.btn-group-vertical :is(.btn, .btn-group) { width: 100%; } .btn-group-vertical > .btn + .btn, @@ -2783,8 +2797,7 @@ fieldset:disabled a.btn { .btn-group-toggle > .btn-group > .btn { margin-bottom: 0; } -.btn-group-toggle > .btn input[type="radio"], -.btn-group-toggle > .btn input[type="checkbox"] { +:is(.btn-group-toggle > .btn, .btn-group-toggle > .btn-group > .btn) :is(input[type="radio"], input[type="checkbox"]) { position: absolute; clip: rect(0, 0, 0, 0); pointer-events: none; @@ -2807,9 +2820,9 @@ fieldset:disabled a.btn { :is(.input-group > .form-control, .input-group > .custom-select, .input-group > .custom-file):focus { z-index: 3; } -.input-group > .form-control + .form-control, -.input-group > .form-control + .custom-select, -.input-group > .form-control + .custom-file { +:is(.input-group > .form-control, .input-group > .custom-select, .input-group > .custom-file) + .form-control, +:is(.input-group > .form-control, .input-group > .custom-select, .input-group > .custom-file) + .custom-select, +:is(.input-group > .form-control, .input-group > .custom-select, .input-group > .custom-file) + .custom-file { margin-left: -1px; } :is(.input-group > .form-control, .input-group > .custom-select):not(:last-child) { @@ -2868,8 +2881,7 @@ fieldset:disabled a.btn { border: 1px solid #ced4da; border-radius: 0.25rem; } -.input-group-text input[type="radio"], -.input-group-text input[type="checkbox"] { +.input-group-text :is(input[type="radio"], input[type="checkbox"]) { margin-top: 0; } .input-group > .input-group-prepend > .btn, @@ -3514,8 +3526,7 @@ fieldset:disabled a.btn { } .navbar-light .navbar-nav .show > .nav-link, .navbar-light .navbar-nav .active > .nav-link, -.navbar-light .navbar-nav .nav-link.show, -.navbar-light .navbar-nav .nav-link.active { +.navbar-light .navbar-nav :is(.nav-link.show, .nav-link.active) { color: rgba(0, 0, 0, 0.9); } .navbar-light .navbar-toggler { @@ -3554,8 +3565,7 @@ fieldset:disabled a.btn { } .navbar-dark .navbar-nav .show > .nav-link, .navbar-dark .navbar-nav .active > .nav-link, -.navbar-dark .navbar-nav .nav-link.show, -.navbar-dark .navbar-nav .nav-link.active { +.navbar-dark .navbar-nav :is(.nav-link.show, .nav-link.active) { color: #fff; } .navbar-dark .navbar-toggler { @@ -3715,46 +3725,37 @@ fieldset:disabled a.btn { border-top-right-radius: 0; border-bottom-right-radius: 0; } - .card-group > .card:first-child .card-img-top, - .card-group > .card:first-child .card-header { + .card-group > .card:first-child :is(.card-img-top, .card-header) { border-top-right-radius: 0; } - .card-group > .card:first-child .card-img-bottom, - .card-group > .card:first-child .card-footer { + .card-group > .card:first-child :is(.card-img-bottom, .card-footer) { border-bottom-right-radius: 0; } .card-group > .card:last-child { border-top-left-radius: 0; border-bottom-left-radius: 0; } - .card-group > .card:last-child .card-img-top, - .card-group > .card:last-child .card-header { + .card-group > .card:last-child :is(.card-img-top, .card-header) { border-top-left-radius: 0; } - .card-group > .card:last-child .card-img-bottom, - .card-group > .card:last-child .card-footer { + .card-group > .card:last-child :is(.card-img-bottom, .card-footer) { border-bottom-left-radius: 0; } .card-group > .card:only-child { border-radius: 0.25rem; } - .card-group > .card:only-child .card-img-top, - .card-group > .card:only-child .card-header { + .card-group > .card:only-child :is(.card-img-top, .card-header) { border-top-left-radius: 0.25rem; border-top-right-radius: 0.25rem; } - .card-group > .card:only-child .card-img-bottom, - .card-group > .card:only-child .card-footer { + .card-group > .card:only-child :is(.card-img-bottom, .card-footer) { border-bottom-right-radius: 0.25rem; border-bottom-left-radius: 0.25rem; } .card-group > .card:not(:first-child):not(:last-child):not(:only-child) { border-radius: 0; } - .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-img-top, - .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-img-bottom, - .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-header, - .card-group > .card:not(:first-child):not(:last-child):not(:only-child) .card-footer { + .card-group > .card:not(:first-child):not(:last-child):not(:only-child) :is(.card-img-top, .card-img-bottom, .card-header, .card-footer) { border-radius: 0; } } @@ -4540,10 +4541,12 @@ button.close { .bs-tooltip-auto[x-placement^="top"] { padding: 0.4rem 0; } -:is(.bs-tooltip-top, .bs-tooltip-auto[x-placement^="top"]) .arrow { +.bs-tooltip-top .arrow, +.bs-tooltip-auto[x-placement^="top"] .arrow { bottom: 0; } -:is(:is(.bs-tooltip-top, .bs-tooltip-auto[x-placement^="top"]) .arrow)::before { +.bs-tooltip-top .arrow::before, +.bs-tooltip-auto[x-placement^="top"] .arrow::before { top: 0; border-width: 0.4rem 0.4rem 0; border-top-color: #000; @@ -4552,12 +4555,14 @@ button.close { .bs-tooltip-auto[x-placement^="right"] { padding: 0 0.4rem; } -:is(.bs-tooltip-right, .bs-tooltip-auto[x-placement^="right"]) .arrow { +.bs-tooltip-right .arrow, +.bs-tooltip-auto[x-placement^="right"] .arrow { left: 0; width: 0.4rem; height: 0.8rem; } -:is(:is(.bs-tooltip-right, .bs-tooltip-auto[x-placement^="right"]) .arrow)::before { +.bs-tooltip-right .arrow::before, +.bs-tooltip-auto[x-placement^="right"] .arrow::before { right: 0; border-width: 0.4rem 0.4rem 0.4rem 0; border-right-color: #000; @@ -4566,10 +4571,12 @@ button.close { .bs-tooltip-auto[x-placement^="bottom"] { padding: 0.4rem 0; } -:is(.bs-tooltip-bottom, .bs-tooltip-auto[x-placement^="bottom"]) .arrow { +.bs-tooltip-bottom .arrow, +.bs-tooltip-auto[x-placement^="bottom"] .arrow { top: 0; } -:is(:is(.bs-tooltip-bottom, .bs-tooltip-auto[x-placement^="bottom"]) .arrow)::before { +.bs-tooltip-bottom .arrow::before, +.bs-tooltip-auto[x-placement^="bottom"] .arrow::before { bottom: 0; border-width: 0 0.4rem 0.4rem; border-bottom-color: #000; @@ -4578,12 +4585,14 @@ button.close { .bs-tooltip-auto[x-placement^="left"] { padding: 0 0.4rem; } -:is(.bs-tooltip-left, .bs-tooltip-auto[x-placement^="left"]) .arrow { +.bs-tooltip-left .arrow, +.bs-tooltip-auto[x-placement^="left"] .arrow { right: 0; width: 0.4rem; height: 0.8rem; } -:is(:is(.bs-tooltip-left, .bs-tooltip-auto[x-placement^="left"]) .arrow)::before { +.bs-tooltip-left .arrow::before, +.bs-tooltip-auto[x-placement^="left"] .arrow::before { left: 0; border-width: 0.4rem 0 0.4rem 0.4rem; border-left-color: #000; @@ -4643,18 +4652,23 @@ button.close { .bs-popover-auto[x-placement^="top"] { margin-bottom: 0.5rem; } -:is(.bs-popover-top, .bs-popover-auto[x-placement^="top"]) .arrow { +.bs-popover-top .arrow, +.bs-popover-auto[x-placement^="top"] .arrow { bottom: calc((0.5rem + 1px) * -1); } -:is(.bs-popover-top, .bs-popover-auto[x-placement^="top"]) .arrow::before, -:is(.bs-popover-top, .bs-popover-auto[x-placement^="top"]) .arrow::after { +.bs-popover-top .arrow::before, +.bs-popover-auto[x-placement^="top"] .arrow::before, +.bs-popover-top .arrow::after, +.bs-popover-auto[x-placement^="top"] .arrow::after { border-width: 0.5rem 0.5rem 0; } -:is(.bs-popover-top, .bs-popover-auto[x-placement^="top"]) .arrow::before { +.bs-popover-top .arrow::before, +.bs-popover-auto[x-placement^="top"] .arrow::before { bottom: 0; border-top-color: rgba(0, 0, 0, 0.25); } -:is(.bs-popover-top, .bs-popover-auto[x-placement^="top"]) .arrow::after { +.bs-popover-top .arrow::after, +.bs-popover-auto[x-placement^="top"] .arrow::after { bottom: 1px; border-top-color: #fff; } @@ -4662,21 +4676,26 @@ button.close { .bs-popover-auto[x-placement^="right"] { margin-left: 0.5rem; } -:is(.bs-popover-right, .bs-popover-auto[x-placement^="right"]) .arrow { +.bs-popover-right .arrow, +.bs-popover-auto[x-placement^="right"] .arrow { left: calc((0.5rem + 1px) * -1); width: 0.5rem; height: 1rem; margin: 0.3rem 0; } -:is(.bs-popover-right, .bs-popover-auto[x-placement^="right"]) .arrow::before, -:is(.bs-popover-right, .bs-popover-auto[x-placement^="right"]) .arrow::after { +.bs-popover-right .arrow::before, +.bs-popover-auto[x-placement^="right"] .arrow::before, +.bs-popover-right .arrow::after, +.bs-popover-auto[x-placement^="right"] .arrow::after { border-width: 0.5rem 0.5rem 0.5rem 0; } -:is(.bs-popover-right, .bs-popover-auto[x-placement^="right"]) .arrow::before { +.bs-popover-right .arrow::before, +.bs-popover-auto[x-placement^="right"] .arrow::before { left: 0; border-right-color: rgba(0, 0, 0, 0.25); } -:is(.bs-popover-right, .bs-popover-auto[x-placement^="right"]) .arrow::after { +.bs-popover-right .arrow::after, +.bs-popover-auto[x-placement^="right"] .arrow::after { left: 1px; border-right-color: #fff; } @@ -4684,22 +4703,28 @@ button.close { .bs-popover-auto[x-placement^="bottom"] { margin-top: 0.5rem; } -:is(.bs-popover-bottom, .bs-popover-auto[x-placement^="bottom"]) .arrow { +.bs-popover-bottom .arrow, +.bs-popover-auto[x-placement^="bottom"] .arrow { top: calc((0.5rem + 1px) * -1); } -:is(.bs-popover-bottom, .bs-popover-auto[x-placement^="bottom"]) .arrow::before, -:is(.bs-popover-bottom, .bs-popover-auto[x-placement^="bottom"]) .arrow::after { +.bs-popover-bottom .arrow::before, +.bs-popover-auto[x-placement^="bottom"] .arrow::before, +.bs-popover-bottom .arrow::after, +.bs-popover-auto[x-placement^="bottom"] .arrow::after { border-width: 0 0.5rem 0.5rem 0.5rem; } -:is(.bs-popover-bottom, .bs-popover-auto[x-placement^="bottom"]) .arrow::before { +.bs-popover-bottom .arrow::before, +.bs-popover-auto[x-placement^="bottom"] .arrow::before { top: 0; border-bottom-color: rgba(0, 0, 0, 0.25); } -:is(.bs-popover-bottom, .bs-popover-auto[x-placement^="bottom"]) .arrow::after { +.bs-popover-bottom .arrow::after, +.bs-popover-auto[x-placement^="bottom"] .arrow::after { top: 1px; border-bottom-color: #fff; } -:is(.bs-popover-bottom, .bs-popover-auto[x-placement^="bottom"]) .popover-header::before { +.bs-popover-bottom .popover-header::before, +.bs-popover-auto[x-placement^="bottom"] .popover-header::before { position: absolute; top: 0; left: 50%; @@ -4713,21 +4738,26 @@ button.close { .bs-popover-auto[x-placement^="left"] { margin-right: 0.5rem; } -:is(.bs-popover-left, .bs-popover-auto[x-placement^="left"]) .arrow { +.bs-popover-left .arrow, +.bs-popover-auto[x-placement^="left"] .arrow { right: calc((0.5rem + 1px) * -1); width: 0.5rem; height: 1rem; margin: 0.3rem 0; } -:is(.bs-popover-left, .bs-popover-auto[x-placement^="left"]) .arrow::before, -:is(.bs-popover-left, .bs-popover-auto[x-placement^="left"]) .arrow::after { +.bs-popover-left .arrow::before, +.bs-popover-auto[x-placement^="left"] .arrow::before, +.bs-popover-left .arrow::after, +.bs-popover-auto[x-placement^="left"] .arrow::after { border-width: 0.5rem 0 0.5rem 0.5rem; } -:is(.bs-popover-left, .bs-popover-auto[x-placement^="left"]) .arrow::before { +.bs-popover-left .arrow::before, +.bs-popover-auto[x-placement^="left"] .arrow::before { right: 0; border-left-color: rgba(0, 0, 0, 0.25); } -:is(.bs-popover-left, .bs-popover-auto[x-placement^="left"]) .arrow::after { +.bs-popover-left .arrow::after, +.bs-popover-auto[x-placement^="left"] .arrow::after { right: 1px; border-left-color: #fff; } @@ -4817,20 +4847,14 @@ button.close { transition-duration: 0.6s; transition-property: opacity; } -.carousel-fade .carousel-item.active, -.carousel-fade .carousel-item-next.carousel-item-left, -.carousel-fade .carousel-item-prev.carousel-item-right { +.carousel-fade :is(.carousel-item.active, .carousel-item-next.carousel-item-left, .carousel-item-prev.carousel-item-right) { opacity: 1; } -.carousel-fade .active.carousel-item-left, -.carousel-fade .active.carousel-item-right { +.carousel-fade :is(.active.carousel-item-left, .active.carousel-item-right) { opacity: 0; } -.carousel-fade .carousel-item-next, -.carousel-fade .carousel-item-prev, -.carousel-fade .carousel-item.active, -.carousel-fade .active.carousel-item-left, -.carousel-fade .active.carousel-item-prev { +.carousel-fade :is(.carousel-item-next, .carousel-item-prev), +.carousel-fade :is(.carousel-item.active, .active.carousel-item-left, .active.carousel-item-prev) { transform: translateX(0); } @supports (transform-style: preserve-3d) { @@ -5289,10 +5313,7 @@ button.close { content: ""; } .embed-responsive .embed-responsive-item, -.embed-responsive iframe, -.embed-responsive embed, -.embed-responsive object, -.embed-responsive video { +.embed-responsive :is(iframe, embed, object, video) { position: absolute; top: 0; bottom: 0; @@ -7458,19 +7479,16 @@ a.text-dark:focus { .table { border-collapse: collapse !important; } - .table td, - .table th { + .table :is(td, th) { background-color: #fff !important; } - .table-bordered th, - .table-bordered td { + .table-bordered :is(th, td) { border: 1px solid #dee2e6 !important; } .table-dark { color: inherit; } - .table-dark th, - .table-dark td, + .table-dark :is(th, td), .table-dark thead th, .table-dark tbody + tbody { border-color: #dee2e6; From 8b5249c97869e67730340ac6ddc856b904c6ad8a Mon Sep 17 00:00:00 2001 From: Matthew Dean <414752+matthew-dean@users.noreply.github.com> Date: Wed, 14 Oct 2026 17:43:24 -0700 Subject: [PATCH 05/12] test(test-data): keep authored calc() parentheses; bind a non-dividing slash loosest calc: parentheses written inside `calc()` are kept exactly as authored, around math and around a lone value alike: `calc(((10vh)) + calc((5vh)))` stays as written, and `calc(100% - (3 * 1))` keeps its parentheses. `calc()` is evaluated by the browser, and the parentheses are part of what the author wrote; the old golden kept only the ones that changed precedence. math/parens-division: under `math: parens-division` a `/` outside parentheses does not divide; it is the separator in ` / `, as in `border-radius` or `font`. A separator binds more loosely than arithmetic, so each side is its own expression: `4px * (1 + 1) / @var + 3px` (with `@var: 4`) is `8px / (4 + 3px)`, and a unitless number takes the other operand's unit, so the result is `8px / 7px`. The old golden applied `+ 3px` to the whole slash and printed `8px / 4 + 3px`. --- .../tests-config/math/parens-division/parens.css | 2 +- packages/test-data/tests-unit/calc/calc.css | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/test-data/tests-config/math/parens-division/parens.css b/packages/test-data/tests-config/math/parens-division/parens.css index cc1efdf67e..bfd332733c 100644 --- a/packages/test-data/tests-config/math/parens-division/parens.css +++ b/packages/test-data/tests-config/math/parens-division/parens.css @@ -21,7 +21,7 @@ margin-keep: 12; margin-parts: 12; margin-all: 12; - border-radius-keep: 8px / 4 + 3px; + border-radius-keep: 8px / 7px; border-radius-parts: 8px / 7px; border-radius-all: 5px; } diff --git a/packages/test-data/tests-unit/calc/calc.css b/packages/test-data/tests-unit/calc/calc.css index 74f2d362c3..773df434a5 100644 --- a/packages/test-data/tests-unit/calc/calc.css +++ b/packages/test-data/tests-unit/calc/calc.css @@ -7,23 +7,23 @@ .calc-variables { root: calc(100% - 30px); root2: calc(100% - 40px); - width: calc(50% + 50vh / 2 - 20px); - height: calc(50% + 50vh / 2 - 20px); + width: calc(50% + (50vh / 2 - 20px)); + height: calc(50% + ((50vh / 2 - 20px))); } .calc-nested { - min-height: calc(10vh + 5vh); + min-height: calc(((10vh)) + calc((5vh))); nested: calc(calc(2.25rem + 2px) - 1px * 2); } .calc-functions { one: calc(100% - 20px); - two: calc(100% - (10px + 10px)); + two: calc(100% - (((10px + 10px)))); bar: calc(1 + 20%); } .calc-escape { - three: calc(100% - 3 * 1); - four: calc(100% - 3 * 1); + three: calc(100% - (3 * 1)); + four: calc(100% - (3 * 1)); } .calc-mixed { foo: 3 calc(3 + 4) 11; - height: calc(100% - (10px * 3 + 10px * 2)); + height: calc(100% - ((10px * 3) + (10px * 2))); } From 42049851cd6a13ebd14cef05da71499d5f32d6eb Mon Sep 17 00:00:00 2001 From: Matthew Dean <414752+matthew-dean@users.noreply.github.com> Date: Wed, 14 Oct 2026 17:43:37 -0700 Subject: [PATCH 06/12] test(test-data): count import-once per output context A sheet imported once is skipped on a later import only when that later copy would land in the same output context, so it would print the same CSS twice. An import inside `@media` or inside a rule block places the sheet's rules somewhere else, so it is rendered there too. - strict-imports: `imported.less` is imported at the root, inside `@media (max-width: 768px)` and inside `.container`; `.imported` now appears in all three places. The old golden applied import-once across the whole document and dropped the two nested copies. - import: `import-test-f.less` imports `import-test-e` at the root after an `@import` inside `@media screen` already placed a copy; the root copy's `body { width: 100%; }` is now in the output. --- .../tests-config/strict-imports/strict-imports.css | 6 ++++++ packages/test-data/tests-unit/import/import.css | 3 +++ 2 files changed, 9 insertions(+) diff --git a/packages/test-data/tests-config/strict-imports/strict-imports.css b/packages/test-data/tests-config/strict-imports/strict-imports.css index 7f3dbdcdfc..2a109b32bf 100644 --- a/packages/test-data/tests-config/strict-imports/strict-imports.css +++ b/packages/test-data/tests-config/strict-imports/strict-imports.css @@ -2,11 +2,17 @@ background: green; } @media (max-width: 768px) { + .imported { + background: green; + } .mobile { color: red; } } .container { + .imported { + background: green; + } .nested { color: blue; } diff --git a/packages/test-data/tests-unit/import/import.css b/packages/test-data/tests-unit/import/import.css index 6f411b27e6..283eafe185 100644 --- a/packages/test-data/tests-unit/import/import.css +++ b/packages/test-data/tests-unit/import/import.css @@ -21,6 +21,9 @@ height: 10px; color: red; } +body { + width: 100%; +} .test-rule-f { height: 10px; } From 4ca4ebb4ab5aec1580e7bd2e8ffa3e2e0572edef Mon Sep 17 00:00:00 2001 From: Matthew Dean <414752+matthew-dean@users.noreply.github.com> Date: Wed, 14 Oct 2026 17:43:37 -0700 Subject: [PATCH 07/12] test(test-data): write glued condition functions and page selectors as authored - media `and(max-width: 1280px)`, container `size(min-width: 60ch)`: with no space before `(`, `and(` and `size(` tokenize as a single function token (CSS Syntax 3, consume an ident-like token), so the condition is a `` function, not `and` or a container name followed by a parenthesized feature. Inserting a space changes what the query means (Media Queries 4, ``; CSS Conditional 5, `@container`), so the output keeps the authored bytes. - media `@page Test:first`: a `` allows no whitespace between the page name and its pseudo-page (CSS Paged Media 3), so `Test :first` matches no page. The authored `Test:first` is written as is. --- packages/test-data/tests-unit/container/container.css | 2 +- packages/test-data/tests-unit/media/media.css | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/test-data/tests-unit/container/container.css b/packages/test-data/tests-unit/container/container.css index c8f60bc1b9..c833fa0d64 100644 --- a/packages/test-data/tests-unit/container/container.css +++ b/packages/test-data/tests-unit/container/container.css @@ -23,7 +23,7 @@ border: 1px solid grey; } } -@container size (min-width: 60ch) { +@container size(min-width: 60ch) { .article--post header { grid-template-areas: "avatar name" diff --git a/packages/test-data/tests-unit/media/media.css b/packages/test-data/tests-unit/media/media.css index ac236bf8df..efe5751fa5 100644 --- a/packages/test-data/tests-unit/media/media.css +++ b/packages/test-data/tests-unit/media/media.css @@ -116,7 +116,7 @@ @page :right { margin: 0.5cm; } - @page Test :first { + @page Test:first { margin: 1cm; } @page :first { @@ -275,7 +275,7 @@ color: red; } } -@media screen and (max-width: 1280px) { +@media screen and(max-width: 1280px) { .form-process-table { color: red; } From 01d3542e6de59cc3fde8d237d1ff706dc303bac8 Mon Sep 17 00:00:00 2001 From: Matthew Dean <414752+matthew-dean@users.noreply.github.com> Date: Wed, 14 Oct 2026 17:43:37 -0700 Subject: [PATCH 08/12] test(test-data): interpolate the value in namespacing-8's custom property A custom property's value is an arbitrary token sequence (CSS Custom Properties 1, ``), and Less 5 writes a bare `@value` inside it as literal text, just as CSS would. Only `@{...}` interpolation substitutes a value there. The fixture means to emit each map entry as a custom property, so the source now writes `--@{key}: @{value};`. The expected output is unchanged. --- packages/test-data/tests-config/namespacing/namespacing-8.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/test-data/tests-config/namespacing/namespacing-8.less b/packages/test-data/tests-config/namespacing/namespacing-8.less index e73835c6b4..5a4d79e4fd 100644 --- a/packages/test-data/tests-config/namespacing/namespacing-8.less +++ b/packages/test-data/tests-config/namespacing/namespacing-8.less @@ -6,7 +6,7 @@ :root { each(@vars, { - --@{key}: @value; + --@{key}: @{value}; }); } From f1969ee1620e613da9b34ffb06d78c8fe597fab3 Mon Sep 17 00:00:00 2001 From: Matthew Dean <414752+matthew-dean@users.noreply.github.com> Date: Wed, 14 Oct 2026 17:43:37 -0700 Subject: [PATCH 09/12] test(alpha): expect eight updated goldens to fail until the Jess pin moves The alpha fixture gate renders each fixture with the pinned Jess release, 2.0.0-alpha.27. The goldens for calc, container, extend, extend-chaining, extend-nest, extend-selector, selectors and strict-imports now record output that release predates, so they are listed as expected render failures, each with what the golden now records. The gate reports any of them that starts passing, so each entry is removed when the pin moves to a release that emits it. --- packages/less/test/alpha-fixtures.mjs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/less/test/alpha-fixtures.mjs b/packages/less/test/alpha-fixtures.mjs index ad7db37072..62ea2c488c 100644 --- a/packages/less/test/alpha-fixtures.mjs +++ b/packages/less/test/alpha-fixtures.mjs @@ -127,7 +127,15 @@ const expectedFailureFixtures = new Map([ ['tests-unit/parser-slashed-combinator/parser-slashed-combinator.less', 'slashed combinator not yet supported'], ['tests-unit/permissive-parse/permissive-parse.less', 'permissive legacy parser corners are not alpha-supported'], ['tests-unit/media/media.less', 'top-level bare @var at-rule preludes are rejected'], - ['tests-unit/at-rule-variable-deprecated/at-rule-variable-deprecated.less', 'bare @variable references in at-rule structural positions are rejected in Less 5 alpha'] + ['tests-unit/at-rule-variable-deprecated/at-rule-variable-deprecated.less', 'bare @variable references in at-rule structural positions are rejected in Less 5 alpha'], + ['tests-config/strict-imports/strict-imports.less', 'golden imports the sheet again in each output context that has not placed it yet; the pinned Jess 2.0.0-alpha.27 predates it; passes once the pin moves to a release that has it'], + ['tests-unit/calc/calc.less', 'golden keeps the parentheses authored inside calc(); the pinned Jess 2.0.0-alpha.27 predates it; passes once the pin moves to a release that has it'], + ['tests-unit/container/container.less', 'golden keeps the glued `size(` function token as authored; the pinned Jess 2.0.0-alpha.27 predates it; passes once the pin moves to a release that has it'], + ['tests-unit/extend-chaining/extend-chaining.less', 'golden keeps extenders of different specificity out of one :is() group; the pinned Jess 2.0.0-alpha.27 predates it; passes once the pin moves to a release that has it'], + ['tests-unit/extend-nest/extend-nest.less', 'golden keeps extenders of different specificity out of one :is() group and writes a leading & parent in place; the pinned Jess 2.0.0-alpha.27 predates it; passes once the pin moves to a release that has it'], + ['tests-unit/extend-selector/extend-selector.less', 'golden keeps extenders of different specificity out of one :is() group; the pinned Jess 2.0.0-alpha.27 predates it; passes once the pin moves to a release that has it'], + ['tests-unit/extend/extend.less', 'golden keeps extenders of different specificity out of one :is() group; the pinned Jess 2.0.0-alpha.27 predates it; passes once the pin moves to a release that has it'], + ['tests-unit/selectors/selectors.less', 'golden joins a compound written before & to the last compound of its parent; the pinned Jess 2.0.0-alpha.27 predates it; passes once the pin moves to a release that has it'] ]); const expectedFailureDiagnosticCodes = new Map([ From 9166fcc8c870f134939feabe443c088cb420dbd0 Mon Sep 17 00:00:00 2001 From: Matthew Dean <414752+matthew-dean@users.noreply.github.com> Date: Fri, 16 Oct 2026 20:18:20 -0700 Subject: [PATCH 10/12] =?UTF-8?q?test(parse-interpolation):=20spell=20sele?= =?UTF-8?q?ctor=20capture=20as=20selector(=E2=80=A6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../parse-interpolation/parse-interpolation.less | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/test-data/tests-unit/parse-interpolation/parse-interpolation.less b/packages/test-data/tests-unit/parse-interpolation/parse-interpolation.less index c6526906ef..33a2ea89c1 100644 --- a/packages/test-data/tests-unit/parse-interpolation/parse-interpolation.less +++ b/packages/test-data/tests-unit/parse-interpolation/parse-interpolation.less @@ -1,7 +1,7 @@ // Parse interpolation tests -// Paired variants: selector-capture (`*[]`) and quoted selectors (`~'...'`). +// Paired variants: selector-capture (`selector()`) and quoted selectors (`~'...'`). -@inputs-cap: *[input[type=text], input[type=email], input[type=password], textarea]; +@inputs-cap: selector(input[type=text], input[type=email], input[type=password], textarea); @inputs-quoted: ~'input[type=text], input[type=email], input[type=password], textarea'; @{inputs-cap} { @@ -16,7 +16,7 @@ } } -@classes-cap: *[.a, .b, .c]; +@classes-cap: selector(.a, .b, .c); @classes-quoted: ~'.a, .b, .c'; @{classes-cap} { @@ -43,8 +43,8 @@ } } -@c-cap: *[.a, .b]; -@d-cap: *[.c, .d]; +@c-cap: selector(.a, .b); +@d-cap: selector(.c, .d); @e-cap: ~' + .e-cap'; @{c-cap}@{d-cap} { @@ -63,7 +63,7 @@ } } -@textClasses-cap: *[&[class="text"], &.text]; +@textClasses-cap: selector(&[class="text"], &.text); @textClasses-quoted: ~'&[class="text"], &.text'; input.cap { @@ -78,7 +78,7 @@ input.quoted { } } -@my-selector-cap: *[.selector-1, .selector-2]; +@my-selector-cap: selector(.selector-1, .selector-2); @my-selector-quoted: ~'.selector-1, .selector-2'; .master-page-cap { @@ -93,7 +93,7 @@ input.quoted { } } -@list-cap: *[apple, satsuma, banana, pear]; +@list-cap: selector(apple, satsuma, banana, pear); @{list-cap} { .fruit-cap-& { From c5bdaa9ae10388d975f7446c3fa73c75c2a52e89 Mon Sep 17 00:00:00 2001 From: Matthew Dean <414752+matthew-dean@users.noreply.github.com> Date: Mon, 19 Oct 2026 23:10:21 -0700 Subject: [PATCH 11/12] chore(alpha): pin Jess 2.0.0-alpha.29 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moves every Jess dependency to 2.0.0-alpha.29 and brings the alpha test expectations in line with it: - 13 expected failures now pass and are removed (the eight goldens waiting on the pin, sourcemaps-url, and four mixed-unit error fixtures that now reject). - functions.css: length() of a ~( … ) list bound to a variable is the list's length (3): ~( … ) passes a list as one argument and is not part of the value. - ampersand-merge-template-invalid.less renders the escaped text as written and is listed as an accepted divergence. - Empty output gets neither a source map nor an annotation, and a source-mapped file ends with its annotation and no trailing newline. - collapseNesting 'native' keeps child branches of different specificity apart; 'compact' folds them into one :is(). --- packages/less/package.json | 12 +- packages/less/test/alpha-fixtures.mjs | 16 +- packages/less/test/alpha-sourcemaps.mjs | 8 +- packages/less/test/lessc-alpha.mjs | 16 +- .../tests-unit/functions/functions.css | 2 +- pnpm-lock.yaml | 186 ++++++++---------- 6 files changed, 98 insertions(+), 142 deletions(-) diff --git a/packages/less/package.json b/packages/less/package.json index e967b4d03a..96421765e2 100644 --- a/packages/less/package.json +++ b/packages/less/package.json @@ -142,15 +142,15 @@ "rawcurrent": "https://raw.github.com/less/less.js/v", "sourcearchive": "https://github.com/less/less.js/archive/v", "dependencies": { - "@jesscss/compiler": "2.0.0-alpha.27", - "@jesscss/core": "2.0.0-alpha.27", - "@jesscss/plugin-less": "2.0.0-alpha.27", - "@jesscss/plugin-less-compat": "2.0.0-alpha.27", - "@jesscss/plugin-node-modules": "2.0.0-alpha.27", + "@jesscss/compiler": "2.0.0-alpha.29", + "@jesscss/core": "2.0.0-alpha.29", + "@jesscss/plugin-less": "2.0.0-alpha.29", + "@jesscss/plugin-less-compat": "2.0.0-alpha.29", + "@jesscss/plugin-node-modules": "2.0.0-alpha.29", "parse-node-version": "^1.0.1" }, "peerDependencies": { - "@jesscss/plugin-js": "2.0.0-alpha.27" + "@jesscss/plugin-js": "2.0.0-alpha.29" }, "peerDependenciesMeta": { "@jesscss/plugin-js": { diff --git a/packages/less/test/alpha-fixtures.mjs b/packages/less/test/alpha-fixtures.mjs index 62ea2c488c..c691627e81 100644 --- a/packages/less/test/alpha-fixtures.mjs +++ b/packages/less/test/alpha-fixtures.mjs @@ -117,7 +117,6 @@ const expectedFailureFixtures = new Map([ ['tests-config/sourcemaps-basepath/sourcemaps-basepath.less', 'map is produced (gated by test/alpha-sourcemaps.mjs) but its source-path normalization/embedded-sources do not byte-match the 4.x golden yet'], ['tests-config/sourcemaps-include-source/sourcemaps-include-source.less', 'map is produced (gated by test/alpha-sourcemaps.mjs) but its source-path normalization/embedded-sources do not byte-match the 4.x golden yet'], ['tests-config/sourcemaps-rootpath/sourcemaps-rootpath.less', 'map is produced (gated by test/alpha-sourcemaps.mjs) but its source-path normalization/embedded-sources do not byte-match the 4.x golden yet'], - ['tests-config/sourcemaps-url/sourcemaps-url.less', 'map is produced (gated by test/alpha-sourcemaps.mjs) but its source-path normalization/embedded-sources do not byte-match the 4.x golden yet'], ['tests-unit/mixins/mixins.less', 'same-named nested ruleset resolves the outer .recursion() mixin; remaining mismatch is fixture-local collapseNesting=false rendering'], ['tests-unit/property-name-interp/property-name-interp.less', 'deprecated dash-only @- and @{-} variable names are rejected'], ['tests-unit/plugin-module/plugin-module.less', 'legacy CommonJS @plugin graph with require() is not supported by the optional JS runtime'], @@ -127,15 +126,7 @@ const expectedFailureFixtures = new Map([ ['tests-unit/parser-slashed-combinator/parser-slashed-combinator.less', 'slashed combinator not yet supported'], ['tests-unit/permissive-parse/permissive-parse.less', 'permissive legacy parser corners are not alpha-supported'], ['tests-unit/media/media.less', 'top-level bare @var at-rule preludes are rejected'], - ['tests-unit/at-rule-variable-deprecated/at-rule-variable-deprecated.less', 'bare @variable references in at-rule structural positions are rejected in Less 5 alpha'], - ['tests-config/strict-imports/strict-imports.less', 'golden imports the sheet again in each output context that has not placed it yet; the pinned Jess 2.0.0-alpha.27 predates it; passes once the pin moves to a release that has it'], - ['tests-unit/calc/calc.less', 'golden keeps the parentheses authored inside calc(); the pinned Jess 2.0.0-alpha.27 predates it; passes once the pin moves to a release that has it'], - ['tests-unit/container/container.less', 'golden keeps the glued `size(` function token as authored; the pinned Jess 2.0.0-alpha.27 predates it; passes once the pin moves to a release that has it'], - ['tests-unit/extend-chaining/extend-chaining.less', 'golden keeps extenders of different specificity out of one :is() group; the pinned Jess 2.0.0-alpha.27 predates it; passes once the pin moves to a release that has it'], - ['tests-unit/extend-nest/extend-nest.less', 'golden keeps extenders of different specificity out of one :is() group and writes a leading & parent in place; the pinned Jess 2.0.0-alpha.27 predates it; passes once the pin moves to a release that has it'], - ['tests-unit/extend-selector/extend-selector.less', 'golden keeps extenders of different specificity out of one :is() group; the pinned Jess 2.0.0-alpha.27 predates it; passes once the pin moves to a release that has it'], - ['tests-unit/extend/extend.less', 'golden keeps extenders of different specificity out of one :is() group; the pinned Jess 2.0.0-alpha.27 predates it; passes once the pin moves to a release that has it'], - ['tests-unit/selectors/selectors.less', 'golden joins a compound written before & to the last compound of its parent; the pinned Jess 2.0.0-alpha.27 predates it; passes once the pin moves to a release that has it'] + ['tests-unit/at-rule-variable-deprecated/at-rule-variable-deprecated.less', 'bare @variable references in at-rule structural positions are rejected in Less 5 alpha'] ]); const expectedFailureDiagnosticCodes = new Map([ @@ -143,12 +134,9 @@ const expectedFailureDiagnosticCodes = new Map([ ]); const expectedErrorPasses = new Map([ - ['tests-error/eval/add-mixed-units.less', 'unit compatibility errors are not emitted yet'], - ['tests-error/eval/add-mixed-units2.less', 'unit compatibility errors are not emitted yet'], + ['tests-error/eval/ampersand-merge-template-invalid.less', 'accepted divergence: text prints as written (owner ruling 2026-10-09), so a merge template whose parent is escaped comma-list text renders that text instead of erroring'], ['tests-error/eval/color-func-invalid-color-2.less', 'color function argument errors are not emitted yet'], ['tests-error/eval/color-func-invalid-color.less', 'color function argument errors are not emitted yet'], - ['tests-error/eval/divide-mixed-units.less', 'unit compatibility errors are not emitted yet'], - ['tests-error/eval/multiply-mixed-units.less', 'unit compatibility errors are not emitted yet'], ['tests-error/eval/percentage-css-var.less', 'function argument type errors are not emitted yet'], ['tests-error/eval/percentage-non-number-argument.less', 'function argument type errors are not emitted yet'], ['tests-error/eval/svg-gradient1.less', 'svg-gradient argument validation errors are not emitted yet'], diff --git a/packages/less/test/alpha-sourcemaps.mjs b/packages/less/test/alpha-sourcemaps.mjs index ae37242c1d..b762b44069 100644 --- a/packages/less/test/alpha-sourcemaps.mjs +++ b/packages/less/test/alpha-sourcemaps.mjs @@ -143,11 +143,11 @@ function assertV3Map(json, { allowEmpty = false } = {}) { assert.match(css, /sourceMappingURL=flat\.css\.map/, 'flat sourceMapURL honored'); } -// 6. Empty input yields a v3 map (possibly empty) and never crashes. +// 6. Empty output gets neither a map nor an annotation, and never crashes. { - const { map } = await less.render('', { sourceMap: true }); - assert.ok(map, 'empty input still returns a map'); - assertV3Map(map, { allowEmpty: true }); + const { css, map } = await less.render('', { sourceMap: true }); + assert.equal(map, undefined, 'empty output gets no map'); + assert.doesNotMatch(css, /sourceMappingURL/, 'empty output gets no annotation'); } // 7. `outputSourceFiles` embeds the original source into sourcesContent. diff --git a/packages/less/test/lessc-alpha.mjs b/packages/less/test/lessc-alpha.mjs index 918f137dce..8d167ccc34 100644 --- a/packages/less/test/lessc-alpha.mjs +++ b/packages/less/test/lessc-alpha.mjs @@ -109,15 +109,15 @@ await realpath(compilerEntrypoint); ); } - // 'native' distributes the child selector list (each branch keeps its own - // specificity); 'compact' folds it into a single :is(). - const listNest = '.a, .b { .c, .d { x: 1 } }\n'; + // 'native' groups child branches of equal specificity in one :is() and + // distributes branches of different specificity; 'compact' folds them all. + const listNest = '.a, .b { .c, #d { x: 1 } }\n'; const nativeCss = (await less.render(listNest, { collapseNesting: 'native' })).css; assert.match(nativeCss, /:is\(\.a, \.b\) \.c/, "'native' distributes .c"); - assert.match(nativeCss, /:is\(\.a, \.b\) \.d/, "'native' distributes .d"); - assert.doesNotMatch(nativeCss, /:is\(\.c, \.d\)/, "'native' does not fold the child list"); + assert.match(nativeCss, /:is\(\.a, \.b\) #d/, "'native' distributes #d"); + assert.doesNotMatch(nativeCss, /:is\(\.c, #d\)/, "'native' keeps different specificities apart"); const compactCss = (await less.render(listNest, { collapseNesting: 'compact' })).css; - assert.match(compactCss, /:is\(\.a, \.b\) :is\(\.c, \.d\)/, "'compact' folds the child list"); + assert.match(compactCss, /:is\(\.a, \.b\) :is\(\.c, #d\)/, "'compact' folds the child list"); const source = '.parent { before: 1; .child { inside: 2; } after: 3; }\n'; assert.equal((await less.render(source)).css, `.parent { @@ -310,8 +310,8 @@ try { assert.equal(sm.code, 0, sm.stderr); assert.match(sm.stdout, /lessc: wrote .+sm\.css\.map\n/, '--source-map reports the sidecar map it wrote'); const smCss = await readFile(smOutput, 'utf8'); - assert.match(smCss, /\/\*# sourceMappingURL=sm\.css\.map \*\/\n$/, - '--source-map annotates the CSS with the sidecar map basename'); + assert.match(smCss, /\/\*# sourceMappingURL=sm\.css\.map \*\/$/, + '--source-map ends the CSS with the sidecar map annotation, no trailing newline'); const smMap = JSON.parse(await readFile(`${smOutput}.map`, 'utf8')); assert.equal(smMap.version, 3, 'the sidecar map is source-map v3'); assert.equal(smMap.file, 'sm.css', 'map.file is the CSS output name'); diff --git a/packages/test-data/tests-unit/functions/functions.css b/packages/test-data/tests-unit/functions/functions.css index 9d96baa632..b303058a24 100644 --- a/packages/test-data/tests-unit/functions/functions.css +++ b/packages/test-data/tests-unit/functions/functions.css @@ -239,7 +239,7 @@ html { } .paren-escapes { list-1: 1, 2, 3; - length-1: 1; + length-1: 3; item-1: 4; item-2: 5; item-3: 6; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3a34782235..ae4d938be5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,20 +30,20 @@ importers: packages/less: dependencies: '@jesscss/compiler': - specifier: 2.0.0-alpha.27 - version: 2.0.0-alpha.27(typescript@5.9.3) + specifier: 2.0.0-alpha.29 + version: 2.0.0-alpha.29(typescript@5.9.3) '@jesscss/core': - specifier: 2.0.0-alpha.27 - version: 2.0.0-alpha.27 + specifier: 2.0.0-alpha.29 + version: 2.0.0-alpha.29 '@jesscss/plugin-less': - specifier: 2.0.0-alpha.27 - version: 2.0.0-alpha.27(typescript@5.9.3) + specifier: 2.0.0-alpha.29 + version: 2.0.0-alpha.29(typescript@5.9.3) '@jesscss/plugin-less-compat': - specifier: 2.0.0-alpha.27 - version: 2.0.0-alpha.27 + specifier: 2.0.0-alpha.29 + version: 2.0.0-alpha.29 '@jesscss/plugin-node-modules': - specifier: 2.0.0-alpha.27 - version: 2.0.0-alpha.27 + specifier: 2.0.0-alpha.29 + version: 2.0.0-alpha.29 parse-node-version: specifier: ^1.0.1 version: 1.0.1 @@ -416,58 +416,52 @@ packages: resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} engines: {node: '>=8'} - '@jesscss/awaitable-pipe@2.0.0-alpha.27': - resolution: {integrity: sha512-RHzSYzCWqhF6L5dKMM7BGyUD7WZUxvExWHae2jr966VRIWlcIYQXbnd0yGG+rqyaNrRIxbAEMjCOZ7xC9+jedA==} + '@jesscss/awaitable-pipe@2.0.0-alpha.29': + resolution: {integrity: sha512-QA/697sPXrLove4LaEpISguhThw9x3ZNnj37OsmiSeaS8FW1Su81gQkqPCvYnDpP95tZlgF9Dte1hJeeMSx4Zg==} engines: {node: ^20.19.0 || >=22.12.0} - '@jesscss/compiler@2.0.0-alpha.27': - resolution: {integrity: sha512-dTkD77nftvUpaHmksjDmN1OZzkcooRakLcgYkq9xQqbayAKMyI980aWclpoFVKR1o8eDyM2lMfdyU/eIF+OrlA==} + '@jesscss/compiler@2.0.0-alpha.29': + resolution: {integrity: sha512-GpQlOoWk6aRh0TMo1e4rUeEmpGyuprNOvEdCOrdFeRDbgx/QJZwbwqQ2Kol/JpQqWGh7OK6LfGWy3LJIKWncLg==} engines: {node: ^20.19.0 || >=22.12.0} - '@jesscss/core@2.0.0-alpha.27': - resolution: {integrity: sha512-BytfZ34Q3mMZTh3HRlUGCxZzGYXGr+kSfFNfyInK+bb4UD2SqAQWG7gcCNnQmrAtLukvklQNkMeZgu1k/vQISA==} + '@jesscss/core@2.0.0-alpha.29': + resolution: {integrity: sha512-fxAKY+4m/LhUuHBJ3uBLC9hqLZ/zhRjrTJvd/QeICMMdaMvkTr98hiYPhoOijw+rd1RzK0tofiDwfvHx5RCZVA==} engines: {node: ^20.19.0 || >=22.12.0} - '@jesscss/css-parser@2.0.0-alpha.27': - resolution: {integrity: sha512-xURoGleHFSs5t/9N+pJgNS+is/4fp9jWwvmvG/WemHkBg7z1RddwcGeBJ1d+fHJ3LCfBzso0uFMBVtKFuEdrYA==} + '@jesscss/css-parser@2.0.0-alpha.29': + resolution: {integrity: sha512-FpI7Lw/FXc5OAtT+Fspu/680QMxqEd1Tvq8x3auprxp3+YMplGIT/RQDyYFPV6HWjnlrWRHiKurNZsluz1oOVw==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: - '@jesscss/core': 2.0.0-alpha.27 - peerDependenciesMeta: - '@jesscss/core': - optional: true + '@jesscss/core': 2.0.0-alpha.29 - '@jesscss/fns@2.0.0-alpha.27': - resolution: {integrity: sha512-wlxHSvE5iMRIpB+uicAVop3VHhtopTU2mDuhpWv7lFJEUgPin+bYsgxm0FaVBQ4gcD8vUHk+H2QhKm5qbuyClA==} + '@jesscss/fns@2.0.0-alpha.29': + resolution: {integrity: sha512-Foy+UsAn+JsNoMWAzZ6d4zE8cFUJC8jjrfnVJxkMlj3LNFh+zix6MbmWQ1jDcShXzfD9U2d+eStpOMpxJH83WA==} engines: {node: ^20.19.0 || >=22.12.0} - '@jesscss/less-parser@2.0.0-alpha.27': - resolution: {integrity: sha512-yumbcE/m84PQt0pgN42bAzFImwfUve0RLEfe/N4raMvD17nRBODYx5xk+Q5Sq36qCPITuEW0lZc/yQI+h3m0mA==} + '@jesscss/less-parser@2.0.0-alpha.29': + resolution: {integrity: sha512-6AFlmu/rxX1Vzyz5tnTQ+mJOjLm5kv12CYF3Cjfe5X+CYd7ZwTO9lhcE0gl76YYuXxmbkKPYoradsZVtwl9gRQ==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: - '@jesscss/core': 2.0.0-alpha.27 - peerDependenciesMeta: - '@jesscss/core': - optional: true + '@jesscss/core': 2.0.0-alpha.29 - '@jesscss/parser-shared@2.0.0-alpha.27': - resolution: {integrity: sha512-s3jjURhU8+Cbunc4B7mTJ9aMqeno6cC6KjsVJemLyAZFpiUf+R+cOYRjK0oOXIqk4fWFJ8GrkBuOUvVK4/wpdQ==} + '@jesscss/parser-shared@2.0.0-alpha.29': + resolution: {integrity: sha512-UJwSByl7xPJmx6zwf7Rzu3LCn0Wk1nijP+2oPFwI5yrfYoRQjpv1iUJZgbqt1Z8HRfCblynv9mG5fNU70e+p/Q==} engines: {node: ^20.19.0 || >=22.12.0} - '@jesscss/plugin-less-compat@2.0.0-alpha.27': - resolution: {integrity: sha512-ZVw9O62wF4N3+niAAQuPeUEjs8JI3968tEw/HXJc7aBYsxLYlKbbHSCMmhjgVFOVZEh4oueFl6ghDJ4e5T+RIQ==} + '@jesscss/plugin-less-compat@2.0.0-alpha.29': + resolution: {integrity: sha512-eNwxs48SgvDvmagGA0QE2lkP/X5afo4Sd6N4BV4DrdDolHzBdcUaJR6AFHLs9T9zA5P8bMPqoAsF6yhrjq4Qew==} engines: {node: ^20.19.0 || >=22.12.0} - '@jesscss/plugin-less@2.0.0-alpha.27': - resolution: {integrity: sha512-a4U/lemprv+ZtP942Y6s3L0P3uoDPJPpOmEz5sULuVE4mXmpbnSjC58ZaXHw4Ao75B3hXSTw3WuwaA08rEIiiw==} + '@jesscss/plugin-less@2.0.0-alpha.29': + resolution: {integrity: sha512-DvE9m2r2Fe1q1RWRE1DbmyyK+lAijdFl+8l9HpdcEkS/kwfwZHa9ioJazLrPkmKcxvfu0a5pqgCplHKPXQYHEQ==} engines: {node: ^20.19.0 || >=22.12.0} - '@jesscss/plugin-node-modules@2.0.0-alpha.27': - resolution: {integrity: sha512-VoM3sjI2C6m3HBdwSwFycZEa0fjYiCDCWW4W6NJa+8b/9HbjKyrhF3lK27Htn0ChC4RLHAIbJnCNkvJUZ1iR9g==} + '@jesscss/plugin-node-modules@2.0.0-alpha.29': + resolution: {integrity: sha512-3VPOnZMmXqdUPkB+jadqVzCEDV8wjPpeGVT+RQpZM5rmzof6Z3oaP5I0FouCfLbNi7n5n8jazl8m2v0SFono+Q==} engines: {node: ^20.19.0 || >=22.12.0} - '@jesscss/style-resolver@2.0.0-alpha.27': - resolution: {integrity: sha512-mwMKYBBP6yQVdT2etHBIiifV8fZDfwufLzlf6/go2aYp0I1gku7/QaXujNqFNWGWll2Jd78p3ft/ugcePBw5/A==} + '@jesscss/style-resolver@2.0.0-alpha.29': + resolution: {integrity: sha512-TMrQyJe2kvtTcowtlDsanklnoSWOxAp1VT2lfcuFTYQrVsZuHTffutxjSSLdBdD3huKAcp1gPjrxv2dmiDFaew==} engines: {node: ^20.19.0 || >=22.12.0} '@jest/diff-sequences@30.0.1': @@ -1124,9 +1118,6 @@ packages: big.js@7.0.1: resolution: {integrity: sha512-iFgV784tD8kq4ccF1xtNMZnXeZzVuXWWM+ERFzKQjv+A5G9HC8CY3DuV45vgzFFcW+u2tIvmF95+AzWgs6BjCg==} - bitset@5.2.3: - resolution: {integrity: sha512-uZ7++Z60MC9cZ+7YzQ1v9yPDydcjhmcMjGx2yoGTjjSXBoVMmTr2LCRbkpI19S9P/C75hhP7Bsakj+gVzVUDbQ==} - bl@0.9.5: resolution: {integrity: sha512-njlCs8XLBIK7LCChTWfzWuIAxkpmmLXcL7/igCofFT1B039Sz0IPnAmosN5QaO22lU4qr8LcUz2ojUlE6pLkRQ==} @@ -1288,9 +1279,6 @@ packages: resolution: {integrity: sha512-XjsuUwpDeY98+yz959OlUK6m7mLBM+1MEG5oaenfuQnNnrQk1WvtcvFgN3FNDP3f2NmZ211t0mNEfSEN1h0eIg==} engines: {node: '>=0.1.90'} - combinate@1.1.11: - resolution: {integrity: sha512-+2MNAQ29HtNejOxkgaTQPC2Bm+pQvFuqf7o18uObl/Bx3daX06kjLUNY/qa9f+YSqzqm/ic3SdrlfN0fvTlw2g==} - combined-stream@0.0.7: resolution: {integrity: sha512-qfexlmLp9MyrkajQVyjEDb0Vj+KhRgR/rxLiVhaihlT+ZkX0lReqtH6Ack40CvMDERR4b5eFp3CreskpBs1Pig==} engines: {node: '>= 0.8'} @@ -1356,9 +1344,6 @@ packages: resolution: {integrity: sha512-T6CEkoSV4q50zW3TlTHMbzy1E5+zlnNcY+yb7tWVYlTwPhx9LpnfAkd4wecpWknDyptp4k97LUZeInlf6jdzBg==} engines: {node: '>= 0.4'} - data-structure-typed@2.0.5: - resolution: {integrity: sha512-L/huLz6qr+vBdpp1NK4Rp44HHiI3sRughHBfETdO4PgcQ5qkYWPK2G6dBkpphvW4enkl8NSjXFdvTissK3FEaQ==} - data-view-buffer@1.0.2: resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} engines: {node: '>= 0.4'} @@ -2200,9 +2185,6 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} - known-css-properties@0.37.0: - resolution: {integrity: sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==} - less-plugin-autoprefix@1.5.1: resolution: {integrity: sha512-l++6pbkvw8XSD1soqugslzAaz0/YFrWXgc+PGo/EhLCjRo9zJfda2hFPLBSYrRDl62dTeDbN93Kx+1dvnHnkIw==} engines: {node: '>=0.4.2'} @@ -2573,8 +2555,8 @@ packages: resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==} engines: {node: '>= 0.10'} - parseman@0.51.2: - resolution: {integrity: sha512-Gc+SBIOMQGAU3v9GZlbrjow0/ZYLvFnPYU+qr+JW6L5cOIpx9m/Ve1ekNmjw+zTPdlHySWQVjSYVlNISyf8ppg==} + parseman@0.54.0: + resolution: {integrity: sha512-axAkINMNllfz1IRxKhI6qm3GVcBvvTVxnjvIo+kKCmL9PWYeK1XYQO/121uYuEsTvsw1QXpT5wTUyZmu+xTfcQ==} engines: {node: ^20.19.0 || >=22.13.0} hasBin: true @@ -3062,8 +3044,8 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - styles-config@2.0.0-alpha.27: - resolution: {integrity: sha512-CIUgQEop/9jwrwQIxuuHiu9tI+OLNsVQYd3TkI3FQ+BNMsUtJieMooe831T3Cok4m58CGGW+rEnvvOJTJYP1pA==} + styles-config@2.0.0-alpha.29: + resolution: {integrity: sha512-FfO+a0wzdZQYAwKTm1DGDJvK0+CunRxI3LxyBd4IpgjADKUQSnoqoMj1xF4EQmQHlttpRjy2wuZC+w1AlZTPlQ==} engines: {node: ^20.19.0 || >=22.12.0} superstruct@1.0.3: @@ -3577,82 +3559,76 @@ snapshots: '@istanbuljs/schema@0.1.6': {} - '@jesscss/awaitable-pipe@2.0.0-alpha.27': {} + '@jesscss/awaitable-pipe@2.0.0-alpha.29': {} - '@jesscss/compiler@2.0.0-alpha.27(typescript@5.9.3)': + '@jesscss/compiler@2.0.0-alpha.29(typescript@5.9.3)': dependencies: - '@jesscss/core': 2.0.0-alpha.27 + '@jesscss/core': 2.0.0-alpha.29 linecraft: 0.2.8 lodash-es: 4.18.1 - styles-config: 2.0.0-alpha.27(typescript@5.9.3) + styles-config: 2.0.0-alpha.29(typescript@5.9.3) transitivePeerDependencies: - typescript - '@jesscss/core@2.0.0-alpha.27': + '@jesscss/core@2.0.0-alpha.29': dependencies: - '@jesscss/awaitable-pipe': 2.0.0-alpha.27 + '@jesscss/awaitable-pipe': 2.0.0-alpha.29 '@jridgewell/gen-mapping': 0.3.13 '@ungap/set-methods': 0.1.1 big.js: 7.0.1 - bitset: 5.2.3 chalk: 4.1.2 color-name: 2.0.2 - combinate: 1.1.11 - data-structure-typed: 2.0.5 - lodash-es: 4.18.1 - '@jesscss/css-parser@2.0.0-alpha.27(@jesscss/core@2.0.0-alpha.27)': + '@jesscss/css-parser@2.0.0-alpha.29(@jesscss/core@2.0.0-alpha.29)': dependencies: - '@jesscss/parser-shared': 2.0.0-alpha.27 - parseman: 0.51.2 - optionalDependencies: - '@jesscss/core': 2.0.0-alpha.27 + '@jesscss/core': 2.0.0-alpha.29 + '@jesscss/parser-shared': 2.0.0-alpha.29 + parseman: 0.54.0 - '@jesscss/fns@2.0.0-alpha.27': + '@jesscss/fns@2.0.0-alpha.29': dependencies: - '@jesscss/awaitable-pipe': 2.0.0-alpha.27 - '@jesscss/core': 2.0.0-alpha.27 + '@jesscss/awaitable-pipe': 2.0.0-alpha.29 + '@jesscss/core': 2.0.0-alpha.29 big.js: 7.0.1 color-name: 2.0.2 lodash-es: 4.18.1 superstruct: 1.0.3 - '@jesscss/less-parser@2.0.0-alpha.27(@jesscss/core@2.0.0-alpha.27)': + '@jesscss/less-parser@2.0.0-alpha.29(@jesscss/core@2.0.0-alpha.29)': dependencies: - '@jesscss/css-parser': 2.0.0-alpha.27(@jesscss/core@2.0.0-alpha.27) - known-css-properties: 0.37.0 - parseman: 0.51.2 - optionalDependencies: - '@jesscss/core': 2.0.0-alpha.27 + '@jesscss/core': 2.0.0-alpha.29 + '@jesscss/css-parser': 2.0.0-alpha.29(@jesscss/core@2.0.0-alpha.29) + '@jesscss/parser-shared': 2.0.0-alpha.29 + parseman: 0.54.0 - '@jesscss/parser-shared@2.0.0-alpha.27': + '@jesscss/parser-shared@2.0.0-alpha.29': dependencies: - parseman: 0.51.2 + parseman: 0.54.0 - '@jesscss/plugin-less-compat@2.0.0-alpha.27': + '@jesscss/plugin-less-compat@2.0.0-alpha.29': dependencies: - '@jesscss/awaitable-pipe': 2.0.0-alpha.27 - '@jesscss/core': 2.0.0-alpha.27 - '@jesscss/less-parser': 2.0.0-alpha.27(@jesscss/core@2.0.0-alpha.27) - '@jesscss/plugin-node-modules': 2.0.0-alpha.27 + '@jesscss/awaitable-pipe': 2.0.0-alpha.29 + '@jesscss/core': 2.0.0-alpha.29 + '@jesscss/less-parser': 2.0.0-alpha.29(@jesscss/core@2.0.0-alpha.29) + '@jesscss/plugin-node-modules': 2.0.0-alpha.29 - '@jesscss/plugin-less@2.0.0-alpha.27(typescript@5.9.3)': + '@jesscss/plugin-less@2.0.0-alpha.29(typescript@5.9.3)': dependencies: - '@jesscss/core': 2.0.0-alpha.27 - '@jesscss/fns': 2.0.0-alpha.27 - '@jesscss/less-parser': 2.0.0-alpha.27(@jesscss/core@2.0.0-alpha.27) - '@jesscss/plugin-less-compat': 2.0.0-alpha.27 - '@jesscss/style-resolver': 2.0.0-alpha.27 - styles-config: 2.0.0-alpha.27(typescript@5.9.3) + '@jesscss/core': 2.0.0-alpha.29 + '@jesscss/fns': 2.0.0-alpha.29 + '@jesscss/less-parser': 2.0.0-alpha.29(@jesscss/core@2.0.0-alpha.29) + '@jesscss/plugin-less-compat': 2.0.0-alpha.29 + '@jesscss/style-resolver': 2.0.0-alpha.29 + styles-config: 2.0.0-alpha.29(typescript@5.9.3) transitivePeerDependencies: - typescript - '@jesscss/plugin-node-modules@2.0.0-alpha.27': + '@jesscss/plugin-node-modules@2.0.0-alpha.29': dependencies: - '@jesscss/core': 2.0.0-alpha.27 - '@jesscss/fns': 2.0.0-alpha.27 + '@jesscss/core': 2.0.0-alpha.29 + '@jesscss/fns': 2.0.0-alpha.29 - '@jesscss/style-resolver@2.0.0-alpha.27': {} + '@jesscss/style-resolver@2.0.0-alpha.29': {} '@jest/diff-sequences@30.0.1': {} @@ -4255,8 +4231,6 @@ snapshots: big.js@7.0.1: {} - bitset@5.2.3: {} - bl@0.9.5: dependencies: readable-stream: 1.0.34 @@ -4433,8 +4407,6 @@ snapshots: colors@0.5.1: {} - combinate@1.1.11: {} - combined-stream@0.0.7: dependencies: delayed-stream: 0.0.5 @@ -4490,8 +4462,6 @@ snapshots: ctype@0.5.3: {} - data-structure-typed@2.0.5: {} - data-view-buffer@1.0.2: dependencies: call-bound: 1.0.4 @@ -5465,8 +5435,6 @@ snapshots: kleur@4.1.5: {} - known-css-properties@0.37.0: {} - less-plugin-autoprefix@1.5.1: dependencies: autoprefixer: 6.7.7 @@ -5856,7 +5824,7 @@ snapshots: parse-node-version@1.0.1: {} - parseman@0.51.2: + parseman@0.54.0: dependencies: linecraft: 0.2.6 magic-string: 0.30.21 @@ -6362,9 +6330,9 @@ snapshots: strip-json-comments@3.1.1: {} - styles-config@2.0.0-alpha.27(typescript@5.9.3): + styles-config@2.0.0-alpha.29(typescript@5.9.3): dependencies: - '@jesscss/core': 2.0.0-alpha.27 + '@jesscss/core': 2.0.0-alpha.29 cosmiconfig: 9.0.2(typescript@5.9.3) picomatch: 4.0.5 transitivePeerDependencies: From a3ac767a914a3ef98986ae70275c6ba26de9611e Mon Sep 17 00:00:00 2001 From: Matthew Dean <414752+matthew-dean@users.noreply.github.com> Date: Mon, 19 Oct 2026 23:15:02 -0700 Subject: [PATCH 12/12] test(sourcemaps): check empty output with sourceMapURL and inline maps too --- packages/less/test/alpha-sourcemaps.mjs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/less/test/alpha-sourcemaps.mjs b/packages/less/test/alpha-sourcemaps.mjs index b762b44069..53d4fcfd4d 100644 --- a/packages/less/test/alpha-sourcemaps.mjs +++ b/packages/less/test/alpha-sourcemaps.mjs @@ -145,9 +145,12 @@ function assertV3Map(json, { allowEmpty = false } = {}) { // 6. Empty output gets neither a map nor an annotation, and never crashes. { - const { css, map } = await less.render('', { sourceMap: true }); - assert.equal(map, undefined, 'empty output gets no map'); - assert.doesNotMatch(css, /sourceMappingURL/, 'empty output gets no annotation'); + for (const sourceMap of [true, { sourceMapURL: 'empty.css.map' }, { sourceMapFileInline: true }]) { + const { css, map } = await less.render('', { sourceMap }); + const label = JSON.stringify(sourceMap); + assert.equal(map, undefined, `empty output gets no map (${label})`); + assert.doesNotMatch(css, /sourceMappingURL/, `empty output gets no annotation (${label})`); + } } // 7. `outputSourceFiles` embeds the original source into sourcesContent.