Skip to content

Commit efa812f

Browse files
Upgrade: Redce number of false-positive migrations of the important modifier
1 parent 5ce37c4 commit efa812f

File tree

5 files changed

+85
-4
lines changed

5 files changed

+85
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1818
- _Upgrade (experimental)_: Minify arbitrary values when printing candidates ([#14720](https://github.com/tailwindlabs/tailwindcss/pull/14720))
1919
- _Upgrade (experimental)_: Ensure legacy theme values ending in `1` (like `theme(spacing.1)`) are correctly migrated to custom properties ([#14724](https://github.com/tailwindlabs/tailwindcss/pull/14724))
2020
- _Upgrade (experimental)_: Migrate arbitrary values to bare values for the `from-*`, `via-*`, and `to-*` utilities ([#14725](https://github.com/tailwindlabs/tailwindcss/pull/14725))
21+
- _Upgrade (experimental)_: Reduce the number of false-positive migration of the important modifier (e.g. `!border` to `border!`) ([#14737](https://github.com/tailwindlabs/tailwindcss/pull/14737))
2122

2223
### Changed
2324

integrations/upgrade/js-config.test.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ test(
7272
@tailwind components;
7373
@tailwind utilities;
7474
`,
75+
'src/test.js': ts`
76+
export default {
77+
shouldNotUse: !border.shouldUse,
78+
}
79+
`,
7580
'node_modules/my-external-lib/src/template.html': html`
7681
<div class="text-red-500">
7782
Hello world!
@@ -82,7 +87,7 @@ test(
8287
async ({ exec, fs }) => {
8388
await exec('npx @tailwindcss/upgrade')
8489

85-
expect(await fs.dumpFiles('src/**/*.css')).toMatchInlineSnapshot(`
90+
expect(await fs.dumpFiles('src/**/*.{css,js}')).toMatchInlineSnapshot(`
8691
"
8792
--- src/input.css ---
8893
@import 'tailwindcss';
@@ -134,6 +139,11 @@ test(
134139
}
135140
}
136141
}
142+
143+
--- src/test.js ---
144+
export default {
145+
shouldNotUse: !border.shouldUse,
146+
}
137147
"
138148
`)
139149

packages/@tailwindcss-upgrade/src/template/codemods/important.test.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,25 @@ test.each([
1515
base: __dirname,
1616
})
1717

18-
expect(important(designSystem, {}, candidate)).toEqual(result)
18+
expect(
19+
important(designSystem, {}, candidate, {
20+
contents: `"${candidate}"`,
21+
start: 1,
22+
end: candidate.length + 1,
23+
}),
24+
).toEqual(result)
25+
})
26+
27+
test('does not match false positives', async () => {
28+
let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', {
29+
base: __dirname,
30+
})
31+
32+
expect(
33+
important(designSystem, {}, '!border', {
34+
contents: `let notBorder = !border\n`,
35+
start: 16,
36+
end: 16 + '!border'.length,
37+
}),
38+
).toEqual('!border')
1939
})

packages/@tailwindcss-upgrade/src/template/codemods/important.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,44 @@ export function important(
1919
designSystem: DesignSystem,
2020
_userConfig: Config,
2121
rawCandidate: string,
22+
location?: {
23+
contents: string
24+
start: number
25+
end: number
26+
},
2227
): string {
2328
for (let candidate of parseCandidate(rawCandidate, designSystem)) {
2429
if (candidate.important && candidate.raw[candidate.raw.length - 1] !== '!') {
30+
// The important migration is one of the most broad migrations with a high
31+
// potential of matching false positives since `!` is a valid character in
32+
// most programming languages. Since v4 is technically backward compatible
33+
// to v3 in that it can read `!` in the front of the utility too, we err
34+
// on the side of caution and only migrate candidates that we are certain
35+
// are inside of a string.
36+
if (location) {
37+
let charBefore = location.contents.at(location.start - 1)
38+
let charAfter = location.contents.at(location.end)
39+
40+
if (
41+
charBefore !== undefined &&
42+
charBefore !== '"' &&
43+
charBefore !== "'" &&
44+
charBefore !== '`' &&
45+
charBefore !== ' '
46+
) {
47+
continue
48+
}
49+
if (
50+
charAfter !== undefined &&
51+
charAfter !== '"' &&
52+
charAfter !== "'" &&
53+
charAfter !== '`' &&
54+
charAfter !== ' '
55+
) {
56+
continue
57+
}
58+
}
59+
2560
// The printCandidate function will already put the exclamation mark in
2661
// the right place, so we just need to mark this candidate as requiring a
2762
// migration.

packages/@tailwindcss-upgrade/src/template/migrate.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ export type Migration = (
1717
designSystem: DesignSystem,
1818
userConfig: Config,
1919
rawCandidate: string,
20+
location?: {
21+
contents: string
22+
start: number
23+
end: number
24+
},
2025
) => string
2126

2227
export const DEFAULT_MIGRATIONS: Migration[] = [
@@ -34,9 +39,15 @@ export function migrateCandidate(
3439
designSystem: DesignSystem,
3540
userConfig: Config,
3641
rawCandidate: string,
42+
// Location is only set when migrating a candidate from a source file
43+
location?: {
44+
contents: string
45+
start: number
46+
end: number
47+
},
3748
): string {
3849
for (let migration of DEFAULT_MIGRATIONS) {
39-
rawCandidate = migration(designSystem, userConfig, rawCandidate)
50+
rawCandidate = migration(designSystem, userConfig, rawCandidate, location)
4051
}
4152
return rawCandidate
4253
}
@@ -52,7 +63,11 @@ export default async function migrateContents(
5263
let changes: StringChange[] = []
5364

5465
for (let { rawCandidate, start, end } of candidates) {
55-
let migratedCandidate = migrateCandidate(designSystem, userConfig, rawCandidate)
66+
let migratedCandidate = migrateCandidate(designSystem, userConfig, rawCandidate, {
67+
contents,
68+
start,
69+
end,
70+
})
5671

5772
if (migratedCandidate === rawCandidate) {
5873
continue

0 commit comments

Comments
 (0)