@@ -25,7 +25,7 @@ import {
2525 isValidSpacingMultiplier ,
2626} from '../../../../tailwindcss/src/utils/infer-data-type'
2727import { 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
3030const __filename = fileURLToPath ( import . meta. url )
3131const __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 - z A - Z _ $ ] [ a - z A - Z 0 - 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
354371const ALLOWED_THEME_KEYS = [
@@ -357,24 +374,30 @@ const ALLOWED_THEME_KEYS = [
357374 'containers' ,
358375]
359376const 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
380403function keyframesToCss ( keyframes : Record < string , unknown > ) : string {
0 commit comments