diff --git a/CHANGELOG.md b/CHANGELOG.md index f9e14df7e014..9bcf4d379b3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Don’t prefix selectors in arbitrary variants ([#8773](https://github.com/tailwindlabs/tailwindcss/pull/8773)) - Add support for alpha values in safe list ([#8774](https://github.com/tailwindlabs/tailwindcss/pull/8774)) - Support default `font-weight`s in font size utilities ([#8763](https://github.com/tailwindlabs/tailwindcss/pull/8763)) +- Add more explicit types for the default theme ([#8780](https://github.com/tailwindlabs/tailwindcss/pull/8780)) ## [3.1.4] - 2022-06-21 diff --git a/defaultTheme.d.ts b/defaultTheme.d.ts index 9172051233cd..2bc9dc718b0c 100644 --- a/defaultTheme.d.ts +++ b/defaultTheme.d.ts @@ -1,3 +1,4 @@ import type { Config } from './types/config' -declare const theme: Config['theme'] +import { DefaultTheme } from './types/generated/default-theme' +declare const theme: Config['theme'] & DefaultTheme export = theme diff --git a/scripts/generate-types.js b/scripts/generate-types.js index ef45233360f6..d3e0d303eefc 100644 --- a/scripts/generate-types.js +++ b/scripts/generate-types.js @@ -1,8 +1,10 @@ import prettier from 'prettier' import { corePlugins } from '../src/corePlugins' import colors from '../src/public/colors' +import defaultTheme from '../src/public/default-theme' import fs from 'fs' import path from 'path' +import * as types from './type-utils' fs.writeFileSync( path.join(process.cwd(), 'types', 'generated', 'corePluginList.d.ts'), @@ -50,3 +52,54 @@ fs.writeFileSync( } ) ) + +const defaultThemeTypes = Object.entries(defaultTheme) + .map(([name, value]) => { + // Special cases for slightly more accurate types + if (name === 'keyframes') { + return [name, `Record<${types.forKeys(value)}, Record>`] + } + + if (name === 'fontSize') { + return [name, `Record<${types.forKeys(value)}, [string, { lineHeight: string }]>`] + } + + // General cases + if (typeof value === 'string') { + return [name, `string`] + } + + if (typeof value === 'function') { + return [name, null] + } + + if (typeof value === 'object') { + if (Object.keys(value).length === 0) { + return [name, null] + } + + return [name, types.forValue(value)] + } + + return [name, `unknown`] + }) + .filter(([, type]) => type !== null) + .map(([name, type]) => `${name}: ${type}`) + .join('\n') + +fs.writeFileSync( + path.join(process.cwd(), 'types', 'generated', 'default-theme.d.ts'), + prettier.format( + ` + import { Config } from '../../types' + type CSSDeclarationList = Record + export type DefaultTheme = Config['theme'] & { ${defaultThemeTypes} } + `, + { + semi: false, + singleQuote: true, + printWidth: 100, + parser: 'typescript', + } + ) +) diff --git a/scripts/type-utils.js b/scripts/type-utils.js new file mode 100644 index 000000000000..d9d0e6133c76 --- /dev/null +++ b/scripts/type-utils.js @@ -0,0 +1,27 @@ +export function union(types) { + return [...new Set(types)].join(' | ') +} + +export function unionValues(values) { + return union(values.map(forValue)) +} + +export function forKeys(value) { + return union(Object.keys(value).map((key) => `'${key}'`)) +} + +export function forValue(value) { + if (Array.isArray(value)) { + return `(${unionValues(value)})[]` + } + + if (typeof value === 'object') { + return `Record<${forKeys(value)}, ${unionValues(Object.values(value))}>` + } + + if (typeof value === 'string') { + return `string` + } + + return `any` +}