Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix template migration issues #14600

Merged
merged 6 commits into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Don’t crash when scanning a candidate equal to the configured prefix ([#14588](https://github.com/tailwindlabs/tailwindcss/pull/14588))
- Ensure there's always a space before `!important` when stringifying CSS ([#14611](https://github.com/tailwindlabs/tailwindcss/pull/14611))
- _Experimental_: Ensure CSS before a layer stays unlayered when running codemods ([#14596](https://github.com/tailwindlabs/tailwindcss/pull/14596))
- _Upgrade (experimental)_: Ensure CSS before a layer stays unlayered when running codemods ([#14596](https://github.com/tailwindlabs/tailwindcss/pull/14596))
- _Upgrade (experimental)_: Resolve issues where some prefixed candidates were not properly migrated ([#14600](https://github.com/tailwindlabs/tailwindcss/pull/14600))

## [4.0.0-alpha.26] - 2024-10-03

Expand Down
29 changes: 22 additions & 7 deletions crates/oxide/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -861,13 +861,17 @@ impl<'a> Extractor<'a> {
ParseAction::SingleCandidate(candidate)
}
Bracketing::Included(sliceable) | Bracketing::Wrapped(sliceable) => {
let parts = vec![candidate, sliceable];
let parts = parts
.into_iter()
.filter(|v| !v.is_empty())
.collect::<Vec<_>>();
if candidate == sliceable {
ParseAction::SingleCandidate(candidate)
} else {
let parts = vec![candidate, sliceable];
let parts = parts
.into_iter()
.filter(|v| !v.is_empty())
.collect::<Vec<_>>();

ParseAction::MultipleCandidates(parts)
ParseAction::MultipleCandidates(parts)
}
}
}
}
Expand Down Expand Up @@ -1185,7 +1189,7 @@ mod test {
fn bad_003() {
// TODO: This seems… wrong
let candidates = run(r"[𕤵:]", false);
assert_eq!(candidates, vec!["𕤵", "𕤵:"]);
assert_eq!(candidates, vec!["𕤵", "𕤵:",]);
}

#[test]
Expand Down Expand Up @@ -1436,4 +1440,15 @@ mod test {
.unwrap();
assert_eq!(result, Some("[.foo_&]:px-[0]"));
}

#[test]
fn does_not_emit_the_same_slice_multiple_times() {
let candidates: Vec<_> =
Extractor::with_positions("<div class=\"flex\"></div>".as_bytes(), Default::default())
.into_iter()
.map(|(s, p)| unsafe { (std::str::from_utf8_unchecked(s), p) })
.collect();

assert_eq!(candidates, vec![("div", 1), ("class", 5), ("flex", 12),]);
}
}
88 changes: 88 additions & 0 deletions integrations/upgrade/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,91 @@ test(
)
},
)

test(
`migrates prefixes even if other files have unprefixed versions of the candidate`,
{
fs: {
'package.json': json`
{
"dependencies": {
"@tailwindcss/upgrade": "workspace:^"
}
}
`,
'tailwind.config.js': js`
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{html,js}'],
prefix: 'tw__',
}
`,
'src/index.html': html`
<div class="flex"></div>
`,
'src/other.html': html`
<div class="tw__flex"></div>
`,
'src/input.css': css`
@tailwind base;
@tailwind components;
@tailwind utilities;
`,
},
},
async ({ exec, fs }) => {
await exec('npx @tailwindcss/upgrade -c tailwind.config.js')

await fs.expectFileToContain('src/index.html', html`
<div class="flex"></div>
`)
await fs.expectFileToContain('src/other.html', html`
<div class="tw:flex"></div>
`)
},
)

test(
`prefixed variants do not cause their unprefixed counterparts to be valid`,
{
fs: {
'package.json': json`
{
"dependencies": {
"@tailwindcss/upgrade": "workspace:^"
}
}
`,
'tailwind.config.js': js`
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/**/*.{html,js}'],
prefix: 'tw__',
}
`,
'src/index.html': html`
<div class="tw__bg-gradient-to-t"></div>
`,
'src/other.html': html`
<div class="bg-gradient-to-t"></div>
`,
},
},
async ({ exec, fs }) => {
await exec('npx @tailwindcss/upgrade -c tailwind.config.js')

await fs.expectFileToContain(
'src/index.html',
html`
<div class="tw:bg-linear-to-t"></div>
`,
)

await fs.expectFileToContain(
'src/other.html',
html`
<div class="bg-gradient-to-t"></div>
`,
)
},
)
2 changes: 1 addition & 1 deletion integrations/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export function test(
) {
return (only || (!process.env.CI && debug) ? defaultTest.only : defaultTest)(
name,
{ timeout: TEST_TIMEOUT, retry: 3 },
{ timeout: TEST_TIMEOUT, retry: debug ? 0 : 3 },
async (options) => {
let rootDir = debug ? path.join(REPO_ROOT, '.debug') : TMP_ROOT
await fs.mkdir(rootDir, { recursive: true })
Expand Down
4 changes: 2 additions & 2 deletions packages/@tailwindcss-upgrade/src/template/candidates.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ test('extracts candidates with positions from a template', async () => {
base: __dirname,
})

let candidates = await extractRawCandidates(content)
let candidates = await extractRawCandidates(content, 'html')
let validCandidates = candidates.filter(
({ rawCandidate }) => designSystem.parseCandidate(rawCandidate).length > 0,
)
Expand Down Expand Up @@ -60,7 +60,7 @@ test('replaces the right positions for a candidate', async () => {
base: __dirname,
})

let candidates = await extractRawCandidates(content)
let candidates = await extractRawCandidates(content, 'html')

let candidate = candidates.find(
({ rawCandidate }) => designSystem.parseCandidate(rawCandidate).length > 0,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Config } from 'tailwindcss'
import { walk, WalkAction } from '../../../../tailwindcss/src/ast'
import type { Candidate, Variant } from '../../../../tailwindcss/src/candidate'
import { type Candidate, type Variant } from '../../../../tailwindcss/src/candidate'
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
import { printCandidate } from '../candidates'

Expand All @@ -9,7 +9,11 @@ export function automaticVarInjection(
_userConfig: Config,
rawCandidate: string,
): string {
for (let candidate of designSystem.parseCandidate(rawCandidate)) {
for (let readonlyCandidate of designSystem.parseCandidate(rawCandidate)) {
// The below logic makes extended use of mutation. Since candidates in the
// DesignSystem are cached, we can't mutate them directly.
let candidate = structuredClone(readonlyCandidate) as Candidate

let didChange = false

// Add `var(…)` in modifier position, e.g.:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ export function bgGradient(
continue
}

candidate.root = `bg-linear-to-${direction}`
return printCandidate(designSystem, candidate)
return printCandidate(designSystem, {
...candidate,
root: `bg-linear-to-${direction}`,
})
}
}
return rawCandidate
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Config } from 'tailwindcss'
import { parseCandidate } from '../../../../tailwindcss/src/candidate'
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
import { printCandidate } from '../candidates'

Expand All @@ -19,7 +20,7 @@ export function important(
_userConfig: Config,
rawCandidate: string,
): string {
for (let candidate of designSystem.parseCandidate(rawCandidate)) {
for (let candidate of parseCandidate(rawCandidate, designSystem)) {
if (candidate.important && candidate.raw[candidate.raw.length - 1] !== '!') {
// The printCandidate function will already put the exclamation mark in
// the right place, so we just need to mark this candidate as requiring a
Expand Down
7 changes: 5 additions & 2 deletions packages/@tailwindcss-upgrade/src/template/codemods/prefix.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Config } from 'tailwindcss'
import type { Candidate } from '../../../../tailwindcss/src/candidate'
import { parseCandidate, type Candidate } from '../../../../tailwindcss/src/candidate'
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
import { segment } from '../../../../tailwindcss/src/utils/segment'
import { printCandidate } from '../candidates'
Expand All @@ -24,7 +24,10 @@ export function prefix(
let unprefixedCandidate =
rawCandidate.slice(0, v3Base.start) + v3Base.base + rawCandidate.slice(v3Base.end)

let candidates = designSystem.parseCandidate(unprefixedCandidate)
// Note: This is not a valid candidate in the original DesignSystem, so we
// can not use the `DesignSystem#parseCandidate` API here or otherwise this
// invalid candidate will be cached.
let candidates = [...parseCandidate(unprefixedCandidate, designSystem)]
if (candidates.length > 0) {
candidate = candidates[0]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Config } from 'tailwindcss'
import { walk, type AstNode } from '../../../../tailwindcss/src/ast'
import type { Variant } from '../../../../tailwindcss/src/candidate'
import { type Variant } from '../../../../tailwindcss/src/candidate'
import type { DesignSystem } from '../../../../tailwindcss/src/design-system'
import { printCandidate } from '../candidates'

Expand Down
10 changes: 7 additions & 3 deletions packages/@tailwindcss-upgrade/src/template/migrate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import fs from 'node:fs/promises'
import path from 'node:path'
import path, { extname } from 'node:path'
import type { Config } from 'tailwindcss'
import type { DesignSystem } from '../../../tailwindcss/src/design-system'
import { extractRawCandidates, replaceCandidateInContent } from './candidates'
Expand Down Expand Up @@ -38,8 +38,9 @@ export default async function migrateContents(
designSystem: DesignSystem,
userConfig: Config,
contents: string,
extension: string,
): Promise<string> {
let candidates = await extractRawCandidates(contents)
let candidates = await extractRawCandidates(contents, extension)

// Sort candidates by starting position desc
candidates.sort((a, z) => z.start - a.start)
Expand All @@ -60,5 +61,8 @@ export async function migrate(designSystem: DesignSystem, userConfig: Config, fi
let fullPath = path.resolve(process.cwd(), file)
let contents = await fs.readFile(fullPath, 'utf-8')

await fs.writeFile(fullPath, await migrateContents(designSystem, userConfig, contents))
await fs.writeFile(
fullPath,
await migrateContents(designSystem, userConfig, contents, extname(file)),
)
}
4 changes: 2 additions & 2 deletions packages/tailwindcss/src/design-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ export type DesignSystem = {
getClassList(): ClassEntry[]
getVariants(): VariantEntry[]

parseCandidate(candidate: string): Candidate[]
parseVariant(variant: string): Variant | null
parseCandidate(candidate: string): Readonly<Candidate>[]
parseVariant(variant: string): Readonly<Variant> | null
compileAstNodes(candidate: Candidate): ReturnType<typeof compileAstNodes>

getVariantOrder(): Map<Variant, number>
Expand Down