Skip to content

Commit 4d2c254

Browse files
cursoragentskovhus
andcommitted
fix: preserve helpers in styled call css templates
Co-authored-by: Kenneth Skovhus <skovhus@users.noreply.github.com>
1 parent 909fb7c commit 4d2c254

2 files changed

Lines changed: 113 additions & 1 deletion

File tree

src/__tests__/transform.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1775,6 +1775,43 @@ export const App = () => (
17751775
expect(result.code).toMatch(/const\s+Typography\s*=\s*styled\.div`/);
17761776
});
17771777

1778+
it("preserves css helpers used by universal styled-call css templates", () => {
1779+
const source = `
1780+
import styled, { css } from "styled-components";
1781+
1782+
const baseColor = css\`
1783+
color: navy;
1784+
\`;
1785+
1786+
const Icon = styled.div\`
1787+
width: 16px;
1788+
height: 16px;
1789+
background-color: green;
1790+
\`;
1791+
1792+
const Typography = styled.div(() => css\`
1793+
\${baseColor}
1794+
1795+
& * {
1796+
margin: 0;
1797+
}
1798+
\`);
1799+
1800+
export const App = () => (
1801+
<div>
1802+
<Icon />
1803+
<Typography><span>Text</span></Typography>
1804+
</div>
1805+
);
1806+
`;
1807+
const result = runPartial(source, "partial-universalSelectorStyledCallHelper.input.tsx");
1808+
1809+
expect(result.code).not.toBeNull();
1810+
expect(result.code).toMatch(/const\s+baseColor\s*=\s*css`/);
1811+
expect(result.code).toMatch(/sx=\{styles\.icon\}/);
1812+
expect(result.code).toMatch(/const\s+Typography\s*=\s*styled\.div\(/);
1813+
});
1814+
17781815
it("preserves selector targets referenced through source-kept helper chains", () => {
17791816
const source = `
17801817
import styled, { css } from "styled-components";

src/internal/transform/css-helpers.ts

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,20 @@ type CssHelperSourcePreservationPlan = {
6464
preserveUniversalSelectorHelpers: boolean;
6565
};
6666

67+
type MaybeCssTemplateBody = {
68+
type?: string;
69+
tag?: { type?: string; name?: string };
70+
quasi?: TemplateLiteral;
71+
body?: Array<{
72+
type?: string;
73+
argument?: {
74+
type?: string;
75+
tag?: { type?: string; name?: string };
76+
quasi?: TemplateLiteral;
77+
};
78+
}>;
79+
};
80+
6781
export function removeInlinedCssHelperFunctions(args: {
6882
root: any;
6983
j: JSCodeshift;
@@ -352,6 +366,7 @@ function createCssHelperSourcePreservationPlan(args: {
352366
root,
353367
j,
354368
styledLocalNames,
369+
cssLocal,
355370
noteUniversalSelector,
356371
});
357372
for (const helperName of universalStyledTemplateReferences) {
@@ -438,9 +453,10 @@ function collectCssHelpersUsedByUniversalStyledTemplates(args: {
438453
root: any;
439454
j: JSCodeshift;
440455
styledLocalNames: Set<string>;
456+
cssLocal: string | undefined;
441457
noteUniversalSelector: (template: TemplateLiteral, rawCss: string) => void;
442458
}): Set<string> {
443-
const { root, j, styledLocalNames, noteUniversalSelector } = args;
459+
const { root, j, styledLocalNames, cssLocal, noteUniversalSelector } = args;
444460
const helperNames = new Set<string>();
445461
root.find(j.TaggedTemplateExpression).forEach((path: any) => {
446462
if (!isStyledTag(styledLocalNames, path.node.tag)) {
@@ -460,9 +476,68 @@ function collectCssHelpersUsedByUniversalStyledTemplates(args: {
460476
helperNames,
461477
);
462478
});
479+
if (!cssLocal) {
480+
return helperNames;
481+
}
482+
root
483+
.find(j.VariableDeclarator, {
484+
init: { type: "CallExpression" },
485+
} as any)
486+
.forEach((path: any) => {
487+
const init = path.node.init;
488+
if (
489+
init?.callee?.type !== "MemberExpression" ||
490+
init.callee.object?.type !== "Identifier" ||
491+
!styledLocalNames.has(init.callee.object.name)
492+
) {
493+
return;
494+
}
495+
const firstArg = init.arguments?.[0];
496+
if (firstArg?.type !== "ArrowFunctionExpression") {
497+
return;
498+
}
499+
const template = extractCssTemplateFromArrowBody(firstArg.body, cssLocal);
500+
if (!template) {
501+
return;
502+
}
503+
const { templateExpressions, hasUniversalSelector } = parseCssHelperTemplate({
504+
template,
505+
noteUniversalSelector,
506+
});
507+
if (!hasUniversalSelector) {
508+
return;
509+
}
510+
collectTemplateReferencePaths(templateExpressions, helperNames);
511+
});
463512
return helperNames;
464513
}
465514

515+
function extractCssTemplateFromArrowBody(
516+
body: MaybeCssTemplateBody | null | undefined,
517+
cssLocal: string,
518+
): TemplateLiteral | null {
519+
if (
520+
body?.type === "TaggedTemplateExpression" &&
521+
body.tag?.type === "Identifier" &&
522+
body.tag.name === cssLocal
523+
) {
524+
return body.quasi ?? null;
525+
}
526+
if (body?.type !== "BlockStatement") {
527+
return null;
528+
}
529+
const returnStatement = body.body?.find((stmt) => stmt.type === "ReturnStatement");
530+
const argument = returnStatement?.argument;
531+
if (
532+
argument?.type === "TaggedTemplateExpression" &&
533+
argument.tag?.type === "Identifier" &&
534+
argument.tag.name === cssLocal
535+
) {
536+
return argument.quasi ?? null;
537+
}
538+
return null;
539+
}
540+
466541
function shouldPreserveCssHelperSourceOnly(
467542
plan: CssHelperSourcePreservationPlan,
468543
helperPath: string,

0 commit comments

Comments
 (0)