Skip to content

Commit a3e5813

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 8bda1db commit a3e5813

File tree

1 file changed

+56
-34
lines changed

1 file changed

+56
-34
lines changed

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

Lines changed: 56 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import {
2626
} from '../../../../tailwindcss/src/utils/infer-data-type'
2727
import * as ValueParser from '../../../../tailwindcss/src/value-parser'
2828
import { findStaticPlugins, type StaticPluginOptions } from '../../utils/extract-static-plugins'
29-
import { highlight, info, relative } from '../../utils/renderer'
29+
import { highlight, info, relative, warn } from '../../utils/renderer'
3030

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

52-
if (!canMigrateConfig(unresolvedConfig, source)) {
52+
let canMigrateConfigResult = canMigrateConfig(unresolvedConfig, source)
53+
if (!canMigrateConfigResult.valid) {
5354
info(
5455
`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.`,
5556
{ prefix: '↳ ' },
5657
)
58+
for (let msg of canMigrateConfigResult.errors) {
59+
warn(msg, { prefix: ' ↳ ' })
60+
}
5761
return null
5862
}
5963

@@ -384,22 +388,34 @@ async function migrateContent(
384388
return sources
385389
}
386390

387-
// Applies heuristics to determine if we can attempt to migrate the config
388-
function canMigrateConfig(unresolvedConfig: Config, source: string): boolean {
389-
// The file may not contain non-serializable values
390-
function isSimpleValue(value: unknown): boolean {
391-
if (typeof value === 'function') return false
392-
if (Array.isArray(value)) return value.every(isSimpleValue)
393-
if (typeof value === 'object' && value !== null) {
394-
return Object.values(value).every(isSimpleValue)
391+
const JS_IDENTIFIER_REGEX = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/
392+
function stringifyPath(path: (string | number)[]): string {
393+
let result = ''
394+
395+
for (let segment of path) {
396+
if (typeof segment === 'number') {
397+
result += `[${segment}]`
398+
} else if (!JS_IDENTIFIER_REGEX.test(segment)) {
399+
result += `[\`${segment}\`]`
400+
} else {
401+
result += result.length > 0 ? `.${segment}` : segment
395402
}
396-
return ['string', 'number', 'boolean', 'undefined'].includes(typeof value)
397403
}
398404

399-
// `theme` and `plugins` are handled separately and allowed to be more complex
400-
let { plugins, theme, ...remainder } = unresolvedConfig
401-
if (!isSimpleValue(remainder)) {
402-
return false
405+
return result
406+
}
407+
408+
// Applies heuristics to determine if we can attempt to migrate the config
409+
function canMigrateConfig(
410+
unresolvedConfig: Config,
411+
source: string,
412+
): { valid: true } | { valid: false; errors: string[] } {
413+
let theme = unresolvedConfig.theme
414+
let errors: string[] = []
415+
416+
// Migrating presets are not supported
417+
if (unresolvedConfig.presets && unresolvedConfig.presets.length > 0) {
418+
errors.push('Cannot migrate config files that use presets')
403419
}
404420

405421
// The file may only contain known-migratable top-level properties
@@ -413,49 +429,55 @@ function canMigrateConfig(unresolvedConfig: Config, source: string): boolean {
413429
'corePlugins',
414430
]
415431

416-
if (Object.keys(unresolvedConfig).some((key) => !knownProperties.includes(key))) {
417-
return false
418-
}
419-
420-
if (findStaticPlugins(source) === null) {
421-
return false
422-
}
423-
424-
if (unresolvedConfig.presets && unresolvedConfig.presets.length > 0) {
425-
return false
432+
for (let key of Object.keys(unresolvedConfig)) {
433+
if (!knownProperties.includes(key)) {
434+
errors.push(`Cannot migrate unknown top-level key: \`${key}\``)
435+
}
426436
}
427437

428438
// Only migrate the config file if all top-level theme keys are allowed to be
429439
// migrated
430440
if (theme && typeof theme === 'object') {
431-
if (theme.extend && !onlyAllowedThemeValues(theme.extend)) return false
432-
let { extend: _extend, ...themeCopy } = theme
433-
if (!onlyAllowedThemeValues(themeCopy)) return false
441+
let { extend, ...themeCopy } = theme
442+
errors.push(...onlyAllowedThemeValues(themeCopy, ['theme']))
443+
444+
if (extend) {
445+
errors.push(...onlyAllowedThemeValues(extend, ['theme', 'extend']))
446+
}
434447
}
435448

436-
return true
449+
// TODO: findStaticPlugins already logs errors for unsupported plugins, maybe
450+
// it should return them instead?
451+
findStaticPlugins(source)
452+
453+
return errors.length <= 0 ? { valid: true } : { valid: false, errors }
437454
}
438455

439456
const ALLOWED_THEME_KEYS = [
440457
...Object.keys(defaultTheme),
441458
// Used by @tailwindcss/container-queries
442459
'containers',
443460
]
444-
function onlyAllowedThemeValues(theme: ThemeConfig): boolean {
461+
function onlyAllowedThemeValues(theme: ThemeConfig, path: (string | number)[]): string[] {
462+
let errors: string[] = []
463+
445464
for (let key of Object.keys(theme)) {
446465
if (!ALLOWED_THEME_KEYS.includes(key)) {
447-
return false
466+
errors.push(`Cannot migrate theme key: \`${stringifyPath([...path, key])}\``)
448467
}
449468
}
450469

451470
if ('screens' in theme && typeof theme.screens === 'object' && theme.screens !== null) {
452-
for (let screen of Object.values(theme.screens)) {
471+
for (let [name, screen] of Object.entries(theme.screens)) {
453472
if (typeof screen === 'object' && screen !== null && ('max' in screen || 'raw' in screen)) {
454-
return false
473+
errors.push(
474+
`Cannot migrate complex screen definition: \`${stringifyPath([...path, 'screens', name])}\``,
475+
)
455476
}
456477
}
457478
}
458-
return true
479+
480+
return errors
459481
}
460482

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

0 commit comments

Comments
 (0)