Skip to content

Commit a158453

Browse files
show object keys that result in failing JS migration
Before this, we would just show "Cannot migrate"-like error messages. But this will show a bit more detail about which theme keys are the culprit. Co-Authored-By: Jordan Pittman <[email protected]>
1 parent fea40a5 commit a158453

File tree

1 file changed

+58
-35
lines changed

1 file changed

+58
-35
lines changed

packages/@tailwindcss-upgrade/src/codemods/config/migrate-js-config.ts

Lines changed: 58 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
isValidSpacingMultiplier,
2626
} from '../../../../tailwindcss/src/utils/infer-data-type'
2727
import { findStaticPlugins, type StaticPluginOptions } from '../../utils/extract-static-plugins'
28-
import { highlight, info, relative } from '../../utils/renderer'
28+
import { highlight, info, relative, warn } from '../../utils/renderer'
2929

3030
const __filename = fileURLToPath(import.meta.url)
3131
const __dirname = path.dirname(__filename)
@@ -48,11 +48,15 @@ export async function migrateJsConfig(
4848
fs.readFile(fullConfigPath, 'utf-8'),
4949
])
5050

51-
if (!canMigrateConfig(unresolvedConfig, source)) {
51+
let canMigrateConfigResult = canMigrateConfig(unresolvedConfig, source)
52+
if (!canMigrateConfigResult.valid) {
5253
info(
5354
`The configuration file at ${highlight(relative(fullConfigPath, base))} could not be automatically migrated to the new CSS configuration format, so your CSS has been updated to load your existing configuration file.`,
5455
{ prefix: '↳ ' },
5556
)
57+
for (let msg of canMigrateConfigResult.errors) {
58+
warn(msg, { prefix: ' ↳ ' })
59+
}
5660
return null
5761
}
5862

@@ -299,22 +303,34 @@ async function migrateContent(
299303
return sources
300304
}
301305

302-
// Applies heuristics to determine if we can attempt to migrate the config
303-
function canMigrateConfig(unresolvedConfig: Config, source: string): boolean {
304-
// The file may not contain non-serializable values
305-
function isSimpleValue(value: unknown): boolean {
306-
if (typeof value === 'function') return false
307-
if (Array.isArray(value)) return value.every(isSimpleValue)
308-
if (typeof value === 'object' && value !== null) {
309-
return Object.values(value).every(isSimpleValue)
306+
const JS_IDENTIFIER_REGEX = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/
307+
function stringifyPath(path: (string | number)[]): string {
308+
let result = ''
309+
310+
for (let segment of path) {
311+
if (typeof segment === 'number') {
312+
result += `[${segment}]`
313+
} else if (!JS_IDENTIFIER_REGEX.test(segment)) {
314+
result += `[\`${segment}\`]`
315+
} else {
316+
result += result.length > 0 ? `.${segment}` : segment
310317
}
311-
return ['string', 'number', 'boolean', 'undefined'].includes(typeof value)
312318
}
313319

314-
// `theme` and `plugins` are handled separately and allowed to be more complex
315-
let { plugins, theme, ...remainder } = unresolvedConfig
316-
if (!isSimpleValue(remainder)) {
317-
return false
320+
return result
321+
}
322+
323+
// Applies heuristics to determine if we can attempt to migrate the config
324+
function canMigrateConfig(
325+
unresolvedConfig: Config,
326+
source: string,
327+
): { valid: true } | { valid: false; errors: string[] } {
328+
let theme = unresolvedConfig.theme
329+
let errors: string[] = []
330+
331+
// Migrating presets are not supported
332+
if (unresolvedConfig.presets && unresolvedConfig.presets.length > 0) {
333+
errors.push('Cannot migrate config files that use presets')
318334
}
319335

320336
// The file may only contain known-migratable top-level properties
@@ -328,27 +344,28 @@ function canMigrateConfig(unresolvedConfig: Config, source: string): boolean {
328344
'corePlugins',
329345
]
330346

331-
if (Object.keys(unresolvedConfig).some((key) => !knownProperties.includes(key))) {
332-
return false
333-
}
334-
335-
if (findStaticPlugins(source) === null) {
336-
return false
337-
}
338-
339-
if (unresolvedConfig.presets && unresolvedConfig.presets.length > 0) {
340-
return false
347+
for (let key of Object.keys(unresolvedConfig)) {
348+
if (!knownProperties.includes(key)) {
349+
errors.push(`Cannot migrate unknown top-level key: \`${key}\``)
350+
}
341351
}
342352

343353
// Only migrate the config file if all top-level theme keys are allowed to be
344354
// migrated
345355
if (theme && typeof theme === 'object') {
346-
if (theme.extend && !onlyAllowedThemeValues(theme.extend)) return false
347-
let { extend: _extend, ...themeCopy } = theme
348-
if (!onlyAllowedThemeValues(themeCopy)) return false
356+
let { extend, ...themeCopy } = theme
357+
errors.push(...onlyAllowedThemeValues(themeCopy, ['theme']))
358+
359+
if (extend) {
360+
errors.push(...onlyAllowedThemeValues(extend, ['theme', 'extend']))
361+
}
349362
}
350363

351-
return true
364+
// TODO: findStaticPlugins already logs errors for unsupported plugins, maybe
365+
// it should return them instead?
366+
findStaticPlugins(source)
367+
368+
return errors.length <= 0 ? { valid: true } : { valid: false, errors }
352369
}
353370

354371
const ALLOWED_THEME_KEYS = [
@@ -357,24 +374,30 @@ const ALLOWED_THEME_KEYS = [
357374
'containers',
358375
]
359376
const BLOCKED_THEME_KEYS = ['supports', 'data', 'aria']
360-
function onlyAllowedThemeValues(theme: ThemeConfig): boolean {
377+
function onlyAllowedThemeValues(theme: ThemeConfig, path: (string | number)[]): string[] {
378+
let errors: string[] = []
379+
361380
for (let key of Object.keys(theme)) {
362381
if (!ALLOWED_THEME_KEYS.includes(key)) {
363-
return false
382+
errors.push(`Cannot migrate theme key: \`${stringifyPath([...path, key])}\``)
364383
}
384+
365385
if (BLOCKED_THEME_KEYS.includes(key)) {
366-
return false
386+
errors.push(`Cannot migrate theme key: \`${stringifyPath([...path, key])}\``)
367387
}
368388
}
369389

370390
if ('screens' in theme && typeof theme.screens === 'object' && theme.screens !== null) {
371-
for (let screen of Object.values(theme.screens)) {
391+
for (let [name, screen] of Object.entries(theme.screens)) {
372392
if (typeof screen === 'object' && screen !== null && ('max' in screen || 'raw' in screen)) {
373-
return false
393+
errors.push(
394+
`Cannot migrate complex screen definition: \`${stringifyPath([...path, 'screens', name])}\``,
395+
)
374396
}
375397
}
376398
}
377-
return true
399+
400+
return errors
378401
}
379402

380403
function keyframesToCss(keyframes: Record<string, unknown>): string {

0 commit comments

Comments
 (0)