Skip to content

Commit

Permalink
Upgrade: Ensure underscores in url() and var() are not escaped
Browse files Browse the repository at this point in the history
  • Loading branch information
philipp-spiess committed Oct 24, 2024
1 parent 1b1d7b9 commit 48526c7
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 6 deletions.
27 changes: 23 additions & 4 deletions integrations/upgrade/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { expect } from 'vitest'
import { css, html, js, json, test } from '../utils'
import { candidate, css, html, js, json, test } from '../utils'

test(
test.only(
`upgrades a v3 project to v4`,
{
fs: {
'package.json': json`
{
"dependencies": {
"@tailwindcss/upgrade": "workspace:^"
},
"devDependencies": {
"@tailwindcss/cli": "workspace:^"
}
}
`,
Expand All @@ -20,7 +23,9 @@ test(
`,
'src/index.html': html`
<h1>🤠👋</h1>
<div class="!flex sm:!block bg-gradient-to-t bg-[--my-red] max-w-screen-md"></div>
<div
class="!flex sm:!block bg-gradient-to-t bg-[--my-red] max-w-screen-md ml-[theme(spacing[1.5])]"
></div>
`,
'src/input.css': css`
@tailwind base;
Expand All @@ -42,7 +47,9 @@ test(
"
--- ./src/index.html ---
<h1>🤠👋</h1>
<div class="flex! sm:block! bg-linear-to-t bg-[var(--my-red)] max-w-[var(--breakpoint-md)]"></div>
<div
class="flex! sm:block! bg-linear-to-t bg-[var(--my-red)] max-w-[var(--breakpoint-md)] ml-[var(--spacing-1_5)]"
></div>
--- ./src/input.css ---
@import 'tailwindcss';
Expand Down Expand Up @@ -92,6 +99,18 @@ test(
expect(packageJson.dependencies).toMatchObject({
tailwindcss: expect.stringContaining('4.0.0'),
})

// Ensure the v4 project compiles correctly
await exec('npx tailwindcss --input src/input.css --output dist/out.css')

await fs.expectFileToContain('dist/out.css', [
candidate`flex!`,
candidate`sm:block!`,
candidate`bg-linear-to-t`,
candidate`bg-[var(--my-red)]`,
candidate`max-w-[var(--breakpoint-md)]`,
candidate`ml-[var(--spacing-1\_5)`,
])
},
)

Expand Down
7 changes: 7 additions & 0 deletions packages/@tailwindcss-upgrade/src/template/candidates.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,13 @@ const candidates = [
// Keep spaces in strings
['content-["hello_world"]', 'content-["hello_world"]'],
['content-[____"hello_world"___]', 'content-["hello_world"]'],

// Do not escape underscores for url() and CSS variable in var()
['bg-[no-repeat_url(/image_13.png)]', 'bg-[no-repeat_url(/image_13.png)]'],
[
'bg-[var(--spacing-0_5,_var(--spacing-1_5,_3rem))]',
'bg-[var(--spacing-0_5,_var(--spacing-1_5,_3rem))]',
],
]

const variants = [
Expand Down
52 changes: 50 additions & 2 deletions packages/@tailwindcss-upgrade/src/template/candidates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,9 @@ function printArbitraryValue(input: string) {
})
}

recursivelyEscapeUnderscores(ast)

return ValueParser.toCss(ast)
.replaceAll('_', String.raw`\_`) // Escape underscores to keep them as-is
.replaceAll(' ', '_') // Replace spaces with underscores
}

function simplifyArbitraryVariant(input: string) {
Expand All @@ -213,3 +213,51 @@ function simplifyArbitraryVariant(input: string) {

return input
}

function recursivelyEscapeUnderscores(ast: ValueParser.ValueAstNode[]) {
for (let node of ast) {
switch (node.kind) {
case 'function': {
if (node.value === 'url' || node.value.endsWith('_url')) {
// Don't decode underscores in url() but do decode the function name
node.value = escapeUnderscore(node.value)
break
}

if (node.value === 'var' || node.value.endsWith('_var')) {
// Don't decode underscores in the first argument of var() but do
// decode the function name
node.value = escapeUnderscore(node.value)
for (let i = 0; i < node.nodes.length; i++) {
if (i == 0 && node.nodes[i].kind === 'word') {
continue
}
recursivelyEscapeUnderscores([node.nodes[i]])
}
break
}

node.value = escapeUnderscore(node.value)
recursivelyEscapeUnderscores(node.nodes)
break
}
case 'separator':
case 'word': {
node.value = escapeUnderscore(node.value)
break
}
default:
never()
}
}
}

function never(): never {
throw new Error('This should never happen')
}

function escapeUnderscore(value: string): string {
return value
.replaceAll('_', String.raw`\_`) // Escape underscores to keep them as-is
.replaceAll(' ', '_') // Replace spaces with underscores
}

0 comments on commit 48526c7

Please sign in to comment.