Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 54 additions & 16 deletions packages/shadcn/src/utils/transformers/transform-import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,67 @@ export const transformImport: Transformer = async ({
}

for (const specifier of sourceFile.getImportStringLiterals()) {
const updated = updateImportAliases(
specifier.getLiteralValue(),
config,
isRemote
)
specifier.setLiteralValue(updated)
const originalValue = specifier.getLiteralValue()

// Replace `import { cn } from "@/lib/utils"`
if (utilsImport === updated || updated === "@/lib/utils") {
// Check if this is a cn import from utils before transforming aliases
let isCnImport = false
// Check for exports first (re-exports don't have import declarations)
const isExport = specifier
.getFirstAncestorByKind(SyntaxKind.ExportDeclaration)
?.getNamedExports()
.some((namedExport) => namedExport.getName() === "cn") ?? false

if (
originalValue === "@/lib/utils" ||
originalValue === "@/src/utils" ||
originalValue === utilsImport ||
originalValue.match(/^@\/registry\/[^/]+\/lib\/utils$/) ||
originalValue.match(/^\/lib\/utils$/) ||
originalValue.match(/^\/src\/utils$/) ||
(config.aliases?.utils && originalValue === config.aliases.utils) ||
(originalValue.startsWith("/") && originalValue.endsWith("/lib/utils")) ||
(originalValue.startsWith("/") && originalValue.endsWith("/src/utils"))
) {
const importDeclaration = specifier.getFirstAncestorByKind(
SyntaxKind.ImportDeclaration
)
const isCnImport = importDeclaration
isCnImport = (importDeclaration
?.getNamedImports()
.some((namedImport) => namedImport.getName() === "cn")
.some((namedImport) => namedImport.getName() === "cn") ?? false) || isExport
}

if (!isCnImport) continue
// Handle sub-path imports from utils (e.g., @/lib/utils/bar or @/src/utils/bar)
const utilsSubPathMatch = originalValue.match(/^(@\/|\/)(lib|src)\/utils\/(.+)$/)
if (utilsSubPathMatch && config.aliases?.utils) {
const [, , , subPath] = utilsSubPathMatch
let utilsPath = config.aliases.utils
const originalAlias = utilsPath
if (!utilsPath.endsWith('/utils')) {
utilsPath += '/utils'
}
const userExplicitlyConfigured = originalAlias.includes('/lib/utils') || originalAlias.includes('/src/utils')
if (!userExplicitlyConfigured && utilsPath.includes('/lib/utils')) {
utilsPath = utilsPath.replace(/\/lib\/utils$/, '/src/utils')
}
specifier.setLiteralValue(`${utilsPath}/${subPath}`)
} else {
const updated = updateImportAliases(originalValue, config, isRemote)
specifier.setLiteralValue(updated)

specifier.setLiteralValue(
utilsImport === updated
? updated.replace(utilsImport, config.aliases.utils)
: config.aliases.utils
)
// Replace `import { cn } from "@/lib/utils"` or `@/src/utils` with configured utils alias
if (isCnImport && config.aliases?.utils) {
let utilsPath = config.aliases.utils
const originalAlias = utilsPath
// If the alias doesn't end with /utils, append it
if (!utilsPath.endsWith('/utils')) {
utilsPath += '/utils'
}
const userExplicitlyConfigured = originalAlias.includes('/lib/utils') || originalAlias.includes('/src/utils')
if (!userExplicitlyConfigured && utilsPath.includes('/lib/utils')) {
utilsPath = utilsPath.replace(/\/lib\/utils$/, '/src/utils')
}
specifier.setLiteralValue(utilsPath)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ import { Foo } from "bar"
import { Label} from "ui/label"
import { Box } from "~/src/components/box"

import { cn, foo, bar } from "~/lib/utils"
import { bar } from "~/lib/utils/bar"
import { cn, foo, bar } from "~/src/utils"
import { bar } from "~/src/utils/bar"
"
`;

Expand All @@ -82,8 +82,8 @@ import { Foo } from "bar"
import { Label} from "ui/label"
import { Box } from "~/src/components/box"

import { cn } from "~/lib/utils"
import { bar } from "~/lib/utils/bar"
import { cn } from "~/src/utils"
import { bar } from "~/src/utils/bar"
"
`;

Expand All @@ -94,8 +94,8 @@ import { Foo } from "bar"
import { Label} from "ui/label"
import { Box } from "~/src/components/box"

import { cn } from "~/lib/utils"
import { bar } from "~/lib/utils/bar"
import { cn } from "~/src/utils"
import { bar } from "~/src/utils/bar"
"
`;

Expand All @@ -106,8 +106,8 @@ import { Foo } from "bar"
import { Label} from "ui/label"
import { Box } from "~/src/components/box"

import { cn } from "~/lib/utils"
import { bar } from "~/lib/utils/bar"
import { cn } from "~/src/utils"
import { bar } from "~/src/utils/bar"
"
`;

Expand All @@ -122,6 +122,28 @@ import { Foo } from "bar"
"
`;

exports[`transform import 7`] = `
"import * as React from "react"
import { Foo } from "bar"
import { Button } from "/components/ui/button"
import { Label} from "ui/label"
import { Box } from "/app/components/box"

import { cn } from "/app/function/lib/utils"
"
`;

exports[`transform import 8`] = `
"import * as React from "react"
import { Foo } from "bar"
import { Button } from "/components/ui/button"
import { Label} from "ui/label"
import { Box } from "/app/components/box"

import { cn } from "/app/function/lib/utils"
"
`;

exports[`transform import for monorepo 1`] = `
"import * as React from "react"
import { Foo } from "bar"
Expand Down
52 changes: 52 additions & 0 deletions packages/shadcn/test/utils/transform-import.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,60 @@ import { Foo } from "bar"
},
})
).toMatchSnapshot()

expect(
await transform({
filename: "test.ts",
raw: `import * as React from "react"
import { Foo } from "bar"
import { Button } from "@/components/ui/button"
import { Label} from "ui/label"
import { Box } from "@/registry/new-york/box"

import { cn } from "/app/function/lib/utils"
`,
config: {
tsx: true,
tailwind: {
baseColor: "neutral",
cssVariables: true,
},
aliases: {
components: "/app/components",
utils: "/app/function/lib/utils",
},
},
})
).toMatchSnapshot()

// Transform @/lib/utils to configured absolute path /app/function/lib/utils
expect(
await transform({
filename: "test.ts",
raw: `import * as React from "react"
import { Foo } from "bar"
import { Button } from "@/components/ui/button"
import { Label} from "ui/label"
import { Box } from "@/registry/new-york/box"

import { cn } from "@/lib/utils"
`,
config: {
tsx: true,
tailwind: {
baseColor: "neutral",
cssVariables: true,
},
aliases: {
components: "/app/components",
utils: "/app/function/lib/utils",
},
},
})
).toMatchSnapshot()
})


test("transform import for monorepo", async () => {
expect(
await transform({
Expand Down