Skip to content

Commit

Permalink
Handle group/peer variants with quoted strings (#10400)
Browse files Browse the repository at this point in the history
* Handle group/peer variants with quoted strings

* Fix CS

* Use `splitAtTopLevelOnly` instead

This solution isn’t that pretty but it is reusing existing machinery

* inline return

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>

* Fix return type

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>

* Fixup

* Update changelog

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
  • Loading branch information
thecrypticace and RobinMalfait committed Jan 23, 2023
1 parent 667eac5 commit f821c71
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 5 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Consider earlier variants before sorting functions ([#10288](https://github.com/tailwindlabs/tailwindcss/pull/10288))
- Allow variants with slashes ([#10336](https://github.com/tailwindlabs/tailwindcss/pull/10336))
- Ensure generated CSS is always sorted in the same order for a given set of templates ([#10382](https://github.com/tailwindlabs/tailwindcss/pull/10382))
- Handle variants when the same class appears multiple times in a selector ([#10397](https://github.com/tailwindlabs/tailwindcss/pull/10397))
- Handle group/peer variants with quoted strings ([#10400](https://github.com/tailwindlabs/tailwindcss/pull/10400))

### Changed

Expand Down
25 changes: 24 additions & 1 deletion src/corePlugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,30 @@ export let variantPlugins = {
if (!result.includes('&')) result = '&' + result

let [a, b] = fn('', extra)
return result.replace(/&(\S+)?/g, (_, pseudo = '') => a + pseudo + b)

let start = null
let end = null
let quotes = 0

for (let i = 0; i < result.length; ++i) {
let c = result[i]
if (c === '&') {
start = i
} else if (c === "'" || c === '"') {
quotes += 1
} else if (start !== null && c === ' ' && !quotes) {
end = i
}
}

if (start !== null && end === null) {
end = result.length
}

// Basically this but can handle quotes:
// result.replace(/&(\S+)?/g, (_, pseudo = '') => a + pseudo + b)

return result.slice(0, start) + a + result.slice(start + 1, end) + b + result.slice(end)
},
{ values: Object.fromEntries(pseudoVariants) }
)
Expand Down
16 changes: 12 additions & 4 deletions src/lib/generateRules.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,18 @@ function applyVariant(variant, matches, context) {

// Retrieve "modifier"
{
let match = /(.*)\/(.*)$/g.exec(variant)
if (match && !context.variantMap.has(variant)) {
variant = match[1]
args.modifier = match[2]
let [baseVariant, ...modifiers] = splitAtTopLevelOnly(variant, '/')

// This is a hack to support variants with `/` in them, like `ar-1/10/20:text-red-500`
// In this case 1/10 is a value but /20 is a modifier
if (modifiers.length > 1) {
baseVariant = baseVariant + '/' + modifiers.slice(0, -1).join('/')
modifiers = modifiers.slice(-1)
}

if (modifiers.length && !context.variantMap.has(variant)) {
variant = baseVariant
args.modifier = modifiers[0]

if (!flagEnabled(context.tailwindConfig, 'generalizedModifiers')) {
return []
Expand Down
62 changes: 62 additions & 0 deletions tests/basic-usage.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -949,4 +949,66 @@ crosscheck(({ stable, oxide }) => {
`)
})
})

test('detects quoted arbitrary values containing a slash', async () => {
let config = {
content: [
{
raw: html`<div class="group-[[href^='/']]:hidden"></div>`,
},
],
}

let input = css`
@tailwind utilities;
`

let result = await run(input, config)

oxide.expect(result.css).toMatchFormattedCss(css`
.group[href^='/'] .group-\[\[href\^\=\'\/\'\]\]\:hidden {
display: none;
}
`)

stable.expect(result.css).toMatchFormattedCss(css`
.hidden {
display: none;
}
.group[href^='/'] .group-\[\[href\^\=\'\/\'\]\]\:hidden {
display: none;
}
`)
})

test('handled quoted arbitrary values containing escaped spaces', async () => {
let config = {
content: [
{
raw: html`<div class="group-[[href^='_bar']]:hidden"></div>`,
},
],
}

let input = css`
@tailwind utilities;
`

let result = await run(input, config)

oxide.expect(result.css).toMatchFormattedCss(css`
.group[href^=' bar'] .group-\[\[href\^\=\'_bar\'\]\]\:hidden {
display: none;
}
`)

stable.expect(result.css).toMatchFormattedCss(css`
.hidden {
display: none;
}
.group[href^=' bar'] .group-\[\[href\^\=\'_bar\'\]\]\:hidden {
display: none;
}
`)
})
})
12 changes: 12 additions & 0 deletions tests/util/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ let nullProxy = new Proxy(
}
)

/**
* @typedef {object} CrossCheck
* @property {typeof import('@jest/globals')} oxide
* @property {typeof import('@jest/globals')} stable
* @property {object} engine
* @property {boolean} engine.oxide
* @property {boolean} engine.stable
*/

/**
* @param {(data: CrossCheck) => void} fn
*/
export function crosscheck(fn) {
let engines =
env.ENGINE === 'oxide' ? [{ engine: 'Stable' }, { engine: 'Oxide' }] : [{ engine: 'Stable' }]
Expand Down

0 comments on commit f821c71

Please sign in to comment.