diff --git a/packages/@tailwindcss-upgrade/src/migrate-js-config.ts b/packages/@tailwindcss-upgrade/src/codemods/config/migrate-js-config.ts similarity index 91% rename from packages/@tailwindcss-upgrade/src/migrate-js-config.ts rename to packages/@tailwindcss-upgrade/src/codemods/config/migrate-js-config.ts index 46d19ef6c4a9..af2320aabf1c 100644 --- a/packages/@tailwindcss-upgrade/src/migrate-js-config.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/config/migrate-js-config.ts @@ -2,27 +2,30 @@ import { Scanner } from '@tailwindcss/oxide' import fs from 'node:fs/promises' import path from 'node:path' import { fileURLToPath } from 'node:url' -import { loadModule } from '../../@tailwindcss-node/src/compile' -import defaultTheme from '../../tailwindcss/dist/default-theme' -import { atRule, toCss, type AstNode } from '../../tailwindcss/src/ast' +import { loadModule } from '../../../../@tailwindcss-node/src/compile' +import defaultTheme from '../../../../tailwindcss/dist/default-theme' +import { atRule, toCss, type AstNode } from '../../../../tailwindcss/src/ast' import { keyPathToCssProperty, themeableValues, -} from '../../tailwindcss/src/compat/apply-config-to-theme' -import { keyframesToRules } from '../../tailwindcss/src/compat/apply-keyframes-to-theme' -import { resolveConfig, type ConfigFile } from '../../tailwindcss/src/compat/config/resolve-config' -import type { ResolvedConfig, ThemeConfig } from '../../tailwindcss/src/compat/config/types' -import { buildCustomContainerUtilityRules } from '../../tailwindcss/src/compat/container' -import { darkModePlugin } from '../../tailwindcss/src/compat/dark-mode' -import type { Config } from '../../tailwindcss/src/compat/plugin-api' -import type { DesignSystem } from '../../tailwindcss/src/design-system' -import { escape } from '../../tailwindcss/src/utils/escape' +} from '../../../../tailwindcss/src/compat/apply-config-to-theme' +import { keyframesToRules } from '../../../../tailwindcss/src/compat/apply-keyframes-to-theme' +import { + resolveConfig, + type ConfigFile, +} from '../../../../tailwindcss/src/compat/config/resolve-config' +import type { ResolvedConfig, ThemeConfig } from '../../../../tailwindcss/src/compat/config/types' +import { buildCustomContainerUtilityRules } from '../../../../tailwindcss/src/compat/container' +import { darkModePlugin } from '../../../../tailwindcss/src/compat/dark-mode' +import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' +import type { DesignSystem } from '../../../../tailwindcss/src/design-system' +import { escape } from '../../../../tailwindcss/src/utils/escape' import { isValidOpacityValue, isValidSpacingMultiplier, -} from '../../tailwindcss/src/utils/infer-data-type' -import { findStaticPlugins, type StaticPluginOptions } from './utils/extract-static-plugins' -import { highlight, info, relative } from './utils/renderer' +} from '../../../../tailwindcss/src/utils/infer-data-type' +import { findStaticPlugins, type StaticPluginOptions } from '../../utils/extract-static-plugins' +import { highlight, info, relative } from '../../utils/renderer' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) diff --git a/packages/@tailwindcss-upgrade/src/migrate-postcss.ts b/packages/@tailwindcss-upgrade/src/codemods/config/migrate-postcss.ts similarity index 98% rename from packages/@tailwindcss-upgrade/src/migrate-postcss.ts rename to packages/@tailwindcss-upgrade/src/codemods/config/migrate-postcss.ts index 0d49c2ad2e4f..057680b6e234 100644 --- a/packages/@tailwindcss-upgrade/src/migrate-postcss.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/config/migrate-postcss.ts @@ -1,7 +1,7 @@ import fs from 'node:fs/promises' import path from 'node:path' -import { pkg } from './utils/packages' -import { highlight, info, relative, success, warn } from './utils/renderer' +import { pkg } from '../../utils/packages' +import { highlight, info, relative, success, warn } from '../../utils/renderer' // Migrates simple PostCSS setups. This is to cover non-dynamic config files // similar to the ones we have all over our docs: diff --git a/packages/@tailwindcss-upgrade/src/migrate-prettier.ts b/packages/@tailwindcss-upgrade/src/codemods/config/migrate-prettier.ts similarity index 83% rename from packages/@tailwindcss-upgrade/src/migrate-prettier.ts rename to packages/@tailwindcss-upgrade/src/codemods/config/migrate-prettier.ts index cd56cc6880e1..980f35617da1 100644 --- a/packages/@tailwindcss-upgrade/src/migrate-prettier.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/config/migrate-prettier.ts @@ -1,7 +1,7 @@ import fs from 'node:fs/promises' import path from 'node:path' -import { pkg } from './utils/packages' -import { highlight, success } from './utils/renderer' +import { pkg } from '../../utils/packages' +import { highlight, success } from '../../utils/renderer' export async function migratePrettierPlugin(base: string) { let packageJsonPath = path.resolve(base, 'package.json') diff --git a/packages/@tailwindcss-upgrade/src/codemods/css/analyze.ts b/packages/@tailwindcss-upgrade/src/codemods/css/analyze.ts new file mode 100644 index 000000000000..c27f5c956346 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/css/analyze.ts @@ -0,0 +1,293 @@ +import { isGitIgnored } from 'globby' +import path from 'node:path' +import postcss, { type Result } from 'postcss' +import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map' +import { segment } from '../../../../tailwindcss/src/utils/segment' +import { Stylesheet, type StylesheetConnection } from '../../stylesheet' +import { error, highlight, relative } from '../../utils/renderer' +import { resolveCssId } from '../../utils/resolve' + +export async function analyze(stylesheets: Stylesheet[]) { + let isIgnored = await isGitIgnored() + let processingQueue: (() => Promise)[] = [] + let stylesheetsByFile = new DefaultMap((file) => { + // We don't want to process ignored files (like node_modules) + if (isIgnored(file)) { + return null + } + + try { + let sheet = Stylesheet.loadSync(file) + + // Mutate incoming stylesheets to include the newly discovered sheet + stylesheets.push(sheet) + + // Queue up the processing of this stylesheet + processingQueue.push(() => processor.process(sheet.root, { from: sheet.file! })) + + return sheet + } catch { + return null + } + }) + + // Step 1: Record which `@import` rules point to which stylesheets + // and which stylesheets are parents/children of each other + let processor = postcss([ + { + postcssPlugin: 'mark-import-nodes', + AtRule: { + import(node) { + // Find what the import points to + let id = node.params.match(/['"](.*)['"]/)?.[1] + if (!id) return + + let basePath = node.source?.input.file + ? path.dirname(node.source.input.file) + : process.cwd() + + // Resolve the import to a file path + let resolvedPath: string | false = false + try { + // We first try to resolve the file as relative to the current file + // to mimic the behavior of `postcss-import` since that's what was + // used to resolve imports in Tailwind CSS v3. + if (id[0] !== '.') { + try { + resolvedPath = resolveCssId(`./${id}`, basePath) + } catch {} + } + + if (!resolvedPath) { + resolvedPath = resolveCssId(id, basePath) + } + } catch (err) { + // Import is a URL, we don't want to process these, but also don't + // want to show an error message for them. + if (id.startsWith('http://') || id.startsWith('https://') || id.startsWith('//')) { + return + } + + // Something went wrong, we can't resolve the import. + error( + `Failed to resolve import: ${highlight(id)} in ${highlight(relative(node.source?.input.file!, basePath))}. Skipping.`, + { prefix: '↳ ' }, + ) + return + } + + if (!resolvedPath) return + + // Find the stylesheet pointing to the resolved path + let stylesheet = stylesheetsByFile.get(resolvedPath) + + // If it _does not_ exist in stylesheets we don't care and skip it + // this is likely because its in node_modules or a workspace package + // that we don't want to modify + if (!stylesheet) return + + // Mark the import node with the ID of the stylesheet it points to + // We will use these later to build lookup tables and modify the AST + node.raws.tailwind_destination_sheet_id = stylesheet.id + + let parent = node.source?.input.file + ? stylesheetsByFile.get(node.source.input.file) + : undefined + + let layers: string[] = [] + + for (let part of segment(node.params, ' ')) { + if (!part.startsWith('layer(')) continue + if (!part.endsWith(')')) continue + + layers.push(part.slice(6, -1).trim()) + } + + // Connect sheets together in a dependency graph + if (parent) { + let meta = { layers } + stylesheet.parents.add({ item: parent, meta }) + parent.children.add({ item: stylesheet, meta }) + } + }, + }, + }, + ]) + + // Seed the map with all the known stylesheets, and queue up the processing of + // each incoming stylesheet. + for (let sheet of stylesheets) { + if (sheet.file) { + stylesheetsByFile.set(sheet.file, sheet) + processingQueue.push(() => processor.process(sheet.root, { from: sheet.file ?? undefined })) + } + } + + // Process all the stylesheets from step 1 + while (processingQueue.length > 0) { + let task = processingQueue.shift()! + await task() + } + + // --- + + let commonPath = process.cwd() + + function pathToString(path: StylesheetConnection[]) { + let parts: string[] = [] + + for (let connection of path) { + if (!connection.item.file) continue + + let filePath = connection.item.file.replace(commonPath, '') + let layers = connection.meta.layers.join(', ') + + if (layers.length > 0) { + parts.push(`${filePath} (layers: ${layers})`) + } else { + parts.push(filePath) + } + } + + return parts.join(' <- ') + } + + let lines: string[] = [] + + for (let sheet of stylesheets) { + if (!sheet.file) continue + + let { convertiblePaths, nonConvertiblePaths } = sheet.analyzeImportPaths() + let isAmbiguous = convertiblePaths.length > 0 && nonConvertiblePaths.length > 0 + + if (!isAmbiguous) continue + + sheet.canMigrate = false + + let filePath = sheet.file.replace(commonPath, '') + + for (let path of convertiblePaths) { + lines.push(`- ${filePath} <- ${pathToString(path)}`) + } + + for (let path of nonConvertiblePaths) { + lines.push(`- ${filePath} <- ${pathToString(path)}`) + } + } + + if (lines.length === 0) { + let tailwindRootLeafs = new Set() + + for (let sheet of stylesheets) { + // If the current file already contains `@config`, then we can assume it's + // a Tailwind CSS root file. + sheet.root.walkAtRules('config', () => { + sheet.isTailwindRoot = true + return false + }) + if (sheet.isTailwindRoot) continue + + // If an `@tailwind` at-rule, or `@import "tailwindcss"` is present, + // then we can assume it's a file where Tailwind CSS might be configured. + // + // However, if 2 or more stylesheets exist with these rules that share a + // common parent, then we want to mark the common parent as the root + // stylesheet instead. + sheet.root.walkAtRules((node) => { + if ( + node.name === 'tailwind' || + (node.name === 'import' && node.params.match(/^["']tailwindcss["']/)) || + (node.name === 'import' && node.params.match(/^["']tailwindcss\/.*?["']$/)) + ) { + sheet.isTailwindRoot = true + tailwindRootLeafs.add(sheet) + } + }) + } + + // Only a single Tailwind CSS root file exists, no need to do anything else. + if (tailwindRootLeafs.size <= 1) { + return + } + + // Mark the common parent as the root file + { + // Group each sheet from tailwindRootLeafs by their common parent + let commonParents = new DefaultMap>(() => new Set()) + + // Seed common parents with leafs + for (let sheet of tailwindRootLeafs) { + commonParents.get(sheet).add(sheet) + } + + // If any 2 common parents come from the same tree, then all children of + // parent A and parent B will be moved to the parent of parent A and + // parent B. Parent A and parent B will be removed. + let repeat = true + repeat: while (repeat) { + repeat = false + + for (let [sheetA, childrenA] of commonParents) { + for (let [sheetB, childrenB] of commonParents) { + if (sheetA === sheetB) continue + + // Ancestors from self to root. Reversed order so we find the + // nearest common parent first + // + // Including self because if you compare a sheet with its parent, + // then the parent is still the common sheet between the two. In + // this case, the parent is the root file. + let ancestorsA = [sheetA].concat(Array.from(sheetA.ancestors()).reverse()) + let ancestorsB = [sheetB].concat(Array.from(sheetB.ancestors()).reverse()) + + for (let parentA of ancestorsA) { + for (let parentB of ancestorsB) { + if (parentA !== parentB) continue + + // Found the parent + let parent = parentA + + commonParents.delete(sheetA) + commonParents.delete(sheetB) + + for (let child of childrenA) { + commonParents.get(parent).add(child) + } + + for (let child of childrenB) { + commonParents.get(parent).add(child) + } + + // Found a common parent between sheet A and sheet B. We can + // stop looking for more common parents between A and B, and + // continue with the next sheet. + repeat = true + continue repeat + } + } + } + } + } + + // Mark the common parent as the Tailwind CSS root file, and remove the + // flag from each leaf. + for (let [parent, children] of commonParents) { + parent.isTailwindRoot = true + + for (let child of children) { + if (parent === child) continue + + child.isTailwindRoot = false + } + } + return + } + } + + { + let error = `You have one or more stylesheets that are imported into a utility layer and non-utility layer.\n` + error += `We cannot convert stylesheets under these conditions. Please look at the following stylesheets:\n` + + throw new Error(error + lines.join('\n')) + } +} diff --git a/packages/@tailwindcss-upgrade/src/codemods/fixtures/test.css b/packages/@tailwindcss-upgrade/src/codemods/css/fixtures/test.css similarity index 100% rename from packages/@tailwindcss-upgrade/src/codemods/fixtures/test.css rename to packages/@tailwindcss-upgrade/src/codemods/css/fixtures/test.css diff --git a/packages/@tailwindcss-upgrade/src/codemods/format-nodes.test.ts b/packages/@tailwindcss-upgrade/src/codemods/css/format-nodes.test.ts similarity index 100% rename from packages/@tailwindcss-upgrade/src/codemods/format-nodes.test.ts rename to packages/@tailwindcss-upgrade/src/codemods/css/format-nodes.test.ts diff --git a/packages/@tailwindcss-upgrade/src/codemods/format-nodes.ts b/packages/@tailwindcss-upgrade/src/codemods/css/format-nodes.ts similarity index 98% rename from packages/@tailwindcss-upgrade/src/codemods/format-nodes.ts rename to packages/@tailwindcss-upgrade/src/codemods/css/format-nodes.ts index a5c98050576d..489f2d349c73 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/format-nodes.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/css/format-nodes.ts @@ -1,6 +1,6 @@ import postcss, { type ChildNode, type Plugin, type Root } from 'postcss' import { format, type Options } from 'prettier' -import { walk } from '../utils/walk' +import { walk } from '../../utils/walk' const FORMAT_OPTIONS: Options = { parser: 'css', diff --git a/packages/@tailwindcss-upgrade/src/codemods/css/link.ts b/packages/@tailwindcss-upgrade/src/codemods/css/link.ts new file mode 100644 index 000000000000..04c2dd770039 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/css/link.ts @@ -0,0 +1,122 @@ +import { normalizePath } from '@tailwindcss/node' +import path from 'node:path' +import postcss from 'postcss' +import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map' +import { Stylesheet } from '../../stylesheet' +import { error, highlight, relative, success } from '../../utils/renderer' +import { detectConfigPath } from '../template/prepare-config' + +export async function linkConfigs( + stylesheets: Stylesheet[], + { configPath, base }: { configPath: string | null; base: string }, +) { + let rootStylesheets = stylesheets.filter((sheet) => sheet.isTailwindRoot) + if (rootStylesheets.length === 0) { + throw new Error( + `Cannot find any CSS files that reference Tailwind CSS.\nBefore your project can be upgraded you need to create a CSS file that imports Tailwind CSS or uses ${highlight('@tailwind')}.`, + ) + } + let withoutAtConfig = rootStylesheets.filter((sheet) => { + let hasConfig = false + sheet.root.walkAtRules('config', (node) => { + let configPath = path.resolve(path.dirname(sheet.file!), node.params.slice(1, -1)) + sheet.linkedConfigPath = configPath + hasConfig = true + return false + }) + return !hasConfig + }) + + // All stylesheets have a `@config` directives + if (withoutAtConfig.length === 0) return + + // Find the config file path for each stylesheet + let configPathBySheet = new Map() + let sheetByConfigPath = new DefaultMap>(() => new Set()) + for (let sheet of withoutAtConfig) { + if (!sheet.file) continue + + let localConfigPath = configPath as string + if (configPath === null) { + localConfigPath = await detectConfigPath(path.dirname(sheet.file), base) + } else if (!path.isAbsolute(localConfigPath)) { + localConfigPath = path.resolve(base, localConfigPath) + } + + configPathBySheet.set(sheet, localConfigPath) + sheetByConfigPath.get(localConfigPath).add(sheet) + } + + let problematicStylesheets = new Set() + for (let sheets of sheetByConfigPath.values()) { + if (sheets.size > 1) { + for (let sheet of sheets) { + problematicStylesheets.add(sheet) + } + } + } + + // There are multiple "root" files without `@config` directives. Manual + // intervention is needed to link to the correct Tailwind config files. + if (problematicStylesheets.size > 1) { + for (let sheet of problematicStylesheets) { + error( + `Could not determine configuration file for: ${highlight(relative(sheet.file!, base))}\nUpdate your stylesheet to use ${highlight('@config')} to specify the correct configuration file explicitly and then run the upgrade tool again.`, + { prefix: '↳ ' }, + ) + } + + process.exit(1) + } + + let relativePath = relative + for (let [sheet, configPath] of configPathBySheet) { + try { + if (!sheet || !sheet.file) return + success( + `Linked ${highlight(relativePath(configPath, base))} to ${highlight(relativePath(sheet.file, base))}`, + { prefix: '↳ ' }, + ) + + // Link the `@config` directive to the root stylesheets + + // Track the config file path on the stylesheet itself for easy access + // without traversing the CSS ast and finding the corresponding + // `@config` later. + sheet.linkedConfigPath = configPath + + // Create a relative path from the current file to the config file. + let relative = path.relative(path.dirname(sheet.file), configPath) + + // If the path points to a file in the same directory, `path.relative` will + // remove the leading `./` and we need to add it back in order to still + // consider the path relative + if (!relative.startsWith('.') && !path.isAbsolute(relative)) { + relative = './' + relative + } + + relative = normalizePath(relative) + + // Add the `@config` directive to the root stylesheet. + { + let target = sheet.root as postcss.Root | postcss.AtRule + let atConfig = postcss.atRule({ name: 'config', params: `'${relative}'` }) + + sheet.root.walkAtRules((node) => { + if (node.name === 'tailwind' || node.name === 'import') { + target = node + } + }) + + if (target.type === 'root') { + sheet.root.prepend(atConfig) + } else if (target.type === 'atrule') { + target.after(atConfig) + } + } + } catch (e: any) { + error('Could not load the configuration file: ' + e.message, { prefix: '↳ ' }) + process.exit(1) + } + } +} diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-at-apply.test.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-apply.test.ts similarity index 97% rename from packages/@tailwindcss-upgrade/src/codemods/migrate-at-apply.test.ts rename to packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-apply.test.ts index 362f52054646..d01368a14073 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-at-apply.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-apply.test.ts @@ -2,7 +2,7 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import dedent from 'dedent' import postcss from 'postcss' import { expect, it } from 'vitest' -import type { Config } from '../../../tailwindcss/src/compat/plugin-api' +import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import { migrateAtApply } from './migrate-at-apply' const css = dedent diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-at-apply.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-apply.ts similarity index 88% rename from packages/@tailwindcss-upgrade/src/codemods/migrate-at-apply.ts rename to packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-apply.ts index 0d3a1f0359ca..ffcb12619479 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-at-apply.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-apply.ts @@ -1,7 +1,7 @@ import type { AtRule, Plugin } from 'postcss' -import type { Config } from '../../../tailwindcss/src/compat/plugin-api' -import type { DesignSystem } from '../../../tailwindcss/src/design-system' -import { segment } from '../../../tailwindcss/src/utils/segment' +import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' +import type { DesignSystem } from '../../../../tailwindcss/src/design-system' +import { segment } from '../../../../tailwindcss/src/utils/segment' import { migrateCandidate } from '../template/migrate' export function migrateAtApply({ diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-at-layer-utilities.test.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-layer-utilities.test.ts similarity index 99% rename from packages/@tailwindcss-upgrade/src/codemods/migrate-at-layer-utilities.test.ts rename to packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-layer-utilities.test.ts index 17aeb857722e..553b1f5d079a 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-at-layer-utilities.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-layer-utilities.test.ts @@ -1,7 +1,7 @@ import dedent from 'dedent' import postcss from 'postcss' import { describe, expect, it } from 'vitest' -import { Stylesheet } from '../stylesheet' +import { Stylesheet } from '../../stylesheet' import { formatNodes } from './format-nodes' import { migrateAtLayerUtilities } from './migrate-at-layer-utilities' import { sortBuckets } from './sort-buckets' diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-at-layer-utilities.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-layer-utilities.ts similarity index 98% rename from packages/@tailwindcss-upgrade/src/codemods/migrate-at-layer-utilities.ts rename to packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-layer-utilities.ts index f0f11d3d66cb..e775e3b6d26b 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-at-layer-utilities.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-at-layer-utilities.ts @@ -1,8 +1,8 @@ import { type AtRule, type Comment, type Plugin, type Rule } from 'postcss' import SelectorParser from 'postcss-selector-parser' -import { segment } from '../../../tailwindcss/src/utils/segment' -import { Stylesheet } from '../stylesheet' -import { walk, WalkAction, walkDepth } from '../utils/walk' +import { segment } from '../../../../tailwindcss/src/utils/segment' +import { Stylesheet } from '../../stylesheet' +import { walk, WalkAction, walkDepth } from '../../utils/walk' export function migrateAtLayerUtilities(stylesheet: Stylesheet): Plugin { function migrate(atRule: AtRule) { diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-config.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-config.ts similarity index 93% rename from packages/@tailwindcss-upgrade/src/codemods/migrate-config.ts rename to packages/@tailwindcss-upgrade/src/codemods/css/migrate-config.ts index b54fec34c828..3149c2725e69 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-config.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-config.ts @@ -1,8 +1,8 @@ import path from 'node:path' import postcss, { AtRule, type Plugin } from 'postcss' -import { normalizePath } from '../../../@tailwindcss-node/src/normalize-path' -import type { JSConfigMigration } from '../migrate-js-config' -import type { Stylesheet } from '../stylesheet' +import { normalizePath } from '../../../../@tailwindcss-node/src/normalize-path' +import type { Stylesheet } from '../../stylesheet' +import type { JSConfigMigration } from '../config/migrate-js-config' const ALREADY_INJECTED = new WeakMap() diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-import.test.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-import.test.ts similarity index 100% rename from packages/@tailwindcss-upgrade/src/codemods/migrate-import.test.ts rename to packages/@tailwindcss-upgrade/src/codemods/css/migrate-import.test.ts diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-import.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-import.ts similarity index 85% rename from packages/@tailwindcss-upgrade/src/codemods/migrate-import.ts rename to packages/@tailwindcss-upgrade/src/codemods/css/migrate-import.ts index cfdf00ee6fee..419f5aa32feb 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-import.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-import.ts @@ -1,9 +1,9 @@ import fs from 'node:fs/promises' import { dirname, resolve } from 'node:path' import { type Plugin, type Root } from 'postcss' -import { parseImportParams } from '../../../tailwindcss/src/at-import' -import { segment } from '../../../tailwindcss/src/utils/segment' -import * as ValueParser from '../../../tailwindcss/src/value-parser' +import { parseImportParams } from '../../../../tailwindcss/src/at-import' +import { segment } from '../../../../tailwindcss/src/utils/segment' +import * as ValueParser from '../../../../tailwindcss/src/value-parser' export function migrateImport(): Plugin { async function migrate(root: Root) { @@ -16,6 +16,7 @@ export function migrateImport(): Plugin { let [firstParam, ...rest] = segment(rule.params, ' ') let params = parseImportParams(ValueParser.parse(firstParam)) + if (!params) return let isRelative = params.uri[0] === '.' let hasCssExtension = params.uri.endsWith('.css') diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.test.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-media-screen.test.ts similarity index 97% rename from packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.test.ts rename to packages/@tailwindcss-upgrade/src/codemods/css/migrate-media-screen.test.ts index f4afd15fa268..18ce72e8b7c0 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-media-screen.test.ts @@ -2,7 +2,7 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import dedent from 'dedent' import postcss from 'postcss' import { expect, it } from 'vitest' -import type { UserConfig } from '../../../tailwindcss/src/compat/config/types' +import type { UserConfig } from '../../../../tailwindcss/src/compat/config/types' import { formatNodes } from './format-nodes' import { migrateMediaScreen } from './migrate-media-screen' import { sortBuckets } from './sort-buckets' diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-media-screen.ts similarity index 70% rename from packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.ts rename to packages/@tailwindcss-upgrade/src/codemods/css/migrate-media-screen.ts index 570a4670f2ca..57cc20eeb45b 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-media-screen.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-media-screen.ts @@ -1,9 +1,9 @@ import { type Plugin, type Root } from 'postcss' -import { resolveConfig } from '../../../tailwindcss/src/compat/config/resolve-config' -import type { Config } from '../../../tailwindcss/src/compat/plugin-api' -import { buildMediaQuery } from '../../../tailwindcss/src/compat/screens-config' -import type { DesignSystem } from '../../../tailwindcss/src/design-system' -import { DefaultMap } from '../../../tailwindcss/src/utils/default-map' +import { resolveConfig } from '../../../../tailwindcss/src/compat/config/resolve-config' +import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' +import { buildMediaQuery } from '../../../../tailwindcss/src/compat/screens-config' +import type { DesignSystem } from '../../../../tailwindcss/src/design-system' +import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map' export function migrateMediaScreen({ designSystem, @@ -15,7 +15,9 @@ export function migrateMediaScreen({ function migrate(root: Root) { if (!designSystem || !userConfig) return - let { resolvedConfig } = resolveConfig(designSystem, [{ base: '', config: userConfig }]) + let { resolvedConfig } = resolveConfig(designSystem, [ + { base: '', config: userConfig, reference: false }, + ]) let screens = resolvedConfig?.theme?.screens || {} let mediaQueries = new DefaultMap((name) => { diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-missing-layers.test.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-missing-layers.test.ts similarity index 100% rename from packages/@tailwindcss-upgrade/src/codemods/migrate-missing-layers.test.ts rename to packages/@tailwindcss-upgrade/src/codemods/css/migrate-missing-layers.test.ts diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-missing-layers.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-missing-layers.ts similarity index 98% rename from packages/@tailwindcss-upgrade/src/codemods/migrate-missing-layers.ts rename to packages/@tailwindcss-upgrade/src/codemods/css/migrate-missing-layers.ts index 268058cbc366..10c9d55de9ca 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-missing-layers.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-missing-layers.ts @@ -1,5 +1,5 @@ import { AtRule, type ChildNode, type Plugin, type Root } from 'postcss' -import { segment } from '../../../tailwindcss/src/utils/segment' +import { segment } from '../../../../tailwindcss/src/utils/segment' export function migrateMissingLayers(): Plugin { function migrate(root: Root) { diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-preflight.test.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-preflight.test.ts similarity index 100% rename from packages/@tailwindcss-upgrade/src/codemods/migrate-preflight.test.ts rename to packages/@tailwindcss-upgrade/src/codemods/css/migrate-preflight.test.ts diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-preflight.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-preflight.ts similarity index 92% rename from packages/@tailwindcss-upgrade/src/codemods/migrate-preflight.ts rename to packages/@tailwindcss-upgrade/src/codemods/css/migrate-preflight.ts index 8677d6af6b5e..fa75562cbcd1 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-preflight.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-preflight.ts @@ -1,10 +1,10 @@ import dedent from 'dedent' import postcss, { type Plugin, type Root } from 'postcss' -import { keyPathToCssProperty } from '../../../tailwindcss/src/compat/apply-config-to-theme' -import type { Config } from '../../../tailwindcss/src/compat/plugin-api' -import type { DesignSystem } from '../../../tailwindcss/src/design-system' -import { toKeyPath } from '../../../tailwindcss/src/utils/to-key-path' -import * as ValueParser from '../../../tailwindcss/src/value-parser' +import { keyPathToCssProperty } from '../../../../tailwindcss/src/compat/apply-config-to-theme' +import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' +import type { DesignSystem } from '../../../../tailwindcss/src/design-system' +import { toKeyPath } from '../../../../tailwindcss/src/utils/to-key-path' +import * as ValueParser from '../../../../tailwindcss/src/value-parser' // Defaults in v4 const DEFAULT_BORDER_COLOR = 'currentColor' diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-tailwind-directives.test.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-tailwind-directives.test.ts similarity index 100% rename from packages/@tailwindcss-upgrade/src/codemods/migrate-tailwind-directives.test.ts rename to packages/@tailwindcss-upgrade/src/codemods/css/migrate-tailwind-directives.test.ts diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-tailwind-directives.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-tailwind-directives.ts similarity index 100% rename from packages/@tailwindcss-upgrade/src/codemods/migrate-tailwind-directives.ts rename to packages/@tailwindcss-upgrade/src/codemods/css/migrate-tailwind-directives.ts diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-theme-to-var.test.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-theme-to-var.test.ts similarity index 100% rename from packages/@tailwindcss-upgrade/src/codemods/migrate-theme-to-var.test.ts rename to packages/@tailwindcss-upgrade/src/codemods/css/migrate-theme-to-var.test.ts diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-theme-to-var.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-theme-to-var.ts similarity index 84% rename from packages/@tailwindcss-upgrade/src/codemods/migrate-theme-to-var.ts rename to packages/@tailwindcss-upgrade/src/codemods/css/migrate-theme-to-var.ts index 558e6d0cb6ad..d724620acd76 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-theme-to-var.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-theme-to-var.ts @@ -1,6 +1,6 @@ import { type Plugin } from 'postcss' -import type { DesignSystem } from '../../../tailwindcss/src/design-system' -import { Convert, createConverter } from '../template/codemods/theme-to-var' +import type { DesignSystem } from '../../../../tailwindcss/src/design-system' +import { Convert, createConverter } from '../template/migrate-theme-to-var' export function migrateThemeToVar({ designSystem, diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-variants-directive.test.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-variants-directive.test.ts similarity index 100% rename from packages/@tailwindcss-upgrade/src/codemods/migrate-variants-directive.test.ts rename to packages/@tailwindcss-upgrade/src/codemods/css/migrate-variants-directive.test.ts diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-variants-directive.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate-variants-directive.ts similarity index 100% rename from packages/@tailwindcss-upgrade/src/codemods/migrate-variants-directive.ts rename to packages/@tailwindcss-upgrade/src/codemods/css/migrate-variants-directive.ts diff --git a/packages/@tailwindcss-upgrade/src/codemods/css/migrate.ts b/packages/@tailwindcss-upgrade/src/codemods/css/migrate.ts new file mode 100644 index 000000000000..ab77bab9f120 --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/css/migrate.ts @@ -0,0 +1,57 @@ +import postcss from 'postcss' +import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' +import type { DesignSystem } from '../../../../tailwindcss/src/design-system' +import { Stylesheet } from '../../stylesheet' +import type { JSConfigMigration } from '../config/migrate-js-config' +import { migrateAtApply } from './migrate-at-apply' +import { migrateAtLayerUtilities } from './migrate-at-layer-utilities' +import { migrateConfig } from './migrate-config' +import { migrateImport } from './migrate-import' +import { migrateMediaScreen } from './migrate-media-screen' +import { migrateMissingLayers } from './migrate-missing-layers' +import { migratePreflight } from './migrate-preflight' +import { migrateTailwindDirectives } from './migrate-tailwind-directives' +import { migrateThemeToVar } from './migrate-theme-to-var' +import { migrateVariantsDirective } from './migrate-variants-directive' + +export interface MigrateOptions { + newPrefix: string | null + designSystem: DesignSystem + userConfig: Config + configFilePath: string + jsConfigMigration: JSConfigMigration +} + +export async function migrate(stylesheet: Stylesheet, options: MigrateOptions) { + if (!stylesheet.file) { + throw new Error('Cannot migrate a stylesheet without a file path') + } + + if (!stylesheet.canMigrate) return + + await migrateContents(stylesheet, options) +} + +export async function migrateContents( + stylesheet: Stylesheet | string, + options: MigrateOptions, + file?: string, +) { + if (typeof stylesheet === 'string') { + stylesheet = await Stylesheet.fromString(stylesheet) + stylesheet.file = file ?? null + } + + return postcss() + .use(migrateImport()) + .use(migrateAtApply(options)) + .use(migrateMediaScreen(options)) + .use(migrateVariantsDirective()) + .use(migrateAtLayerUtilities(stylesheet)) + .use(migrateMissingLayers()) + .use(migrateTailwindDirectives(options)) + .use(migrateConfig(stylesheet, options)) + .use(migratePreflight(options)) + .use(migrateThemeToVar(options)) + .process(stylesheet.root, { from: stylesheet.file ?? undefined }) +} diff --git a/packages/@tailwindcss-upgrade/src/codemods/sort-buckets.ts b/packages/@tailwindcss-upgrade/src/codemods/css/sort-buckets.ts similarity index 97% rename from packages/@tailwindcss-upgrade/src/codemods/sort-buckets.ts rename to packages/@tailwindcss-upgrade/src/codemods/css/sort-buckets.ts index f76a9424930f..22b0581ddb5a 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/sort-buckets.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/css/sort-buckets.ts @@ -1,6 +1,6 @@ import postcss, { type AtRule, type ChildNode, type Comment, type Plugin, type Root } from 'postcss' -import { DefaultMap } from '../../../tailwindcss/src/utils/default-map' -import { walk, WalkAction } from '../utils/walk' +import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map' +import { walk, WalkAction } from '../../utils/walk' const BUCKET_ORDER = [ // Imports diff --git a/packages/@tailwindcss-upgrade/src/codemods/css/split.ts b/packages/@tailwindcss-upgrade/src/codemods/css/split.ts new file mode 100644 index 000000000000..146d30733cbb --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/codemods/css/split.ts @@ -0,0 +1,257 @@ +import postcss from 'postcss' +import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map' +import { Stylesheet, type StylesheetId } from '../../stylesheet' +import { walk, WalkAction } from '../../utils/walk' + +export async function split(stylesheets: Stylesheet[]) { + let stylesheetsById = new Map() + let stylesheetsByFile = new Map() + + for (let sheet of stylesheets) { + stylesheetsById.set(sheet.id, sheet) + + if (sheet.file) { + stylesheetsByFile.set(sheet.file, sheet) + } + } + + // Keep track of sheets that contain `@utility` rules + let requiresSplit = new Set() + + for (let sheet of stylesheets) { + // Root files don't need to be split + if (sheet.isTailwindRoot) continue + + let containsUtility = false + let containsUnsafe = sheet.layers().size > 0 + + walk(sheet.root, (node) => { + if (node.type === 'atrule' && node.name === 'utility') { + containsUtility = true + } + + // Safe to keep without splitting + else if ( + // An `@import "…" layer(…)` is safe + (node.type === 'atrule' && node.name === 'import' && node.params.includes('layer(')) || + // @layer blocks are safe + (node.type === 'atrule' && node.name === 'layer') || + // Comments are safe + node.type === 'comment' + ) { + return WalkAction.Skip + } + + // Everything else is not safe, and requires a split + else { + containsUnsafe = true + } + + // We already know we need to split this sheet + if (containsUtility && containsUnsafe) { + return WalkAction.Stop + } + + return WalkAction.Skip + }) + + if (containsUtility && containsUnsafe) { + requiresSplit.add(sheet) + } + } + + // Split every imported stylesheet into two parts + let utilitySheets = new Map() + + for (let sheet of stylesheets) { + // Ignore stylesheets that were not imported + if (!sheet.file) continue + if (sheet.parents.size === 0) continue + + // Skip stylesheets that don't have utilities + // and don't have any children that have utilities + if (!requiresSplit.has(sheet)) { + if (!Array.from(sheet.descendants()).some((child) => requiresSplit.has(child))) { + continue + } + } + + let utilities = postcss.root() + + walk(sheet.root, (node) => { + if (node.type !== 'atrule') return + if (node.name !== 'utility') return + + // `append` will move this node from the original sheet + // to the new utilities sheet + utilities.append(node) + + return WalkAction.Skip + }) + + let newFileName = sheet.file.replace(/\.css$/, '.utilities.css') + + let counter = 0 + + // If we already have a utility sheet with this name, we need to rename it + while (stylesheetsByFile.has(newFileName)) { + counter += 1 + newFileName = sheet.file.replace(/\.css$/, `.utilities.${counter}.css`) + } + + let utilitySheet = await Stylesheet.fromRoot(utilities, newFileName) + + utilitySheet.extension = counter > 0 ? `.utilities.${counter}.css` : `.utilities.css` + + utilitySheets.set(sheet, utilitySheet) + stylesheetsById.set(utilitySheet.id, utilitySheet) + } + + // Make sure the utility sheets are linked to one another + for (let [normalSheet, utilitySheet] of utilitySheets) { + for (let parent of normalSheet.parents) { + let utilityParent = utilitySheets.get(parent.item) + if (!utilityParent) continue + utilitySheet.parents.add({ + item: utilityParent, + meta: parent.meta, + }) + } + + for (let child of normalSheet.children) { + let utilityChild = utilitySheets.get(child.item) + if (!utilityChild) continue + utilitySheet.children.add({ + item: utilityChild, + meta: child.meta, + }) + } + } + + for (let sheet of stylesheets) { + let utilitySheet = utilitySheets.get(sheet) + let utilityImports: Set = new Set() + + for (let node of sheet.importRules) { + let sheetId = node.raws.tailwind_destination_sheet_id as StylesheetId | undefined + + // This import rule does not point to a stylesheet + // which likely means it points to `node_modules` + if (!sheetId) continue + + let originalDestination = stylesheetsById.get(sheetId) + + // This import points to a stylesheet that no longer exists which likely + // means it was removed by the optimizer this will be cleaned up later + if (!originalDestination) continue + + let utilityDestination = utilitySheets.get(originalDestination) + + // A utility sheet doesn't exist for this import so it doesn't need + // to be processed + if (!utilityDestination) continue + + let match = node.params.match(/(['"])(.*)\1/) + if (!match) return + + let quote = match[1] + let id = match[2] + + let newFile = id.replace(/\.css$/, utilityDestination.extension!) + + // The import will just point to the new file without any media queries, + // layers, or other conditions because `@utility` MUST be top-level. + let newImport = node.clone({ + params: `${quote}${newFile}${quote}`, + raws: { + tailwind_injected_layer: node.raws.tailwind_injected_layer, + tailwind_original_params: `${quote}${id}${quote}`, + tailwind_destination_sheet_id: utilityDestination.id, + }, + }) + + if (utilitySheet) { + // If this import is intended to go into the utility sheet + // we'll collect it into a list to add later. If we don't' + // we'll end up adding them in reverse order. + utilityImports.add(newImport) + } else { + // This import will go immediately after the original import + node.after(newImport) + } + } + + // Add imports to the top of the utility sheet if necessary + if (utilitySheet && utilityImports.size > 0) { + utilitySheet.root.prepend(Array.from(utilityImports)) + } + } + + // Tracks the at rules that import a given stylesheet + let importNodes = new DefaultMap>(() => new Set()) + + for (let sheet of stylesheetsById.values()) { + for (let node of sheet.importRules) { + let sheetId = node.raws.tailwind_destination_sheet_id as StylesheetId | undefined + + // This import rule does not point to a stylesheet + if (!sheetId) continue + + let destination = stylesheetsById.get(sheetId) + + // This import rule does not point to a stylesheet that exists + // We'll remove it later + if (!destination) continue + + importNodes.get(destination).add(node) + } + } + + // At this point we've created many `{name}.utilities.css` files. + // If the original file _becomes_ empty after splitting that means that + // dedicated utility file is not required and we can move the utilities + // back to the original file. + // + // This could be done in one step but separating them makes it easier to + // reason about since the stylesheets are in a consistent state before we + // perform any cleanup tasks. + let list: Stylesheet[] = [] + + for (let sheet of stylesheets.slice()) { + for (let child of sheet.descendants()) { + list.push(child) + } + + list.push(sheet) + } + + for (let sheet of list) { + let utilitySheet = utilitySheets.get(sheet) + + // This sheet was not split so there's nothing to do + if (!utilitySheet) continue + + // This sheet did not become empty + if (!sheet.isEmpty) continue + + // We have a sheet that became empty after splitting + // 1. Replace the sheet with it's utility sheet content + sheet.root = utilitySheet.root + + // 2. Rewrite imports in parent sheets to point to the original sheet + // Ideally this wouldn't need to be _undone_ but instead only done once at the end + for (let node of importNodes.get(utilitySheet)) { + node.params = node.raws.tailwind_original_params as any + } + + // 3. Remove the original import from the non-utility sheet + for (let node of importNodes.get(sheet)) { + node.remove() + } + + // 3. Mark the utility sheet for removal + utilitySheets.delete(sheet) + } + + stylesheets.push(...utilitySheets.values()) +} diff --git a/packages/@tailwindcss-upgrade/src/template/candidates.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/candidates.test.ts similarity index 98% rename from packages/@tailwindcss-upgrade/src/template/candidates.test.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/candidates.test.ts index 5ec45fa81c06..c3949985dc94 100644 --- a/packages/@tailwindcss-upgrade/src/template/candidates.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/candidates.test.ts @@ -1,7 +1,7 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import { describe, expect, test } from 'vitest' +import { spliceChangesIntoString } from '../../utils/splice-changes-into-string' import { extractRawCandidates, printCandidate } from './candidates' -import { spliceChangesIntoString } from './splice-changes-into-string' let html = String.raw diff --git a/packages/@tailwindcss-upgrade/src/template/candidates.ts b/packages/@tailwindcss-upgrade/src/codemods/template/candidates.ts similarity index 97% rename from packages/@tailwindcss-upgrade/src/template/candidates.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/candidates.ts index e292168a5d5a..81d8f5410312 100644 --- a/packages/@tailwindcss-upgrade/src/template/candidates.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/candidates.ts @@ -1,7 +1,7 @@ import { Scanner } from '@tailwindcss/oxide' -import type { Candidate, Variant } from '../../../tailwindcss/src/candidate' -import type { DesignSystem } from '../../../tailwindcss/src/design-system' -import * as ValueParser from '../../../tailwindcss/src/value-parser' +import type { Candidate, Variant } from '../../../../tailwindcss/src/candidate' +import type { DesignSystem } from '../../../../tailwindcss/src/design-system' +import * as ValueParser from '../../../../tailwindcss/src/value-parser' export async function extractRawCandidates( content: string, diff --git a/packages/@tailwindcss-upgrade/src/template/is-safe-migration.ts b/packages/@tailwindcss-upgrade/src/codemods/template/is-safe-migration.ts similarity index 100% rename from packages/@tailwindcss-upgrade/src/template/is-safe-migration.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/is-safe-migration.ts diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-value-to-bare-value.test.ts similarity index 93% rename from packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.test.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-value-to-bare-value.test.ts index c9b459d84b6b..77e275360027 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-value-to-bare-value.test.ts @@ -1,6 +1,6 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import { expect, test } from 'vitest' -import { arbitraryValueToBareValue } from './arbitrary-value-to-bare-value' +import { migrateArbitraryValueToBareValue } from './migrate-arbitrary-value-to-bare-value' test.each([ ['aspect-[12/34]', 'aspect-12/34'], @@ -65,5 +65,5 @@ test.each([ base: __dirname, }) - expect(arbitraryValueToBareValue(designSystem, {}, candidate)).toEqual(result) + expect(migrateArbitraryValueToBareValue(designSystem, {}, candidate)).toEqual(result) }) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-value-to-bare-value.ts similarity index 98% rename from packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-value-to-bare-value.ts index 64c06a8f3ea9..6b01bb7437e6 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/arbitrary-value-to-bare-value.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-arbitrary-value-to-bare-value.ts @@ -3,9 +3,9 @@ import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' import { isPositiveInteger } from '../../../../tailwindcss/src/utils/infer-data-type' import { segment } from '../../../../tailwindcss/src/utils/segment' -import { printCandidate } from '../candidates' +import { printCandidate } from './candidates' -export function arbitraryValueToBareValue( +export function migrateArbitraryValueToBareValue( designSystem: DesignSystem, _userConfig: Config, rawCandidate: string, diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/automatic-var-injection.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-automatic-var-injection.test.ts similarity index 95% rename from packages/@tailwindcss-upgrade/src/template/codemods/automatic-var-injection.test.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/migrate-automatic-var-injection.test.ts index 59b1523b7d8e..d26c185cc3bf 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/automatic-var-injection.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-automatic-var-injection.test.ts @@ -1,6 +1,6 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import { expect, test } from 'vitest' -import { automaticVarInjection } from './automatic-var-injection' +import { migrateAutomaticVarInjection } from './migrate-automatic-var-injection' test.each([ // Arbitrary candidates @@ -58,6 +58,6 @@ test.each([ base: __dirname, }) - let migrated = automaticVarInjection(designSystem, {}, candidate) + let migrated = migrateAutomaticVarInjection(designSystem, {}, candidate) expect(migrated).toEqual(result) }) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/automatic-var-injection.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-automatic-var-injection.ts similarity index 98% rename from packages/@tailwindcss-upgrade/src/template/codemods/automatic-var-injection.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/migrate-automatic-var-injection.ts index 643d000c628a..dfb702ed8657 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/automatic-var-injection.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-automatic-var-injection.ts @@ -2,9 +2,9 @@ import { walk, WalkAction } from '../../../../tailwindcss/src/ast' import { type Candidate, type Variant } from '../../../../tailwindcss/src/candidate' import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' -import { printCandidate } from '../candidates' +import { printCandidate } from './candidates' -export function automaticVarInjection( +export function migrateAutomaticVarInjection( designSystem: DesignSystem, _userConfig: Config, rawCandidate: string, diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/bg-gradient.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-bg-gradient.test.ts similarity index 84% rename from packages/@tailwindcss-upgrade/src/template/codemods/bg-gradient.test.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/migrate-bg-gradient.test.ts index 3c42903c3c93..df0ca747ba8e 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/bg-gradient.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-bg-gradient.test.ts @@ -1,6 +1,6 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import { expect, test } from 'vitest' -import { bgGradient } from './bg-gradient' +import { migrateBgGradient } from './migrate-bg-gradient' test.each([ ['bg-gradient-to-t', 'bg-linear-to-t'], @@ -18,5 +18,5 @@ test.each([ base: __dirname, }) - expect(bgGradient(designSystem, {}, candidate)).toEqual(result) + expect(migrateBgGradient(designSystem, {}, candidate)).toEqual(result) }) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/bg-gradient.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-bg-gradient.ts similarity index 90% rename from packages/@tailwindcss-upgrade/src/template/codemods/bg-gradient.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/migrate-bg-gradient.ts index 02ad5590bfa7..a056adae6dbf 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/bg-gradient.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-bg-gradient.ts @@ -1,10 +1,10 @@ import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' -import { printCandidate } from '../candidates' +import { printCandidate } from './candidates' const DIRECTIONS = ['t', 'tr', 'r', 'br', 'b', 'bl', 'l', 'tl'] -export function bgGradient( +export function migrateBgGradient( designSystem: DesignSystem, _userConfig: Config, rawCandidate: string, diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/handle-empty-arbitrary-values.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-handle-empty-arbitrary-values.test.ts similarity index 77% rename from packages/@tailwindcss-upgrade/src/template/codemods/handle-empty-arbitrary-values.test.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/migrate-handle-empty-arbitrary-values.test.ts index b451fbf86b8e..107c901c0a6c 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/handle-empty-arbitrary-values.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-handle-empty-arbitrary-values.test.ts @@ -1,7 +1,7 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import { expect, test } from 'vitest' -import { handleEmptyArbitraryValues } from './handle-empty-arbitrary-values' -import { prefix } from './prefix' +import { migrateEmptyArbitraryValues } from './migrate-handle-empty-arbitrary-values' +import { migratePrefix } from './migrate-prefix' test.each([ ['group-[]:flex', 'group-[&]:flex'], @@ -14,7 +14,7 @@ test.each([ base: __dirname, }) - expect(handleEmptyArbitraryValues(designSystem, {}, candidate)).toEqual(result) + expect(migrateEmptyArbitraryValues(designSystem, {}, candidate)).toEqual(result) }) test.each([ @@ -29,7 +29,7 @@ test.each([ }) expect( - [handleEmptyArbitraryValues, prefix].reduce( + [migrateEmptyArbitraryValues, migratePrefix].reduce( (acc, step) => step(designSystem, { prefix: 'tw-' }, acc), candidate, ), diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/handle-empty-arbitrary-values.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-handle-empty-arbitrary-values.ts similarity index 95% rename from packages/@tailwindcss-upgrade/src/template/codemods/handle-empty-arbitrary-values.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/migrate-handle-empty-arbitrary-values.ts index 8f7dc5e6eea2..0c4fddb11bc9 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/handle-empty-arbitrary-values.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-handle-empty-arbitrary-values.ts @@ -1,7 +1,7 @@ import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' -export function handleEmptyArbitraryValues( +export function migrateEmptyArbitraryValues( designSystem: DesignSystem, _userConfig: Config, rawCandidate: string, diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/important.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-important.test.ts similarity index 90% rename from packages/@tailwindcss-upgrade/src/template/codemods/important.test.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/migrate-important.test.ts index 6667d1e3ac47..bc84538aaa35 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/important.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-important.test.ts @@ -1,6 +1,6 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import { expect, test } from 'vitest' -import { important } from './important' +import { migrateImportant } from './migrate-important' test.each([ ['!flex', 'flex!'], @@ -16,7 +16,7 @@ test.each([ }) expect( - important(designSystem, {}, candidate, { + migrateImportant(designSystem, {}, candidate, { contents: `"${candidate}"`, start: 1, end: candidate.length + 1, @@ -30,7 +30,7 @@ test('does not match false positives', async () => { }) expect( - important(designSystem, {}, '!border', { + migrateImportant(designSystem, {}, '!border', { contents: `let notBorder = !border\n`, start: 16, end: 16 + '!border'.length, @@ -45,7 +45,7 @@ test('does not replace classes in invalid positions', async () => { function shouldNotReplace(example: string, candidate = '!border') { expect( - important(designSystem, {}, candidate, { + migrateImportant(designSystem, {}, candidate, { contents: example, start: example.indexOf(candidate), end: example.indexOf(candidate) + candidate.length, diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/important.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-important.ts similarity index 92% rename from packages/@tailwindcss-upgrade/src/template/codemods/important.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/migrate-important.ts index 4f2c6871f85d..576557fb98d5 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/important.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-important.ts @@ -1,8 +1,8 @@ import { parseCandidate } from '../../../../tailwindcss/src/candidate' import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' -import { printCandidate } from '../candidates' -import { isSafeMigration } from '../is-safe-migration' +import { printCandidate } from './candidates' +import { isSafeMigration } from './is-safe-migration' // In v3 the important modifier `!` sits in front of the utility itself, not // before any of the variants. In v4, we want it to be at the end of the utility @@ -16,7 +16,7 @@ import { isSafeMigration } from '../is-safe-migration' // Should turn into: // // flex! md:block! -export function important( +export function migrateImportant( designSystem: DesignSystem, _userConfig: Config, rawCandidate: string, diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/legacy-arbitrary-values.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-arbitrary-values.test.ts similarity index 72% rename from packages/@tailwindcss-upgrade/src/template/codemods/legacy-arbitrary-values.test.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-arbitrary-values.test.ts index 974cd0ca3289..8bb193b4360c 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/legacy-arbitrary-values.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-arbitrary-values.test.ts @@ -1,6 +1,6 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import { expect, test } from 'vitest' -import { legacyArbitraryValues } from './legacy-arbitrary-values' +import { migrateLegacyArbitraryValues } from './migrate-legacy-arbitrary-values' test.each([ ['grid-cols-[auto,1fr]', 'grid-cols-[auto_1fr]'], @@ -11,5 +11,5 @@ test.each([ base: __dirname, }) - expect(legacyArbitraryValues(designSystem, {}, candidate)).toEqual(result) + expect(migrateLegacyArbitraryValues(designSystem, {}, candidate)).toEqual(result) }) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/legacy-arbitrary-values.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-arbitrary-values.ts similarity index 91% rename from packages/@tailwindcss-upgrade/src/template/codemods/legacy-arbitrary-values.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-arbitrary-values.ts index 1c60526fd772..b2bd44d07a06 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/legacy-arbitrary-values.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-arbitrary-values.ts @@ -2,9 +2,9 @@ import { parseCandidate } from '../../../../tailwindcss/src/candidate' import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' import { segment } from '../../../../tailwindcss/src/utils/segment' -import { printCandidate } from '../candidates' +import { printCandidate } from './candidates' -export function legacyArbitraryValues( +export function migrateLegacyArbitraryValues( designSystem: DesignSystem, _userConfig: Config, rawCandidate: string, diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/legacy-classes.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.test.ts similarity index 92% rename from packages/@tailwindcss-upgrade/src/template/codemods/legacy-classes.test.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.test.ts index 6b8bbe4c610f..ad589cbee80e 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/legacy-classes.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.test.ts @@ -1,6 +1,6 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import { expect, test } from 'vitest' -import { legacyClasses } from './legacy-classes' +import { migrateLegacyClasses } from './migrate-legacy-classes' test.each([ ['shadow', 'shadow-sm'], @@ -37,7 +37,7 @@ test.each([ base: __dirname, }) - expect(await legacyClasses(designSystem, {}, candidate)).toEqual(result) + expect(await migrateLegacyClasses(designSystem, {}, candidate)).toEqual(result) }) test('does not replace classes in invalid positions', async () => { @@ -47,7 +47,7 @@ test('does not replace classes in invalid positions', async () => { async function shouldNotReplace(example: string, candidate = 'shadow') { expect( - await legacyClasses(designSystem, {}, candidate, { + await migrateLegacyClasses(designSystem, {}, candidate, { contents: example, start: example.indexOf(candidate), end: example.indexOf(candidate) + candidate.length, diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/legacy-classes.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.ts similarity index 97% rename from packages/@tailwindcss-upgrade/src/template/codemods/legacy-classes.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.ts index 8dad3967ce76..8f3835c8f955 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/legacy-classes.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-legacy-classes.ts @@ -5,8 +5,8 @@ import type { Candidate } from '../../../../tailwindcss/src/candidate' import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' import { DefaultMap } from '../../../../tailwindcss/src/utils/default-map' -import { printCandidate } from '../candidates' -import { isSafeMigration } from '../is-safe-migration' +import { printCandidate } from './candidates' +import { isSafeMigration } from './is-safe-migration' const __filename = url.fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) @@ -65,7 +65,7 @@ const DESIGN_SYSTEMS = new DefaultMap((base) => { return __unstable__loadDesignSystem('@import "tailwindcss";', { base }) }) -export async function legacyClasses( +export async function migrateLegacyClasses( designSystem: DesignSystem, _userConfig: Config, rawCandidate: string, diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/max-width-screen.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-max-width-screen.test.ts similarity index 74% rename from packages/@tailwindcss-upgrade/src/template/codemods/max-width-screen.test.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/migrate-max-width-screen.test.ts index 3e3151cbc7da..91b57b3db94e 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/max-width-screen.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-max-width-screen.test.ts @@ -1,12 +1,12 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import { expect, test } from 'vitest' -import { maxWidthScreen } from './max-width-screen' +import { migrateMaxWidthScreen } from './migrate-max-width-screen' test('converts max-w-screen-* to max-w-[theme(screens.*)] (so it will later be converted to the var injection)', async () => { let designSystem = await __unstable__loadDesignSystem('@import "tailwindcss";', { base: __dirname, }) - let migrated = maxWidthScreen(designSystem, {}, 'max-w-screen-md') + let migrated = migrateMaxWidthScreen(designSystem, {}, 'max-w-screen-md') expect(migrated).toMatchInlineSnapshot(`"max-w-[theme(screens.md)]"`) }) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/max-width-screen.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-max-width-screen.ts similarity index 89% rename from packages/@tailwindcss-upgrade/src/template/codemods/max-width-screen.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/migrate-max-width-screen.ts index 4a14e5aadac8..92fec5099546 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/max-width-screen.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-max-width-screen.ts @@ -1,8 +1,8 @@ import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' -import { printCandidate } from '../candidates' +import { printCandidate } from './candidates' -export function maxWidthScreen( +export function migrateMaxWidthScreen( designSystem: DesignSystem, _userConfig: Config, rawCandidate: string, diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.test.ts similarity index 93% rename from packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.test.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.test.ts index ec38d08641cb..cc4e5a4a5fdc 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.test.ts @@ -1,8 +1,8 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import { expect, test } from 'vitest' -import { handleEmptyArbitraryValues } from './handle-empty-arbitrary-values' -import { modernizeArbitraryValues } from './modernize-arbitrary-values' -import { prefix } from './prefix' +import { migrateEmptyArbitraryValues } from './migrate-handle-empty-arbitrary-values' +import { migrateModernizeArbitraryValues } from './migrate-modernize-arbitrary-values' +import { migratePrefix } from './migrate-prefix' test.each([ // Arbitrary variants @@ -96,7 +96,7 @@ test.each([ }) expect( - [handleEmptyArbitraryValues, modernizeArbitraryValues].reduce( + [migrateEmptyArbitraryValues, migrateModernizeArbitraryValues].reduce( (acc, step) => step(designSystem, {}, acc), candidate, ), @@ -130,7 +130,7 @@ test.each([ }) expect( - [handleEmptyArbitraryValues, prefix, modernizeArbitraryValues].reduce( + [migrateEmptyArbitraryValues, migratePrefix, migrateModernizeArbitraryValues].reduce( (acc, step) => step(designSystem, { prefix: 'tw-' }, acc), candidate, ), diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.ts similarity index 99% rename from packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.ts index 3d90351b1433..f0eec7485a29 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/modernize-arbitrary-values.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-modernize-arbitrary-values.ts @@ -3,7 +3,7 @@ import { parseCandidate, type Candidate, type Variant } from '../../../../tailwi import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' import { isPositiveInteger } from '../../../../tailwindcss/src/utils/infer-data-type' -import { printCandidate } from '../candidates' +import { printCandidate } from './candidates' function memcpy(target: T, source: U): U { // Clear out the target object, otherwise inspecting the final object will @@ -13,7 +13,7 @@ function memcpy(target: T, source: U) return Object.assign(target, source) } -export function modernizeArbitraryValues( +export function migrateModernizeArbitraryValues( designSystem: DesignSystem, _userConfig: Config, rawCandidate: string, diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/prefix.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-prefix.test.ts similarity index 82% rename from packages/@tailwindcss-upgrade/src/template/codemods/prefix.test.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/migrate-prefix.test.ts index a7ad6a03ec75..04d9282ab7b1 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/prefix.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-prefix.test.ts @@ -1,6 +1,6 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import { describe, expect, test } from 'vitest' -import { prefix } from './prefix' +import { migratePrefix } from './migrate-prefix' describe('for projects with configured prefix', () => { test.each([ @@ -31,7 +31,7 @@ describe('for projects with configured prefix', () => { base: __dirname, }) - expect(prefix(designSystem, { prefix: 'tw-' }, candidate)).toEqual(result) + expect(migratePrefix(designSystem, { prefix: 'tw-' }, candidate)).toEqual(result) }) }) @@ -40,7 +40,7 @@ test('can handle complex prefix separators', async () => { base: __dirname, }) - expect(prefix(designSystem, { prefix: 'tw__' }, 'tw__flex')).toEqual('tw:flex') + expect(migratePrefix(designSystem, { prefix: 'tw__' }, 'tw__flex')).toEqual('tw:flex') }) describe('for projects without configured prefix', () => { @@ -49,6 +49,6 @@ describe('for projects without configured prefix', () => { base: __dirname, }) - expect(prefix(designSystem, {}, 'tw-flex')).toEqual('tw-flex') + expect(migratePrefix(designSystem, {}, 'tw-flex')).toEqual('tw-flex') }) }) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/prefix.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-prefix.ts similarity index 96% rename from packages/@tailwindcss-upgrade/src/template/codemods/prefix.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/migrate-prefix.ts index 47a377239a21..95bb4c69c3ee 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/prefix.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-prefix.ts @@ -2,11 +2,11 @@ import { parseCandidate, type Candidate } from '../../../../tailwindcss/src/cand import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' import { segment } from '../../../../tailwindcss/src/utils/segment' -import { printCandidate } from '../candidates' +import { printCandidate } from './candidates' let seenDesignSystems = new WeakSet() -export function prefix( +export function migratePrefix( designSystem: DesignSystem, userConfig: Config, rawCandidate: string, @@ -115,7 +115,7 @@ function extractV3Base( } const VALID_PREFIX = /([a-z]+)/ -export function migratePrefix(prefix: string): string { +export function migratePrefixValue(prefix: string): string { let result = VALID_PREFIX.exec(prefix.toLocaleLowerCase()) if (!result) { console.warn( diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-simple-legacy-classes.test.ts similarity index 84% rename from packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.test.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/migrate-simple-legacy-classes.test.ts index a9da9e54e036..cd54234efd4d 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-simple-legacy-classes.test.ts @@ -1,6 +1,6 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import { expect, test } from 'vitest' -import { simpleLegacyClasses } from './simple-legacy-classes' +import { migrateSimpleLegacyClasses } from './migrate-simple-legacy-classes' test.each([ ['overflow-ellipsis', 'text-ellipsis'], @@ -24,5 +24,5 @@ test.each([ base: __dirname, }) - expect(simpleLegacyClasses(designSystem, {}, candidate)).toEqual(result) + expect(migrateSimpleLegacyClasses(designSystem, {}, candidate)).toEqual(result) }) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-simple-legacy-classes.ts similarity index 93% rename from packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/migrate-simple-legacy-classes.ts index 1251d0f2d15c..dd6599152c7f 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/simple-legacy-classes.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-simple-legacy-classes.ts @@ -1,6 +1,6 @@ import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' -import { printCandidate } from '../candidates' +import { printCandidate } from './candidates' // Classes that used to exist in Tailwind CSS v3, but do not exist in Tailwind // CSS v4 anymore. @@ -20,7 +20,7 @@ const LEGACY_CLASS_MAP = { let seenDesignSystems = new WeakSet() -export function simpleLegacyClasses( +export function migrateSimpleLegacyClasses( designSystem: DesignSystem, _userConfig: Config, rawCandidate: string, diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-theme-to-var.test.ts similarity index 90% rename from packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.test.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/migrate-theme-to-var.test.ts index c5131fc29499..e30109429f29 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-theme-to-var.test.ts @@ -1,6 +1,6 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import { expect, test } from 'vitest' -import { themeToVar } from './theme-to-var' +import { migrateThemeToVar } from './migrate-theme-to-var' let css = String.raw @@ -139,7 +139,7 @@ test.each([ }, ) - expect(themeToVar(designSystem, {}, candidate)).toEqual(result) + expect(migrateThemeToVar(designSystem, {}, candidate)).toEqual(result) }) test('extended space scale converts to var or calc', async () => { @@ -155,16 +155,16 @@ test('extended space scale converts to var or calc', async () => { base: __dirname, }, ) - expect(themeToVar(designSystem, {}, '[--value:theme(spacing.1)]')).toEqual( + expect(migrateThemeToVar(designSystem, {}, '[--value:theme(spacing.1)]')).toEqual( '[--value:--spacing(1)]', ) - expect(themeToVar(designSystem, {}, '[--value:theme(spacing.2)]')).toEqual( + expect(migrateThemeToVar(designSystem, {}, '[--value:theme(spacing.2)]')).toEqual( '[--value:var(--spacing-2)]', ) - expect(themeToVar(designSystem, {}, '[--value:theme(spacing.miami)]')).toEqual( + expect(migrateThemeToVar(designSystem, {}, '[--value:theme(spacing.miami)]')).toEqual( '[--value:var(--spacing-miami)]', ) - expect(themeToVar(designSystem, {}, '[--value:theme(spacing.nyc)]')).toEqual( + expect(migrateThemeToVar(designSystem, {}, '[--value:theme(spacing.nyc)]')).toEqual( '[--value:theme(spacing.nyc)]', ) }) @@ -183,13 +183,13 @@ test('custom space scale converts to var', async () => { base: __dirname, }, ) - expect(themeToVar(designSystem, {}, '[--value:theme(spacing.1)]')).toEqual( + expect(migrateThemeToVar(designSystem, {}, '[--value:theme(spacing.1)]')).toEqual( '[--value:var(--spacing-1)]', ) - expect(themeToVar(designSystem, {}, '[--value:theme(spacing.2)]')).toEqual( + expect(migrateThemeToVar(designSystem, {}, '[--value:theme(spacing.2)]')).toEqual( '[--value:var(--spacing-2)]', ) - expect(themeToVar(designSystem, {}, '[--value:theme(spacing.3)]')).toEqual( + expect(migrateThemeToVar(designSystem, {}, '[--value:theme(spacing.3)]')).toEqual( '[--value:theme(spacing.3)]', ) }) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-theme-to-var.ts similarity index 99% rename from packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/migrate-theme-to-var.ts index 6248d43473fb..3abb4c96fcc4 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/theme-to-var.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-theme-to-var.ts @@ -11,7 +11,7 @@ import { isValidSpacingMultiplier } from '../../../../tailwindcss/src/utils/infe import { segment } from '../../../../tailwindcss/src/utils/segment' import { toKeyPath } from '../../../../tailwindcss/src/utils/to-key-path' import * as ValueParser from '../../../../tailwindcss/src/value-parser' -import { printCandidate } from '../candidates' +import { printCandidate } from './candidates' export const enum Convert { All = 0, @@ -19,7 +19,7 @@ export const enum Convert { MigrateThemeOnly = 1 << 1, } -export function themeToVar( +export function migrateThemeToVar( designSystem: DesignSystem, _userConfig: Config, rawCandidate: string, diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/variant-order.test.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-variant-order.test.ts similarity index 92% rename from packages/@tailwindcss-upgrade/src/template/codemods/variant-order.test.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/migrate-variant-order.test.ts index ffceaaecb0da..61589b8fed5a 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/variant-order.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-variant-order.test.ts @@ -1,7 +1,7 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import dedent from 'dedent' import { expect, test } from 'vitest' -import { variantOrder } from './variant-order' +import { migrateVariantOrder } from './migrate-variant-order' let css = dedent @@ -53,7 +53,7 @@ test.each([ base: __dirname, }) - expect(variantOrder(designSystem, {}, candidate)).toEqual(result) + expect(migrateVariantOrder(designSystem, {}, candidate)).toEqual(result) }) test('it works with custom variants', async () => { @@ -83,7 +83,7 @@ test('it works with custom variants', async () => { }, ) - expect(variantOrder(designSystem, {}, 'combinator:pseudo:atrule:underline')).toEqual( + expect(migrateVariantOrder(designSystem, {}, 'combinator:pseudo:atrule:underline')).toEqual( 'atrule:combinator:pseudo:underline', ) }) diff --git a/packages/@tailwindcss-upgrade/src/template/codemods/variant-order.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-variant-order.ts similarity index 97% rename from packages/@tailwindcss-upgrade/src/template/codemods/variant-order.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/migrate-variant-order.ts index 07c7015ab6de..ab18b6b78476 100644 --- a/packages/@tailwindcss-upgrade/src/template/codemods/variant-order.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate-variant-order.ts @@ -2,9 +2,9 @@ import { walk, type AstNode } from '../../../../tailwindcss/src/ast' import { type Variant } from '../../../../tailwindcss/src/candidate' import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' import type { DesignSystem } from '../../../../tailwindcss/src/design-system' -import { printCandidate } from '../candidates' +import { printCandidate } from './candidates' -export function variantOrder( +export function migrateVariantOrder( designSystem: DesignSystem, _userConfig: Config, rawCandidate: string, diff --git a/packages/@tailwindcss-upgrade/src/template/migrate.ts b/packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts similarity index 55% rename from packages/@tailwindcss-upgrade/src/template/migrate.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts index 7fdc164f23d8..c18e2f25e6a6 100644 --- a/packages/@tailwindcss-upgrade/src/template/migrate.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/migrate.ts @@ -1,22 +1,22 @@ import fs from 'node:fs/promises' import path, { extname } from 'node:path' -import type { Config } from '../../../tailwindcss/src/compat/plugin-api' -import type { DesignSystem } from '../../../tailwindcss/src/design-system' +import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' +import type { DesignSystem } from '../../../../tailwindcss/src/design-system' +import { spliceChangesIntoString, type StringChange } from '../../utils/splice-changes-into-string' import { extractRawCandidates } from './candidates' -import { arbitraryValueToBareValue } from './codemods/arbitrary-value-to-bare-value' -import { automaticVarInjection } from './codemods/automatic-var-injection' -import { bgGradient } from './codemods/bg-gradient' -import { handleEmptyArbitraryValues } from './codemods/handle-empty-arbitrary-values' -import { important } from './codemods/important' -import { legacyArbitraryValues } from './codemods/legacy-arbitrary-values' -import { legacyClasses } from './codemods/legacy-classes' -import { maxWidthScreen } from './codemods/max-width-screen' -import { modernizeArbitraryValues } from './codemods/modernize-arbitrary-values' -import { prefix } from './codemods/prefix' -import { simpleLegacyClasses } from './codemods/simple-legacy-classes' -import { themeToVar } from './codemods/theme-to-var' -import { variantOrder } from './codemods/variant-order' -import { spliceChangesIntoString, type StringChange } from './splice-changes-into-string' +import { migrateArbitraryValueToBareValue } from './migrate-arbitrary-value-to-bare-value' +import { migrateAutomaticVarInjection } from './migrate-automatic-var-injection' +import { migrateBgGradient } from './migrate-bg-gradient' +import { migrateEmptyArbitraryValues } from './migrate-handle-empty-arbitrary-values' +import { migrateImportant } from './migrate-important' +import { migrateLegacyArbitraryValues } from './migrate-legacy-arbitrary-values' +import { migrateLegacyClasses } from './migrate-legacy-classes' +import { migrateMaxWidthScreen } from './migrate-max-width-screen' +import { migrateModernizeArbitraryValues } from './migrate-modernize-arbitrary-values' +import { migratePrefix } from './migrate-prefix' +import { migrateSimpleLegacyClasses } from './migrate-simple-legacy-classes' +import { migrateThemeToVar } from './migrate-theme-to-var' +import { migrateVariantOrder } from './migrate-variant-order' export type Migration = ( designSystem: DesignSystem, @@ -30,19 +30,19 @@ export type Migration = ( ) => string | Promise export const DEFAULT_MIGRATIONS: Migration[] = [ - handleEmptyArbitraryValues, - prefix, - important, - bgGradient, - simpleLegacyClasses, - legacyClasses, - maxWidthScreen, - themeToVar, - variantOrder, // Has to happen before migrations that modify variants - automaticVarInjection, - legacyArbitraryValues, - arbitraryValueToBareValue, - modernizeArbitraryValues, + migrateEmptyArbitraryValues, + migratePrefix, + migrateImportant, + migrateBgGradient, + migrateSimpleLegacyClasses, + migrateLegacyClasses, + migrateMaxWidthScreen, + migrateThemeToVar, + migrateVariantOrder, // Has to happen before migrations that modify variants + migrateAutomaticVarInjection, + migrateLegacyArbitraryValues, + migrateArbitraryValueToBareValue, + migrateModernizeArbitraryValues, ] export async function migrateCandidate( diff --git a/packages/@tailwindcss-upgrade/src/template/prepare-config.ts b/packages/@tailwindcss-upgrade/src/codemods/template/prepare-config.ts similarity index 88% rename from packages/@tailwindcss-upgrade/src/template/prepare-config.ts rename to packages/@tailwindcss-upgrade/src/codemods/template/prepare-config.ts index b4a6e18b31e1..d9fae442b77a 100644 --- a/packages/@tailwindcss-upgrade/src/template/prepare-config.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/template/prepare-config.ts @@ -2,12 +2,12 @@ import { __unstable__loadDesignSystem, compile } from '@tailwindcss/node' import fs from 'node:fs/promises' import path, { dirname } from 'node:path' import { fileURLToPath } from 'node:url' -import { loadModule } from '../../../@tailwindcss-node/src/compile' -import { resolveConfig } from '../../../tailwindcss/src/compat/config/resolve-config' -import type { Config } from '../../../tailwindcss/src/compat/plugin-api' -import type { DesignSystem } from '../../../tailwindcss/src/design-system' -import { error, highlight, relative } from '../utils/renderer' -import { migratePrefix } from './codemods/prefix' +import { loadModule } from '../../../../@tailwindcss-node/src/compile' +import { resolveConfig } from '../../../../tailwindcss/src/compat/config/resolve-config' +import type { Config } from '../../../../tailwindcss/src/compat/plugin-api' +import type { DesignSystem } from '../../../../tailwindcss/src/design-system' +import { error, highlight, relative } from '../../utils/renderer' +import { migratePrefixValue } from './migrate-prefix' const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) @@ -46,7 +46,7 @@ export async function prepareConfig( let userConfig = await createResolvedUserConfig(configFilePath) - let newPrefix = userConfig.prefix ? migratePrefix(userConfig.prefix) : null + let newPrefix = userConfig.prefix ? migratePrefixValue(userConfig.prefix) : null let input = css` @import 'tailwindcss' ${newPrefix ? `prefix(${newPrefix})` : ''}; @config '${relative}'; diff --git a/packages/@tailwindcss-upgrade/src/index.test.ts b/packages/@tailwindcss-upgrade/src/index.test.ts index 65d74937b42b..746aa56d5403 100644 --- a/packages/@tailwindcss-upgrade/src/index.test.ts +++ b/packages/@tailwindcss-upgrade/src/index.test.ts @@ -3,9 +3,9 @@ import dedent from 'dedent' import path from 'node:path' import postcss from 'postcss' import { expect, it } from 'vitest' -import { formatNodes } from './codemods/format-nodes' -import { sortBuckets } from './codemods/sort-buckets' -import { migrateContents } from './migrate' +import { formatNodes } from './codemods/css/format-nodes' +import { migrateContents } from './codemods/css/migrate' +import { sortBuckets } from './codemods/css/sort-buckets' const css = dedent diff --git a/packages/@tailwindcss-upgrade/src/index.ts b/packages/@tailwindcss-upgrade/src/index.ts index f8f97934fe03..75bd47b523a4 100644 --- a/packages/@tailwindcss-upgrade/src/index.ts +++ b/packages/@tailwindcss-upgrade/src/index.ts @@ -4,21 +4,19 @@ import { globby } from 'globby' import fs from 'node:fs/promises' import path from 'node:path' import postcss from 'postcss' -import { formatNodes } from './codemods/format-nodes' -import { sortBuckets } from './codemods/sort-buckets' +import { migrateJsConfig } from './codemods/config/migrate-js-config' +import { migratePostCSSConfig } from './codemods/config/migrate-postcss' +import { migratePrettierPlugin } from './codemods/config/migrate-prettier' +import { analyze as analyzeStylesheets } from './codemods/css/analyze' +import { formatNodes } from './codemods/css/format-nodes' +import { linkConfigs as linkConfigsToStylesheets } from './codemods/css/link' +import { migrate as migrateStylesheet } from './codemods/css/migrate' +import { sortBuckets } from './codemods/css/sort-buckets' +import { split as splitStylesheets } from './codemods/css/split' +import { migrate as migrateTemplate } from './codemods/template/migrate' +import { prepareConfig } from './codemods/template/prepare-config' import { help } from './commands/help' -import { - analyze as analyzeStylesheets, - linkConfigs as linkConfigsToStylesheets, - migrate as migrateStylesheet, - split as splitStylesheets, -} from './migrate' -import { migrateJsConfig } from './migrate-js-config' -import { migratePostCSSConfig } from './migrate-postcss' -import { migratePrettierPlugin } from './migrate-prettier' import { Stylesheet } from './stylesheet' -import { migrate as migrateTemplate } from './template/migrate' -import { prepareConfig } from './template/prepare-config' import { args, type Arg } from './utils/args' import { isRepoDirty } from './utils/git' import { hoistStaticGlobParts } from './utils/hoist-static-glob-parts' diff --git a/packages/@tailwindcss-upgrade/src/migrate.ts b/packages/@tailwindcss-upgrade/src/migrate.ts deleted file mode 100644 index 45e75849622d..000000000000 --- a/packages/@tailwindcss-upgrade/src/migrate.ts +++ /dev/null @@ -1,719 +0,0 @@ -import { normalizePath } from '@tailwindcss/node' -import { isGitIgnored } from 'globby' -import path from 'node:path' -import postcss, { type Result } from 'postcss' -import type { Config } from '../../tailwindcss/src/compat/plugin-api' -import type { DesignSystem } from '../../tailwindcss/src/design-system' -import { DefaultMap } from '../../tailwindcss/src/utils/default-map' -import { segment } from '../../tailwindcss/src/utils/segment' -import { migrateAtApply } from './codemods/migrate-at-apply' -import { migrateAtLayerUtilities } from './codemods/migrate-at-layer-utilities' -import { migrateConfig } from './codemods/migrate-config' -import { migrateImport } from './codemods/migrate-import' -import { migrateMediaScreen } from './codemods/migrate-media-screen' -import { migrateMissingLayers } from './codemods/migrate-missing-layers' -import { migratePreflight } from './codemods/migrate-preflight' -import { migrateTailwindDirectives } from './codemods/migrate-tailwind-directives' -import { migrateThemeToVar } from './codemods/migrate-theme-to-var' -import { migrateVariantsDirective } from './codemods/migrate-variants-directive' -import type { JSConfigMigration } from './migrate-js-config' -import { Stylesheet, type StylesheetConnection, type StylesheetId } from './stylesheet' -import { detectConfigPath } from './template/prepare-config' -import { error, highlight, relative, success } from './utils/renderer' -import { resolveCssId } from './utils/resolve' -import { walk, WalkAction } from './utils/walk' - -export interface MigrateOptions { - newPrefix: string | null - designSystem: DesignSystem - userConfig: Config - configFilePath: string - jsConfigMigration: JSConfigMigration -} - -export async function migrateContents( - stylesheet: Stylesheet | string, - options: MigrateOptions, - file?: string, -) { - if (typeof stylesheet === 'string') { - stylesheet = await Stylesheet.fromString(stylesheet) - stylesheet.file = file ?? null - } - - return postcss() - .use(migrateImport()) - .use(migrateAtApply(options)) - .use(migrateMediaScreen(options)) - .use(migrateVariantsDirective()) - .use(migrateAtLayerUtilities(stylesheet)) - .use(migrateMissingLayers()) - .use(migrateTailwindDirectives(options)) - .use(migrateConfig(stylesheet, options)) - .use(migratePreflight(options)) - .use(migrateThemeToVar(options)) - .process(stylesheet.root, { from: stylesheet.file ?? undefined }) -} - -export async function migrate(stylesheet: Stylesheet, options: MigrateOptions) { - if (!stylesheet.file) { - throw new Error('Cannot migrate a stylesheet without a file path') - } - - if (!stylesheet.canMigrate) return - - await migrateContents(stylesheet, options) -} - -export async function analyze(stylesheets: Stylesheet[]) { - let isIgnored = await isGitIgnored() - let processingQueue: (() => Promise)[] = [] - let stylesheetsByFile = new DefaultMap((file) => { - // We don't want to process ignored files (like node_modules) - if (isIgnored(file)) { - return null - } - - try { - let sheet = Stylesheet.loadSync(file) - - // Mutate incoming stylesheets to include the newly discovered sheet - stylesheets.push(sheet) - - // Queue up the processing of this stylesheet - processingQueue.push(() => processor.process(sheet.root, { from: sheet.file! })) - - return sheet - } catch { - return null - } - }) - - // Step 1: Record which `@import` rules point to which stylesheets - // and which stylesheets are parents/children of each other - let processor = postcss([ - { - postcssPlugin: 'mark-import-nodes', - AtRule: { - import(node) { - // Find what the import points to - let id = node.params.match(/['"](.*)['"]/)?.[1] - if (!id) return - - let basePath = node.source?.input.file - ? path.dirname(node.source.input.file) - : process.cwd() - - // Resolve the import to a file path - let resolvedPath: string | false = false - try { - // We first try to resolve the file as relative to the current file - // to mimic the behavior of `postcss-import` since that's what was - // used to resolve imports in Tailwind CSS v3. - if (id[0] !== '.') { - try { - resolvedPath = resolveCssId(`./${id}`, basePath) - } catch {} - } - - if (!resolvedPath) { - resolvedPath = resolveCssId(id, basePath) - } - } catch (err) { - // Import is a URL, we don't want to process these, but also don't - // want to show an error message for them. - if (id.startsWith('http://') || id.startsWith('https://') || id.startsWith('//')) { - return - } - - // Something went wrong, we can't resolve the import. - error( - `Failed to resolve import: ${highlight(id)} in ${highlight(relative(node.source?.input.file!, basePath))}. Skipping.`, - { prefix: '↳ ' }, - ) - return - } - - if (!resolvedPath) return - - // Find the stylesheet pointing to the resolved path - let stylesheet = stylesheetsByFile.get(resolvedPath) - - // If it _does not_ exist in stylesheets we don't care and skip it - // this is likely because its in node_modules or a workspace package - // that we don't want to modify - if (!stylesheet) return - - // Mark the import node with the ID of the stylesheet it points to - // We will use these later to build lookup tables and modify the AST - node.raws.tailwind_destination_sheet_id = stylesheet.id - - let parent = node.source?.input.file - ? stylesheetsByFile.get(node.source.input.file) - : undefined - - let layers: string[] = [] - - for (let part of segment(node.params, ' ')) { - if (!part.startsWith('layer(')) continue - if (!part.endsWith(')')) continue - - layers.push(part.slice(6, -1).trim()) - } - - // Connect sheets together in a dependency graph - if (parent) { - let meta = { layers } - stylesheet.parents.add({ item: parent, meta }) - parent.children.add({ item: stylesheet, meta }) - } - }, - }, - }, - ]) - - // Seed the map with all the known stylesheets, and queue up the processing of - // each incoming stylesheet. - for (let sheet of stylesheets) { - if (sheet.file) { - stylesheetsByFile.set(sheet.file, sheet) - processingQueue.push(() => processor.process(sheet.root, { from: sheet.file ?? undefined })) - } - } - - // Process all the stylesheets from step 1 - while (processingQueue.length > 0) { - let task = processingQueue.shift()! - await task() - } - - // --- - - let commonPath = process.cwd() - - function pathToString(path: StylesheetConnection[]) { - let parts: string[] = [] - - for (let connection of path) { - if (!connection.item.file) continue - - let filePath = connection.item.file.replace(commonPath, '') - let layers = connection.meta.layers.join(', ') - - if (layers.length > 0) { - parts.push(`${filePath} (layers: ${layers})`) - } else { - parts.push(filePath) - } - } - - return parts.join(' <- ') - } - - let lines: string[] = [] - - for (let sheet of stylesheets) { - if (!sheet.file) continue - - let { convertiblePaths, nonConvertiblePaths } = sheet.analyzeImportPaths() - let isAmbiguous = convertiblePaths.length > 0 && nonConvertiblePaths.length > 0 - - if (!isAmbiguous) continue - - sheet.canMigrate = false - - let filePath = sheet.file.replace(commonPath, '') - - for (let path of convertiblePaths) { - lines.push(`- ${filePath} <- ${pathToString(path)}`) - } - - for (let path of nonConvertiblePaths) { - lines.push(`- ${filePath} <- ${pathToString(path)}`) - } - } - - if (lines.length === 0) { - let tailwindRootLeafs = new Set() - - for (let sheet of stylesheets) { - // If the current file already contains `@config`, then we can assume it's - // a Tailwind CSS root file. - sheet.root.walkAtRules('config', () => { - sheet.isTailwindRoot = true - return false - }) - if (sheet.isTailwindRoot) continue - - // If an `@tailwind` at-rule, or `@import "tailwindcss"` is present, - // then we can assume it's a file where Tailwind CSS might be configured. - // - // However, if 2 or more stylesheets exist with these rules that share a - // common parent, then we want to mark the common parent as the root - // stylesheet instead. - sheet.root.walkAtRules((node) => { - if ( - node.name === 'tailwind' || - (node.name === 'import' && node.params.match(/^["']tailwindcss["']/)) || - (node.name === 'import' && node.params.match(/^["']tailwindcss\/.*?["']$/)) - ) { - sheet.isTailwindRoot = true - tailwindRootLeafs.add(sheet) - } - }) - } - - // Only a single Tailwind CSS root file exists, no need to do anything else. - if (tailwindRootLeafs.size <= 1) { - return - } - - // Mark the common parent as the root file - { - // Group each sheet from tailwindRootLeafs by their common parent - let commonParents = new DefaultMap>(() => new Set()) - - // Seed common parents with leafs - for (let sheet of tailwindRootLeafs) { - commonParents.get(sheet).add(sheet) - } - - // If any 2 common parents come from the same tree, then all children of - // parent A and parent B will be moved to the parent of parent A and - // parent B. Parent A and parent B will be removed. - let repeat = true - repeat: while (repeat) { - repeat = false - - for (let [sheetA, childrenA] of commonParents) { - for (let [sheetB, childrenB] of commonParents) { - if (sheetA === sheetB) continue - - // Ancestors from self to root. Reversed order so we find the - // nearest common parent first - // - // Including self because if you compare a sheet with its parent, - // then the parent is still the common sheet between the two. In - // this case, the parent is the root file. - let ancestorsA = [sheetA].concat(Array.from(sheetA.ancestors()).reverse()) - let ancestorsB = [sheetB].concat(Array.from(sheetB.ancestors()).reverse()) - - for (let parentA of ancestorsA) { - for (let parentB of ancestorsB) { - if (parentA !== parentB) continue - - // Found the parent - let parent = parentA - - commonParents.delete(sheetA) - commonParents.delete(sheetB) - - for (let child of childrenA) { - commonParents.get(parent).add(child) - } - - for (let child of childrenB) { - commonParents.get(parent).add(child) - } - - // Found a common parent between sheet A and sheet B. We can - // stop looking for more common parents between A and B, and - // continue with the next sheet. - repeat = true - continue repeat - } - } - } - } - } - - // Mark the common parent as the Tailwind CSS root file, and remove the - // flag from each leaf. - for (let [parent, children] of commonParents) { - parent.isTailwindRoot = true - - for (let child of children) { - if (parent === child) continue - - child.isTailwindRoot = false - } - } - return - } - } - - { - let error = `You have one or more stylesheets that are imported into a utility layer and non-utility layer.\n` - error += `We cannot convert stylesheets under these conditions. Please look at the following stylesheets:\n` - - throw new Error(error + lines.join('\n')) - } -} - -export async function linkConfigs( - stylesheets: Stylesheet[], - { configPath, base }: { configPath: string | null; base: string }, -) { - let rootStylesheets = stylesheets.filter((sheet) => sheet.isTailwindRoot) - if (rootStylesheets.length === 0) { - throw new Error( - `Cannot find any CSS files that reference Tailwind CSS.\nBefore your project can be upgraded you need to create a CSS file that imports Tailwind CSS or uses ${highlight('@tailwind')}.`, - ) - } - let withoutAtConfig = rootStylesheets.filter((sheet) => { - let hasConfig = false - sheet.root.walkAtRules('config', (node) => { - let configPath = path.resolve(path.dirname(sheet.file!), node.params.slice(1, -1)) - sheet.linkedConfigPath = configPath - hasConfig = true - return false - }) - return !hasConfig - }) - - // All stylesheets have a `@config` directives - if (withoutAtConfig.length === 0) return - - // Find the config file path for each stylesheet - let configPathBySheet = new Map() - let sheetByConfigPath = new DefaultMap>(() => new Set()) - for (let sheet of withoutAtConfig) { - if (!sheet.file) continue - - let localConfigPath = configPath as string - if (configPath === null) { - localConfigPath = await detectConfigPath(path.dirname(sheet.file), base) - } else if (!path.isAbsolute(localConfigPath)) { - localConfigPath = path.resolve(base, localConfigPath) - } - - configPathBySheet.set(sheet, localConfigPath) - sheetByConfigPath.get(localConfigPath).add(sheet) - } - - let problematicStylesheets = new Set() - for (let sheets of sheetByConfigPath.values()) { - if (sheets.size > 1) { - for (let sheet of sheets) { - problematicStylesheets.add(sheet) - } - } - } - - // There are multiple "root" files without `@config` directives. Manual - // intervention is needed to link to the correct Tailwind config files. - if (problematicStylesheets.size > 1) { - for (let sheet of problematicStylesheets) { - error( - `Could not determine configuration file for: ${highlight(relative(sheet.file!, base))}\nUpdate your stylesheet to use ${highlight('@config')} to specify the correct configuration file explicitly and then run the upgrade tool again.`, - { prefix: '↳ ' }, - ) - } - - process.exit(1) - } - - let relativePath = relative - for (let [sheet, configPath] of configPathBySheet) { - try { - if (!sheet || !sheet.file) return - success( - `Linked ${highlight(relativePath(configPath, base))} to ${highlight(relativePath(sheet.file, base))}`, - { prefix: '↳ ' }, - ) - - // Link the `@config` directive to the root stylesheets - - // Track the config file path on the stylesheet itself for easy access - // without traversing the CSS ast and finding the corresponding - // `@config` later. - sheet.linkedConfigPath = configPath - - // Create a relative path from the current file to the config file. - let relative = path.relative(path.dirname(sheet.file), configPath) - - // If the path points to a file in the same directory, `path.relative` will - // remove the leading `./` and we need to add it back in order to still - // consider the path relative - if (!relative.startsWith('.') && !path.isAbsolute(relative)) { - relative = './' + relative - } - - relative = normalizePath(relative) - - // Add the `@config` directive to the root stylesheet. - { - let target = sheet.root as postcss.Root | postcss.AtRule - let atConfig = postcss.atRule({ name: 'config', params: `'${relative}'` }) - - sheet.root.walkAtRules((node) => { - if (node.name === 'tailwind' || node.name === 'import') { - target = node - } - }) - - if (target.type === 'root') { - sheet.root.prepend(atConfig) - } else if (target.type === 'atrule') { - target.after(atConfig) - } - } - } catch (e: any) { - error('Could not load the configuration file: ' + e.message, { prefix: '↳ ' }) - process.exit(1) - } - } -} - -export async function split(stylesheets: Stylesheet[]) { - let stylesheetsById = new Map() - let stylesheetsByFile = new Map() - - for (let sheet of stylesheets) { - stylesheetsById.set(sheet.id, sheet) - - if (sheet.file) { - stylesheetsByFile.set(sheet.file, sheet) - } - } - - // Keep track of sheets that contain `@utility` rules - let requiresSplit = new Set() - - for (let sheet of stylesheets) { - // Root files don't need to be split - if (sheet.isTailwindRoot) continue - - let containsUtility = false - let containsUnsafe = sheet.layers().size > 0 - - walk(sheet.root, (node) => { - if (node.type === 'atrule' && node.name === 'utility') { - containsUtility = true - } - - // Safe to keep without splitting - else if ( - // An `@import "…" layer(…)` is safe - (node.type === 'atrule' && node.name === 'import' && node.params.includes('layer(')) || - // @layer blocks are safe - (node.type === 'atrule' && node.name === 'layer') || - // Comments are safe - node.type === 'comment' - ) { - return WalkAction.Skip - } - - // Everything else is not safe, and requires a split - else { - containsUnsafe = true - } - - // We already know we need to split this sheet - if (containsUtility && containsUnsafe) { - return WalkAction.Stop - } - - return WalkAction.Skip - }) - - if (containsUtility && containsUnsafe) { - requiresSplit.add(sheet) - } - } - - // Split every imported stylesheet into two parts - let utilitySheets = new Map() - - for (let sheet of stylesheets) { - // Ignore stylesheets that were not imported - if (!sheet.file) continue - if (sheet.parents.size === 0) continue - - // Skip stylesheets that don't have utilities - // and don't have any children that have utilities - if (!requiresSplit.has(sheet)) { - if (!Array.from(sheet.descendants()).some((child) => requiresSplit.has(child))) { - continue - } - } - - let utilities = postcss.root() - - walk(sheet.root, (node) => { - if (node.type !== 'atrule') return - if (node.name !== 'utility') return - - // `append` will move this node from the original sheet - // to the new utilities sheet - utilities.append(node) - - return WalkAction.Skip - }) - - let newFileName = sheet.file.replace(/\.css$/, '.utilities.css') - - let counter = 0 - - // If we already have a utility sheet with this name, we need to rename it - while (stylesheetsByFile.has(newFileName)) { - counter += 1 - newFileName = sheet.file.replace(/\.css$/, `.utilities.${counter}.css`) - } - - let utilitySheet = await Stylesheet.fromRoot(utilities, newFileName) - - utilitySheet.extension = counter > 0 ? `.utilities.${counter}.css` : `.utilities.css` - - utilitySheets.set(sheet, utilitySheet) - stylesheetsById.set(utilitySheet.id, utilitySheet) - } - - // Make sure the utility sheets are linked to one another - for (let [normalSheet, utilitySheet] of utilitySheets) { - for (let parent of normalSheet.parents) { - let utilityParent = utilitySheets.get(parent.item) - if (!utilityParent) continue - utilitySheet.parents.add({ - item: utilityParent, - meta: parent.meta, - }) - } - - for (let child of normalSheet.children) { - let utilityChild = utilitySheets.get(child.item) - if (!utilityChild) continue - utilitySheet.children.add({ - item: utilityChild, - meta: child.meta, - }) - } - } - - for (let sheet of stylesheets) { - let utilitySheet = utilitySheets.get(sheet) - let utilityImports: Set = new Set() - - for (let node of sheet.importRules) { - let sheetId = node.raws.tailwind_destination_sheet_id as StylesheetId | undefined - - // This import rule does not point to a stylesheet - // which likely means it points to `node_modules` - if (!sheetId) continue - - let originalDestination = stylesheetsById.get(sheetId) - - // This import points to a stylesheet that no longer exists which likely - // means it was removed by the optimizer this will be cleaned up later - if (!originalDestination) continue - - let utilityDestination = utilitySheets.get(originalDestination) - - // A utility sheet doesn't exist for this import so it doesn't need - // to be processed - if (!utilityDestination) continue - - let match = node.params.match(/(['"])(.*)\1/) - if (!match) return - - let quote = match[1] - let id = match[2] - - let newFile = id.replace(/\.css$/, utilityDestination.extension!) - - // The import will just point to the new file without any media queries, - // layers, or other conditions because `@utility` MUST be top-level. - let newImport = node.clone({ - params: `${quote}${newFile}${quote}`, - raws: { - tailwind_injected_layer: node.raws.tailwind_injected_layer, - tailwind_original_params: `${quote}${id}${quote}`, - tailwind_destination_sheet_id: utilityDestination.id, - }, - }) - - if (utilitySheet) { - // If this import is intended to go into the utility sheet - // we'll collect it into a list to add later. If we don't' - // we'll end up adding them in reverse order. - utilityImports.add(newImport) - } else { - // This import will go immediately after the original import - node.after(newImport) - } - } - - // Add imports to the top of the utility sheet if necessary - if (utilitySheet && utilityImports.size > 0) { - utilitySheet.root.prepend(Array.from(utilityImports)) - } - } - - // Tracks the at rules that import a given stylesheet - let importNodes = new DefaultMap>(() => new Set()) - - for (let sheet of stylesheetsById.values()) { - for (let node of sheet.importRules) { - let sheetId = node.raws.tailwind_destination_sheet_id as StylesheetId | undefined - - // This import rule does not point to a stylesheet - if (!sheetId) continue - - let destination = stylesheetsById.get(sheetId) - - // This import rule does not point to a stylesheet that exists - // We'll remove it later - if (!destination) continue - - importNodes.get(destination).add(node) - } - } - - // At this point we've created many `{name}.utilities.css` files. - // If the original file _becomes_ empty after splitting that means that - // dedicated utility file is not required and we can move the utilities - // back to the original file. - // - // This could be done in one step but separating them makes it easier to - // reason about since the stylesheets are in a consistent state before we - // perform any cleanup tasks. - let list: Stylesheet[] = [] - - for (let sheet of stylesheets.slice()) { - for (let child of sheet.descendants()) { - list.push(child) - } - - list.push(sheet) - } - - for (let sheet of list) { - let utilitySheet = utilitySheets.get(sheet) - - // This sheet was not split so there's nothing to do - if (!utilitySheet) continue - - // This sheet did not become empty - if (!sheet.isEmpty) continue - - // We have a sheet that became empty after splitting - // 1. Replace the sheet with it's utility sheet content - sheet.root = utilitySheet.root - - // 2. Rewrite imports in parent sheets to point to the original sheet - // Ideally this wouldn't need to be _undone_ but instead only done once at the end - for (let node of importNodes.get(utilitySheet)) { - node.params = node.raws.tailwind_original_params as any - } - - // 3. Remove the original import from the non-utility sheet - for (let node of importNodes.get(sheet)) { - node.remove() - } - - // 3. Mark the utility sheet for removal - utilitySheets.delete(sheet) - } - - stylesheets.push(...utilitySheets.values()) -} diff --git a/packages/@tailwindcss-upgrade/src/template/splice-changes-into-string.ts b/packages/@tailwindcss-upgrade/src/utils/splice-changes-into-string.ts similarity index 100% rename from packages/@tailwindcss-upgrade/src/template/splice-changes-into-string.ts rename to packages/@tailwindcss-upgrade/src/utils/splice-changes-into-string.ts