Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Only migrate the config file if all top-level theme keys are allowed
  • Loading branch information
philipp-spiess committed Oct 14, 2024
commit 7daed84bd8c4913b388cd8676a55dadaaa36f504
2 changes: 1 addition & 1 deletion integrations/upgrade/js-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ test(
)

test(
'does not upgrade JS config files with deeply nested objects in the theme config',
'does not upgrade JS config files with theme keys contributed to by plugins in the theme config',
{
fs: {
'package.json': json`
Expand Down
35 changes: 15 additions & 20 deletions packages/@tailwindcss-upgrade/src/migrate-js-config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import fs from 'node:fs/promises'
import { dirname } from 'path'
import type { Config } from 'tailwindcss'
import defaultTheme from 'tailwindcss/defaultTheme'
import { fileURLToPath } from 'url'
import { loadModule } from '../../@tailwindcss-node/src/compile'
import {
Expand All @@ -9,6 +10,7 @@ import {
} from '../../tailwindcss/src/compat/apply-config-to-theme'
import { deepMerge } from '../../tailwindcss/src/compat/config/deep-merge'
import { mergeThemeExtension } from '../../tailwindcss/src/compat/config/resolve-config'
import type { ThemeConfig } from '../../tailwindcss/src/compat/config/types'
import { darkModePlugin } from '../../tailwindcss/src/compat/dark-mode'
import { info } from './utils/renderer'

Expand Down Expand Up @@ -192,35 +194,28 @@ function canMigrateConfig(unresolvedConfig: Config, source: string): boolean {
return false
}

// The file may not contain deeply nested objects in the theme
// Only migrate the config file if all top-level theme keys are allowed to be
// migrated
let theme = unresolvedConfig.theme
if (theme && typeof theme === 'object') {
if (theme.extend && isTooNested(theme.extend, 4)) return false
if (theme.extend && !onlyUsesAllowedTopLevelKeys(theme.extend)) return false
let { extend: _extend, ...themeCopy } = theme
if (isTooNested(themeCopy, 4)) return false
if (!onlyUsesAllowedTopLevelKeys(themeCopy)) return false
}

return true
}

// The file may not contain deeply nested objects in the theme
function isTooNested(value: any, maxDepth: number): boolean {
if (maxDepth === 0) return true

if (!value) return false

if (Array.isArray(value)) {
// This is a tuple value so its fine
if (value.length === 2 && typeof value[0] === 'string' && typeof value[1] === 'object') {
const DEFAULT_THEME_KEYS = [
...Object.keys(defaultTheme),
// Used by @tailwindcss/container-queries
'containers',
]
function onlyUsesAllowedTopLevelKeys(theme: ThemeConfig): boolean {
for (let key of Object.keys(theme)) {
if (!DEFAULT_THEME_KEYS.includes(key)) {
return false
}

return value.some((v) => isTooNested(v, maxDepth - 1))
}

if (typeof value === 'object') {
return Object.values(value).some((v) => isTooNested(v, maxDepth - 1))
}

return false
return true
}