Skip to content

Commit

Permalink
Only migrate the config file if all top-level theme keys are allowlisted
Browse files Browse the repository at this point in the history
  • Loading branch information
philipp-spiess committed Oct 14, 2024
1 parent a2ff8cc commit 966addb
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 21 deletions.
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
}

0 comments on commit 966addb

Please sign in to comment.