From 490b1af2412329fbc25fd94bc709707abb47aef7 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Wed, 22 Feb 2023 11:19:33 -0500 Subject: [PATCH 1/8] Allow escaping in `splitAtTopLevelOnly` --- src/util/splitAtTopLevelOnly.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/util/splitAtTopLevelOnly.js b/src/util/splitAtTopLevelOnly.js index 63fd767403ce..a749c7932ed9 100644 --- a/src/util/splitAtTopLevelOnly.js +++ b/src/util/splitAtTopLevelOnly.js @@ -17,17 +17,24 @@ export function splitAtTopLevelOnly(input, separator) { let stack = [] let parts = [] let lastPos = 0 + let isEscaped = false for (let idx = 0; idx < input.length; idx++) { let char = input[idx] - if (stack.length === 0 && char === separator[0]) { + if (stack.length === 0 && char === separator[0] && !isEscaped) { if (separator.length === 1 || input.slice(idx, idx + separator.length) === separator) { parts.push(input.slice(lastPos, idx)) lastPos = idx + separator.length } } + if (isEscaped) { + isEscaped = false + } else if (char === '\\') { + isEscaped = true + } + if (char === '(' || char === '[' || char === '{') { stack.push(char) } else if ( From 1fffa49cfdf783678886a95da2cd3196b6358562 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Wed, 22 Feb 2023 11:20:25 -0500 Subject: [PATCH 2/8] Correctly parse arbitrary variants that have multiple selectors --- src/lib/generateRules.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/lib/generateRules.js b/src/lib/generateRules.js index f7096e556602..3dea37f69908 100644 --- a/src/lib/generateRules.js +++ b/src/lib/generateRules.js @@ -205,17 +205,21 @@ function applyVariant(variant, matches, context) { // Register arbitrary variants if (isArbitraryValue(variant) && !context.variantMap.has(variant)) { + let sort = context.offsets.recordVariant(variant) + let selector = normalize(variant.slice(1, -1)) + let selectors = splitAtTopLevelOnly(selector, ',') - if (!isValidVariantFormatString(selector)) { + if (!selectors.every(isValidVariantFormatString)) { return [] } - let fn = parseVariant(selector) - - let sort = context.offsets.recordVariant(variant) + let records = selectors.map((sel, idx) => [ + context.offsets.applyParallelOffset(sort, idx), + parseVariant(sel.trim()), + ]) - context.variantMap.set(variant, [[sort, fn]]) + context.variantMap.set(variant, records) } if (context.variantMap.has(variant)) { From 63a99536868d4ac6ec35826c38dc7ec5f3492c13 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Wed, 22 Feb 2023 11:21:03 -0500 Subject: [PATCH 3/8] Explicitly disallow multiple selector arbitrary variants Now that we parse them correctly we can restrict them to explicitly supporting only a single selector --- src/lib/generateRules.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib/generateRules.js b/src/lib/generateRules.js index 3dea37f69908..0306893ad3ef 100644 --- a/src/lib/generateRules.js +++ b/src/lib/generateRules.js @@ -210,6 +210,11 @@ function applyVariant(variant, matches, context) { let selector = normalize(variant.slice(1, -1)) let selectors = splitAtTopLevelOnly(selector, ',') + // We do not support multiple selectors for arbitrary variants + if (selectors.length > 1) { + return [] + } + if (!selectors.every(isValidVariantFormatString)) { return [] } From c56c96e9c3a779abbbdd481184c6d35090393285 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Wed, 22 Feb 2023 11:22:50 -0500 Subject: [PATCH 4/8] Add test to verify that multiple selector arbitrary variants are dropped --- tests/arbitrary-variants.test.js | 43 ++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/arbitrary-variants.test.js b/tests/arbitrary-variants.test.js index 46da5cdf2027..04a6d49955a0 100644 --- a/tests/arbitrary-variants.test.js +++ b/tests/arbitrary-variants.test.js @@ -1116,6 +1116,49 @@ crosscheck(({ stable, oxide }) => { }) }) + it('it should discard arbitrary variants with multiple selectors', () => { + let config = { + content: [ + { + raw: html` +
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+ `, + }, + ], + corePlugins: { preflight: false }, + } + + let input = css` + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + .p-1, + .span\,div .hover\:\[\.span\\\,div_\&\]\:p-1:hover, + div .\[div_\&\]\:p-1, + div .hover\:\[div_\&\]\:p-1:hover { + padding: 0.25rem; + } + `) + }) + }) + it('should sort multiple variant fns with normal variants between them', () => { /** @type {string[]} */ let lines = [] From d7b2f2631e362d1a94f22b56ebf70a8844742404 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Wed, 22 Feb 2023 11:26:37 -0500 Subject: [PATCH 5/8] Add test --- tests/arbitrary-variants.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/arbitrary-variants.test.js b/tests/arbitrary-variants.test.js index 04a6d49955a0..f81f361efa54 100644 --- a/tests/arbitrary-variants.test.js +++ b/tests/arbitrary-variants.test.js @@ -1137,6 +1137,7 @@ crosscheck(({ stable, oxide }) => {
+
`, }, ], @@ -1151,6 +1152,7 @@ crosscheck(({ stable, oxide }) => { expect(result.css).toMatchFormattedCss(css` .p-1, .span\,div .hover\:\[\.span\\\,div_\&\]\:p-1:hover, + :is(span, div) .hover\:\[\:is\(span\,div\)_\&\]\:p-1:hover, div .\[div_\&\]\:p-1, div .hover\:\[div_\&\]\:p-1:hover { padding: 0.25rem; From 0d027f117ca9126754234288e3be4c8258df94bd Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Wed, 22 Feb 2023 11:38:38 -0500 Subject: [PATCH 6/8] Make prettier happy --- tests/arbitrary-variants.test.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/arbitrary-variants.test.js b/tests/arbitrary-variants.test.js index f81f361efa54..fd54f1c31df2 100644 --- a/tests/arbitrary-variants.test.js +++ b/tests/arbitrary-variants.test.js @@ -1134,12 +1134,14 @@ crosscheck(({ stable, oxide }) => {
- - -
`, }, + { + // escaped commas are a-ok + // This is separate because prettier complains about `\,` in the template string + raw: '
' + }, ], corePlugins: { preflight: false }, } From 5d252569c316a231a25ea1f7c4653a1ce519d63e Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Wed, 22 Feb 2023 14:15:01 -0500 Subject: [PATCH 7/8] Fix CS --- tests/arbitrary-variants.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/arbitrary-variants.test.js b/tests/arbitrary-variants.test.js index fd54f1c31df2..b210bdc77ecb 100644 --- a/tests/arbitrary-variants.test.js +++ b/tests/arbitrary-variants.test.js @@ -1140,7 +1140,7 @@ crosscheck(({ stable, oxide }) => { { // escaped commas are a-ok // This is separate because prettier complains about `\,` in the template string - raw: '
' + raw: '
', }, ], corePlugins: { preflight: false }, From 81090836990ae4237b3db3f4f9ea72f178ee2add Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Wed, 22 Feb 2023 14:36:05 -0500 Subject: [PATCH 8/8] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e76da292e3f4..c4d96da84e49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `caption-side` utilities ([#10470](https://github.com/tailwindlabs/tailwindcss/pull/10470)) - Add `justify-normal` and `justify-stretch` utilities ([#10560](https://github.com/tailwindlabs/tailwindcss/pull/10560)) +### Fixed + +- Disallow multiple selectors in arbitrary variants ([#10655](https://github.com/tailwindlabs/tailwindcss/pull/10655)) + ### Changed - [Oxide] Disable color opacity plugins by default in the `oxide` engine ([#10618](https://github.com/tailwindlabs/tailwindcss/pull/10618))