Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(shadcn) arrays and nested deeply nested spread #5711

Merged
merged 9 commits into from
Nov 13, 2024
5 changes: 5 additions & 0 deletions .changeset/empty-pants-care.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"shadcn": patch
---

Update spread/unspread helpers to handle ArrayLiteralExpression and nested values within arrays
116 changes: 108 additions & 8 deletions packages/shadcn/src/utils/updaters/update-tailwind-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import deepmerge from "deepmerge"
import objectToString from "stringify-object"
import { type Config as TailwindConfig } from "tailwindcss"
import {
ArrayLiteralExpression,
ObjectLiteralExpression,
Project,
PropertyAssignment,
Expand Down Expand Up @@ -194,8 +195,11 @@ async function addTailwindConfigTheme(
if (themeInitializer?.isKind(SyntaxKind.ObjectLiteralExpression)) {
const themeObjectString = themeInitializer.getText()
const themeObject = await parseObjectLiteral(themeObjectString)
const result = deepmerge(themeObject, theme)
const result = deepmerge(themeObject, theme, {
arrayMerge: (dst, src) => src,
})
const resultString = objectToString(result)
.replace(/\'\.\.\.(.*)\'/g, "...$1") // Remove quote around spread element
.replace(/\'\"/g, "'") // Replace `\" with "
.replace(/\"\'/g, "'") // Replace `\" with "
.replace(/\'\[/g, "[") // Replace `[ with [
Expand Down Expand Up @@ -287,7 +291,8 @@ export function nestSpreadProperties(obj: ObjectLiteralExpression) {

// Replace spread with a property assignment
obj.insertPropertyAssignment(i, {
name: `___${spreadText.replace(/^\.\.\./, "")}`,
// Need to escape the name with " so that deepmerge doesn't mishandle the key
name: `"___${spreadText.replace(/^\.\.\./, "")}"`,
initializer: `"...${spreadText.replace(/^\.\.\./, "")}"`,
})

Expand All @@ -305,11 +310,41 @@ export function nestSpreadProperties(obj: ObjectLiteralExpression) {
nestSpreadProperties(
initializer.asKindOrThrow(SyntaxKind.ObjectLiteralExpression)
)
} else if (
initializer &&
initializer.isKind(SyntaxKind.ArrayLiteralExpression)
) {
nestSpreadElements(
initializer.asKindOrThrow(SyntaxKind.ArrayLiteralExpression)
)
}
}
}
}

export function nestSpreadElements(arr: ArrayLiteralExpression) {
const elements = arr.getElements()
for (let j = 0; j < elements.length; j++) {
const element = elements[j]
if (element.isKind(SyntaxKind.ObjectLiteralExpression)) {
// Recursive check on objects within arrays
nestSpreadProperties(
element.asKindOrThrow(SyntaxKind.ObjectLiteralExpression)
)
} else if (element.isKind(SyntaxKind.ArrayLiteralExpression)) {
// Recursive check on nested arrays
nestSpreadElements(
element.asKindOrThrow(SyntaxKind.ArrayLiteralExpression)
)
} else if (element.isKind(SyntaxKind.SpreadElement)) {
const spreadText = element.getText()
// Spread element within an array
arr.removeElement(j)
arr.insertElement(j, `"${spreadText}"`)
}
}
}

export function unnestSpreadProperties(obj: ObjectLiteralExpression) {
const properties = obj.getProperties()

Expand All @@ -319,14 +354,49 @@ export function unnestSpreadProperties(obj: ObjectLiteralExpression) {
const propAssignment = prop as PropertyAssignment
const initializer = propAssignment.getInitializer()

if (initializer?.isKind(SyntaxKind.StringLiteral)) {
const value = initializer.getLiteralValue()
if (initializer && initializer.isKind(SyntaxKind.StringLiteral)) {
const value = initializer
.asKindOrThrow(SyntaxKind.StringLiteral)
.getLiteralValue()
if (value.startsWith("...")) {
obj.insertSpreadAssignment(i, { expression: value.slice(3) })
propAssignment.remove()
}
} else if (initializer?.isKind(SyntaxKind.ObjectLiteralExpression)) {
unnestSpreadProperties(initializer as ObjectLiteralExpression)
} else if (
initializer &&
initializer.isKind(SyntaxKind.ArrayLiteralExpression)
) {
unnsetSpreadElements(
initializer.asKindOrThrow(SyntaxKind.ArrayLiteralExpression)
)
}
}
}
}

export function unnsetSpreadElements(arr: ArrayLiteralExpression) {
const elements = arr.getElements()
for (let j = 0; j < elements.length; j++) {
const element = elements[j]
if (element.isKind(SyntaxKind.ObjectLiteralExpression)) {
// Recursive check on objects within arrays
unnestSpreadProperties(
element.asKindOrThrow(SyntaxKind.ObjectLiteralExpression)
)
} else if (element.isKind(SyntaxKind.ArrayLiteralExpression)) {
// Recursive check on nested arrays
unnsetSpreadElements(
element.asKindOrThrow(SyntaxKind.ArrayLiteralExpression)
)
} else if (element.isKind(SyntaxKind.StringLiteral)) {
const spreadText = element.getText()
// check if spread element
const spreadTest = /(?:^['"])(\.\.\..*)(?:['"]$)/g
if (spreadTest.test(spreadText)) {
arr.removeElement(j)
arr.insertElement(j, spreadText.replace(spreadTest, "$1"))
}
}
}
Expand Down Expand Up @@ -363,6 +433,12 @@ function parseObjectLiteralExpression(node: ObjectLiteralExpression): any {
result[name] = parseObjectLiteralExpression(
property.getInitializer() as ObjectLiteralExpression
)
} else if (
property.getInitializer()?.isKind(SyntaxKind.ArrayLiteralExpression)
) {
result[name] = parseArrayLiteralExpression(
property.getInitializer() as ArrayLiteralExpression
)
} else {
result[name] = parseValue(property.getInitializer())
}
Expand All @@ -371,20 +447,44 @@ function parseObjectLiteralExpression(node: ObjectLiteralExpression): any {
return result
}

function parseArrayLiteralExpression(node: ArrayLiteralExpression): any[] {
const result: any[] = []
for (const element of node.getElements()) {
if (element.isKind(SyntaxKind.ObjectLiteralExpression)) {
result.push(
parseObjectLiteralExpression(
element.asKindOrThrow(SyntaxKind.ObjectLiteralExpression)
)
)
} else if (element.isKind(SyntaxKind.ArrayLiteralExpression)) {
result.push(
parseArrayLiteralExpression(
element.asKindOrThrow(SyntaxKind.ArrayLiteralExpression)
)
)
} else {
result.push(parseValue(element))
}
}
return result
}

function parseValue(node: any): any {
switch (node.kind) {
switch (node.getKind()) {
case SyntaxKind.StringLiteral:
return node.text
return node.getText()
case SyntaxKind.NumericLiteral:
return Number(node.text)
return Number(node.getText())
case SyntaxKind.TrueKeyword:
return true
case SyntaxKind.FalseKeyword:
return false
case SyntaxKind.NullKeyword:
return null
case SyntaxKind.ArrayLiteralExpression:
return node.elements.map(parseValue)
return node.getElements().map(parseValue)
case SyntaxKind.ObjectLiteralExpression:
return parseObjectLiteralExpression(node)
default:
return node.getText()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,8 +324,14 @@ const config: Config = {
theme: {
extend: {
fontFamily: {
sans: ["var(--font-geist-sans)", ...fontFamily.sans],
mono: ["var(--font-mono)", ...fontFamily.mono],
sans: [
'var(--font-geist-sans)',
...fontFamily.sans
],
mono: [
'var(--font-mono)',
...fontFamily.mono
],
heading: [
'var(--font-geist-sans)'
]
Expand Down Expand Up @@ -369,6 +375,75 @@ export default config
"
`;

exports[`transformTailwindConfig -> theme > should handle objects nested in arrays 1`] = `
"import type { Config } from 'tailwindcss'

const config: Config = {
darkMode: ["class"],
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
fontSize: {
xs: [
'0.75rem',
{
lineHeight: '1rem'
}
],
sm: [
'0.875rem',
{
lineHeight: '1.25rem'
}
],
xl: [
'clamp(1.5rem, 1.04vi + 1.17rem, 2rem)',
{
lineHeight: '1.2',
letterSpacing: '-0.02em',
fontWeight: '600'
}
]
}
}
},
}
export default config
"
`;

exports[`transformTailwindConfig -> theme > should keep arrays when formatted on multilines 1`] = `
"import type { Config } from 'tailwindcss'

const config: Config = {
darkMode: ["class"],
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
fontFamily: {
sans: [
'Figtree',
...defaultTheme.fontFamily.sans
],
mono: [
'Foo'
]
}
}
},
}
export default config
"
`;

exports[`transformTailwindConfig -> theme > should keep quotes in strings 1`] = `
"import type { Config } from 'tailwindcss'

Expand All @@ -382,7 +457,10 @@ const config: Config = {
theme: {
extend: {
fontFamily: {
sans: ['Figtree', ...defaultTheme.fontFamily.sans]
sans: [
'Figtree',
...defaultTheme.fontFamily.sans
]
},
colors: {
...defaultColors,
Expand Down Expand Up @@ -448,6 +526,12 @@ const config: Config = {
],
theme: {
extend: {
fontFamily: {
sans: [
'ui-sans-serif',
'sans-serif'
]
},
colors: {
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
Expand All @@ -466,3 +550,23 @@ const config: Config = {
export default config
"
`;

exports[`transformTailwindConfig -> theme > should preserve boolean values 1`] = `
"import type { Config } from 'tailwindcss'

const config: Config = {
darkMode: ["class"],
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
container: {
center: true
}
},
}
export default config
"
`;
Loading
Loading