From cce9740989258fbf97093e2f1da5c0de60af30f7 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Thu, 16 Feb 2023 09:39:10 -0500 Subject: [PATCH 1/5] Cleanup code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes it more explicit that we’re parsing a string selector, modifying it, and turning it back into a string --- src/util/pluginUtils.js | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/util/pluginUtils.js b/src/util/pluginUtils.js index e347f404ba8a..91ca66e3b090 100644 --- a/src/util/pluginUtils.js +++ b/src/util/pluginUtils.js @@ -21,20 +21,23 @@ import negateValue from './negateValue' import { backgroundSize } from './validateFormalSyntax' import { flagEnabled } from '../featureFlags.js' +/** + * @param {string} selectors + * @param {(className: string) => string} updateClass + * @returns {string} + */ export function updateAllClasses(selectors, updateClass) { - let parser = selectorParser((selectors) => { - selectors.walkClasses((sel) => { - let updatedClass = updateClass(sel.value) - sel.value = updatedClass - if (sel.raws && sel.raws.value) { - sel.raws.value = escapeCommas(sel.raws.value) - } - }) - }) + let root = selectorParser().astSync(selectors) - let result = parser.processSync(selectors) + root.walkClasses((sel) => { + sel.value = updateClass(sel.value) - return result + if (sel.raws && sel.raws.value) { + sel.raws.value = escapeCommas(sel.raws.value) + } + }) + + return root.toString() } export function filterSelectorsForClass(selectors, classCandidate) { From ceb8f2d9e238cad4014c763b14821868d89d4d64 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Thu, 16 Feb 2023 09:40:53 -0500 Subject: [PATCH 2/5] Fix important modifier when :where is involved --- src/util/formatVariantSelector.js | 2 +- src/util/pluginUtils.js | 22 ++++++++-------- tests/important-modifier.test.js | 42 +++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 13 deletions(-) diff --git a/src/util/formatVariantSelector.js b/src/util/formatVariantSelector.js index ee6350b8ef33..da5e2c78dfbc 100644 --- a/src/util/formatVariantSelector.js +++ b/src/util/formatVariantSelector.js @@ -120,7 +120,7 @@ function resortSelector(sel) { * @param {Selector} ast * @param {string} base */ -function eliminateIrrelevantSelectors(sel, base) { +export function eliminateIrrelevantSelectors(sel, base) { let hasClassesMatchingCandidate = false sel.walk((child) => { diff --git a/src/util/pluginUtils.js b/src/util/pluginUtils.js index 91ca66e3b090..f17c0ae435d2 100644 --- a/src/util/pluginUtils.js +++ b/src/util/pluginUtils.js @@ -20,6 +20,7 @@ import { import negateValue from './negateValue' import { backgroundSize } from './validateFormalSyntax' import { flagEnabled } from '../featureFlags.js' +import { eliminateIrrelevantSelectors } from './formatVariantSelector.js' /** * @param {string} selectors @@ -40,21 +41,18 @@ export function updateAllClasses(selectors, updateClass) { return root.toString() } +/** + * @param {string} selectors + * @param {string} classCandidate + * @returns {string} + */ export function filterSelectorsForClass(selectors, classCandidate) { - let parser = selectorParser((selectors) => { - selectors.each((sel) => { - const containsClass = sel.nodes.some( - (node) => node.type === 'class' && node.value === classCandidate - ) - if (!containsClass) { - sel.remove() - } - }) - }) + let root = selectorParser().astSync(selectors) - let result = parser.processSync(selectors) + // Remove extraneous selectors that do not include the base candidate + root.each((sel) => eliminateIrrelevantSelectors(sel, classCandidate)) - return result + return root.toString() } function resolveArbitraryValue(modifier, validate) { diff --git a/tests/important-modifier.test.js b/tests/important-modifier.test.js index bde7eee2ea84..6d30fd0bceb3 100644 --- a/tests/important-modifier.test.js +++ b/tests/important-modifier.test.js @@ -108,4 +108,46 @@ crosscheck(() => { `) }) }) + + test('the important modifier works on utilities using :where()', () => { + let config = { + content: [ + { + raw: html`
`, + }, + ], + corePlugins: { preflight: false }, + plugins: [ + function ({ addComponents }) { + addComponents({ + ':where(.btn)': { + backgroundColor: '#00f', + }, + }) + }, + ], + } + + let input = css` + @tailwind components; + @tailwind utilities; + ` + + return run(input, config).then((result) => { + expect(result.css).toMatchFormattedCss(css` + :where(.\!btn) { + background-color: #00f !important; + } + :where(.btn) { + background-color: #00f; + } + :where(.hover\:btn:hover) { + background-color: #00f; + } + :where(.hover\:focus\:disabled\:\!btn:disabled:focus:hover) { + background-color: #00f !important; + } + `) + }) + }) }) From cc2fab1c1b189649c12d8f8dd5b453dc8cde5f33 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Thu, 16 Feb 2023 09:44:19 -0500 Subject: [PATCH 3/5] Only parse selector list once when handling the important modifier --- src/lib/generateRules.js | 22 ++++++++++++++-------- src/util/pluginUtils.js | 22 ++-------------------- 2 files changed, 16 insertions(+), 28 deletions(-) diff --git a/src/lib/generateRules.js b/src/lib/generateRules.js index 0533a9e86c51..b7f55a6ea3af 100644 --- a/src/lib/generateRules.js +++ b/src/lib/generateRules.js @@ -111,22 +111,28 @@ function applyImportant(matches, classCandidate) { if (matches.length === 0) { return matches } + let result = [] for (let [meta, rule] of matches) { let container = postcss.root({ nodes: [rule.clone()] }) + container.walkRules((r) => { - r.selector = updateAllClasses( - filterSelectorsForClass(r.selector, classCandidate), - (className) => { - if (className === classCandidate) { - return `!${className}` - } - return className - } + let ast = selectorParser().astSync(r.selector) + + // Remove extraneous selectors that do not include the base candidate + ast.each((sel) => eliminateIrrelevantSelectors(sel, classCandidate)) + + // Update all instances of the base candidate to include the important marker + updateAllClasses(ast, (className) => + className === classCandidate ? `!${className}` : className ) + + r.selector = ast.toString() + r.walkDecls((d) => (d.important = true)) }) + result.push([{ ...meta, important: true }, container.nodes[0]]) } diff --git a/src/util/pluginUtils.js b/src/util/pluginUtils.js index f17c0ae435d2..ba18f0cd9425 100644 --- a/src/util/pluginUtils.js +++ b/src/util/pluginUtils.js @@ -23,36 +23,18 @@ import { flagEnabled } from '../featureFlags.js' import { eliminateIrrelevantSelectors } from './formatVariantSelector.js' /** - * @param {string} selectors + * @param {import('postcss-selector-parser').Container} selectors * @param {(className: string) => string} updateClass * @returns {string} */ export function updateAllClasses(selectors, updateClass) { - let root = selectorParser().astSync(selectors) - - root.walkClasses((sel) => { + selectors.walkClasses((sel) => { sel.value = updateClass(sel.value) if (sel.raws && sel.raws.value) { sel.raws.value = escapeCommas(sel.raws.value) } }) - - return root.toString() -} - -/** - * @param {string} selectors - * @param {string} classCandidate - * @returns {string} - */ -export function filterSelectorsForClass(selectors, classCandidate) { - let root = selectorParser().astSync(selectors) - - // Remove extraneous selectors that do not include the base candidate - root.each((sel) => eliminateIrrelevantSelectors(sel, classCandidate)) - - return root.toString() } function resolveArbitraryValue(modifier, validate) { From 513a4812575f7b25adac55be67401f62ae23d09e Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Thu, 16 Feb 2023 09:52:37 -0500 Subject: [PATCH 4/5] Fix import --- src/lib/generateRules.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/generateRules.js b/src/lib/generateRules.js index b7f55a6ea3af..0bd51e2e50cf 100644 --- a/src/lib/generateRules.js +++ b/src/lib/generateRules.js @@ -6,7 +6,11 @@ import prefixSelector from '../util/prefixSelector' import { updateAllClasses, filterSelectorsForClass, getMatchingTypes } from '../util/pluginUtils' import log from '../util/log' import * as sharedState from './sharedState' -import { formatVariantSelector, finalizeSelector } from '../util/formatVariantSelector' +import { + formatVariantSelector, + finalizeSelector, + eliminateIrrelevantSelectors, +} from '../util/formatVariantSelector' import { asClass } from '../util/nameClass' import { normalize } from '../util/dataTypes' import { isValidVariantFormatString, parseVariant } from './setupContextUtils' From 74afac959cef9e64228b1c2896f5f3f30125a0ee Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Thu, 16 Feb 2023 09:59:47 -0500 Subject: [PATCH 5/5] Fix lint errors --- src/lib/generateRules.js | 2 +- src/util/pluginUtils.js | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/generateRules.js b/src/lib/generateRules.js index 0bd51e2e50cf..f7096e556602 100644 --- a/src/lib/generateRules.js +++ b/src/lib/generateRules.js @@ -3,7 +3,7 @@ import selectorParser from 'postcss-selector-parser' import parseObjectStyles from '../util/parseObjectStyles' import isPlainObject from '../util/isPlainObject' import prefixSelector from '../util/prefixSelector' -import { updateAllClasses, filterSelectorsForClass, getMatchingTypes } from '../util/pluginUtils' +import { updateAllClasses, getMatchingTypes } from '../util/pluginUtils' import log from '../util/log' import * as sharedState from './sharedState' import { diff --git a/src/util/pluginUtils.js b/src/util/pluginUtils.js index ba18f0cd9425..407019c905a6 100644 --- a/src/util/pluginUtils.js +++ b/src/util/pluginUtils.js @@ -1,4 +1,3 @@ -import selectorParser from 'postcss-selector-parser' import escapeCommas from './escapeCommas' import { withAlphaValue } from './withAlphaVariable' import { @@ -20,7 +19,6 @@ import { import negateValue from './negateValue' import { backgroundSize } from './validateFormalSyntax' import { flagEnabled } from '../featureFlags.js' -import { eliminateIrrelevantSelectors } from './formatVariantSelector.js' /** * @param {import('postcss-selector-parser').Container} selectors