diff --git a/__tests__/variantsAtRule.test.js b/__tests__/variantsAtRule.test.js index d0b20e4fc..44411663f 100644 --- a/__tests__/variantsAtRule.test.js +++ b/__tests__/variantsAtRule.test.js @@ -300,10 +300,43 @@ test('plugin variants can modify selectors with a simplified API', () => { ...config, plugins: [ ...config.plugins, - function({ addVariant }) { + function({ addVariant, e }) { addVariant('first-child', ({ modifySelectors, separator }) => { modifySelectors(({ className }) => { - return `.first-child${separator}${className}:first-child` + return `.${e(`first-child${separator}${className}`)}:first-child` + }) + }) + }, + ], + }).then(result => { + expect(result.css).toMatchCss(output) + expect(result.warnings().length).toBe(0) + }) +}) + +test('plugin variants that use modify selectors need to manually escape the class name they are modifying', () => { + const input = ` + @variants first-child { + .banana-1\\/2 { color: yellow; } + .chocolate-1\\.5 { color: brown; } + } + ` + + const output = ` + .banana-1\\/2 { color: yellow; } + .chocolate-1\\.5 { color: brown; } + .first-child\\:banana-1\\/2:first-child { color: yellow; } + .first-child\\:chocolate-1\\.5:first-child { color: brown; } + ` + + return run(input, { + ...config, + plugins: [ + ...config.plugins, + function({ addVariant, e }) { + addVariant('first-child', ({ modifySelectors, separator }) => { + modifySelectors(({ className }) => { + return `.${e(`first-child${separator}${className}`)}:first-child` }) }) }, @@ -335,13 +368,13 @@ test('plugin variants can wrap rules in another at-rule using the raw PostCSS AP ...config, plugins: [ ...config.plugins, - function({ addVariant }) { + function({ addVariant, e }) { addVariant('supports-grid', ({ container, separator }) => { const supportsRule = postcss.atRule({ name: 'supports', params: '(display: grid)' }) supportsRule.nodes = container.nodes container.nodes = [supportsRule] supportsRule.walkRules(rule => { - rule.selector = `.supports-grid${separator}${rule.selector.slice(1)}` + rule.selector = `.${e(`supports-grid${separator}${rule.selector.slice(1)}`)}` }) }) }, diff --git a/src/lib/substituteVariantsAtRules.js b/src/lib/substituteVariantsAtRules.js index a586a66ec..73981d6c3 100644 --- a/src/lib/substituteVariantsAtRules.js +++ b/src/lib/substituteVariantsAtRules.js @@ -1,11 +1,12 @@ import _ from 'lodash' import postcss from 'postcss' import generateVariantFunction from '../util/generateVariantFunction' +import e from '../util/escapeClassName' function generatePseudoClassVariant(pseudoClass) { return generateVariantFunction(({ modifySelectors, separator }) => { return modifySelectors(({ className }) => { - return `.${pseudoClass}${separator}${className}:${pseudoClass}` + return `.${e(`${pseudoClass}${separator}${className}`)}:${pseudoClass}` }) }) } @@ -18,7 +19,7 @@ const defaultVariantGenerators = { default: generateVariantFunction(() => {}), 'group-hover': generateVariantFunction(({ modifySelectors, separator }) => { return modifySelectors(({ className }) => { - return `.group:hover .group-hover${separator}${className}` + return `.group:hover .${e(`group-hover${separator}${className}`)}` }) }), hover: generatePseudoClassVariant('hover'), diff --git a/src/util/generateVariantFunction.js b/src/util/generateVariantFunction.js index 616935894..b4db56559 100644 --- a/src/util/generateVariantFunction.js +++ b/src/util/generateVariantFunction.js @@ -1,6 +1,6 @@ import _ from 'lodash' import postcss from 'postcss' -import escapeClassName from './escapeClassName' +import selectorParser from 'postcss-selector-parser' export default function generateVariantFunction(generator) { return (container, config) => { @@ -10,15 +10,19 @@ export default function generateVariantFunction(generator) { _.defaultTo( generator({ container: cloned, - separator: escapeClassName(config.separator), + separator: config.separator, modifySelectors: modifierFunction => { cloned.walkRules(rule => { - rule.selectors = rule.selectors.map(selector => - modifierFunction({ - className: selector.slice(1), + rule.selectors = rule.selectors.map(selector => { + const className = selectorParser(selectors => { + return selectors.first.filter(({ type }) => type === 'class').pop().value + }).transformSync(selector) + + return modifierFunction({ + className, selector, }) - ) + }) }) return cloned },