Skip to content

Commit 6728754

Browse files
authored
P1: [Darkside] Primitives spacing -> space migrations (#3597)
* ✨ Setup darkside migration * ✨ Can now convert primitives spacing * 🧪 Added more tests * 📝 Docs for utils * 📝 Changeset * 📝 Typos * [Darkside] ✨ Added migration for css, less and scss (#3598) * ✨ Added migration for css, less and scsss * Update @navikt/aksel/src/codemod/migrations.ts * ✨ Make old px spacing space-1 * ✨ Use esbuild to inline local references * ✨ Added js migration to space-tokens (#3600) * ♻️ Use eslint-bundle as main build for CLI * ♻️ Add mappings exports in ds-css, avoid using esbuild
1 parent bf76336 commit 6728754

32 files changed

+804
-1
lines changed

.changeset/heavy-snails-draw.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@navikt/aksel": minor
3+
---
4+
5+
CLI: Added primitives migration to new space-tokens. Run `npx @navikt/aksel migration primitive-spacing` to start migrating now. This update is supported for old and new (darkside) system.

.changeset/heavy-snails-draws.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@navikt/aksel": minor
3+
---
4+
5+
CLI: Added js/ts migration to new space-tokens. Run `npx @navikt/aksel migration token-spacing-js` to start migrating now. This update is supported for old and new (darkside) system.

@navikt/aksel/src/codemod/migrations.ts

+20
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,26 @@ export const migrations: {
9797
"Remember to update use of `variant`-prop to match previous use of colors. If needed the component exposes css-variables for custom overrides",
9898
},
9999
],
100+
darkside: [
101+
{
102+
description:
103+
"Updates all Primitives to use new `space`-tokens. (Works with old and new system)",
104+
value: "primitive-spacing",
105+
path: "darkside/primitives-spacing/spacing",
106+
},
107+
{
108+
description:
109+
"Updates css, scss and less-variables to use new `space`-tokens. (Works with old and new system)",
110+
value: "token-spacing",
111+
path: "darkside/token-spacing/spacing",
112+
},
113+
{
114+
description:
115+
"Updates js-tokens to use new `space`-tokens. (Works with old and new system)",
116+
value: "token-spacing-js",
117+
path: "darkside/token-spacing-js/spacing",
118+
},
119+
],
100120
};
101121

102122
export function getMigrationPath(str: string) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import type {
2+
API,
3+
ASTPath,
4+
FileInfo,
5+
ImportDeclaration,
6+
JSXElement,
7+
JSXOpeningElement,
8+
} from "jscodeshift";
9+
10+
/**
11+
* Finds a component import, accounting for sub-components and aliases.
12+
* Returns the local name of the component. If the component is not found, returns null.
13+
*/
14+
function findComponentImport(input: {
15+
j: API["j"];
16+
file: FileInfo;
17+
name: string;
18+
packageType?: "react" | "tokens";
19+
}) {
20+
const { j, file, name: _name, packageType = "react" } = input;
21+
22+
/* Account for sub-components */
23+
const name = _name.includes(".") ? _name.split(".")[0] : _name;
24+
25+
const root = j(file.source);
26+
27+
let foundName: string | null = null;
28+
29+
root.find(j.ImportDeclaration).forEach((path) => {
30+
if (packageType === "react" && !isAkselReactImport(path)) {
31+
return;
32+
}
33+
34+
if (packageType === "tokens" && !isAkselTokensImport(path)) {
35+
return;
36+
}
37+
38+
path.node.specifiers.forEach((specifier) => {
39+
if (
40+
specifier.type === "ImportSpecifier" &&
41+
specifier.imported.name === name
42+
) {
43+
foundName = specifier.local
44+
? specifier.local.name
45+
: specifier.imported.name;
46+
}
47+
});
48+
});
49+
50+
return foundName;
51+
}
52+
53+
/**
54+
* Finds a JSX element in the AST, accounting for sub-components.
55+
*/
56+
function findJSXElement(input: {
57+
root: ReturnType<API["j"]>;
58+
j: API["j"];
59+
name: string;
60+
originalName: string;
61+
}) {
62+
const { root, j, name, originalName } = input;
63+
64+
const isSubComponent = originalName.includes(".");
65+
66+
const openingElement = (
67+
isSubComponent
68+
? {
69+
name: {
70+
type: "JSXMemberExpression",
71+
object: {
72+
name,
73+
},
74+
property: {
75+
name: originalName.split(".")[1],
76+
},
77+
},
78+
}
79+
: {
80+
name: {
81+
type: "JSXIdentifier",
82+
name,
83+
},
84+
}
85+
) as JSXOpeningElement;
86+
87+
return root.find(j.JSXElement, {
88+
openingElement,
89+
});
90+
}
91+
92+
/**
93+
* Finds a prop in a JSX element.
94+
*/
95+
function findProp(input: {
96+
j: API["j"];
97+
path: ASTPath<JSXElement>;
98+
name: string;
99+
}) {
100+
const { j, path, name } = input;
101+
102+
return j(path).find(j.JSXAttribute, {
103+
name: {
104+
name,
105+
},
106+
});
107+
}
108+
109+
/**
110+
* Checks if an import is from @navikt/ds-react.
111+
*/
112+
function isAkselReactImport(path: ASTPath<ImportDeclaration>) {
113+
const importSource = path.node.source.value;
114+
return (
115+
typeof importSource === "string" &&
116+
importSource.startsWith("@navikt/ds-react")
117+
);
118+
}
119+
120+
/**
121+
* Checks if an import is from @navikt/ds-tokens.
122+
*/
123+
function isAkselTokensImport(path: ASTPath<ImportDeclaration>) {
124+
const importSource = path.node.source.value;
125+
return (
126+
typeof importSource === "string" &&
127+
importSource.startsWith("@navikt/ds-tokens")
128+
);
129+
}
130+
131+
/**
132+
* Maps old spacing-token values to new space-tokens
133+
*/
134+
const legacySpacingTokenMap = new Map<string, string>([
135+
["32", "128"],
136+
["24", "96"],
137+
["20", "80"],
138+
["18", "72"],
139+
["16", "64"],
140+
["14", "56"],
141+
["12", "48"],
142+
["11", "44"],
143+
["10", "40"],
144+
["9", "36"],
145+
["8", "32"],
146+
["7", "28"],
147+
["6", "24"],
148+
["5", "20"],
149+
["4", "16"],
150+
["3", "12"],
151+
["2", "8"],
152+
["1-alt", "6"],
153+
["1", "4"],
154+
["05", "2"],
155+
["0", "0"],
156+
]);
157+
158+
function convertSpacingToSpace(spacing: string): string | null {
159+
return legacySpacingTokenMap.get(spacing) || null;
160+
}
161+
162+
export {
163+
findComponentImport,
164+
findJSXElement,
165+
findProp,
166+
convertSpacingToSpace,
167+
legacySpacingTokenMap,
168+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import type { API, FileInfo } from "jscodeshift";
2+
import { getLineTerminator } from "../../../utils/lineterminator";
3+
import {
4+
findComponentImport,
5+
findJSXElement,
6+
findProp,
7+
legacySpacingTokenMap,
8+
} from "../darkside.utils";
9+
10+
export default function transformer(file: FileInfo, api: API) {
11+
const j = api.jscodeshift;
12+
const root = j(file.source);
13+
14+
/**
15+
* Migrate Primitives to `space` from `spacing`
16+
*/
17+
const primitives = [
18+
"Box",
19+
"Box.New",
20+
"BoxNew",
21+
"Hgrid",
22+
"Stack",
23+
"HStack",
24+
"VStack",
25+
"Bleed",
26+
];
27+
28+
const affectedProps = [
29+
"padding",
30+
"paddingInline",
31+
"paddingBlock",
32+
"margin",
33+
"marginInline",
34+
"marginBlock",
35+
"inset",
36+
"top",
37+
"right",
38+
"bottom",
39+
"left",
40+
"gap",
41+
];
42+
43+
for (const primitive of primitives) {
44+
const name = findComponentImport({ file, j, name: primitive });
45+
if (!name) {
46+
continue;
47+
}
48+
49+
findJSXElement({
50+
root,
51+
j,
52+
name,
53+
originalName: primitive,
54+
}).forEach((path) => {
55+
for (const prop of affectedProps) {
56+
findProp({ j, path, name: prop }).forEach((attr) => {
57+
const attrValue = attr.value.value;
58+
59+
if (attrValue.type === "StringLiteral") {
60+
/* padding="32" */
61+
attrValue.value = convertSpacingToSpace(attrValue.value);
62+
} else if (attrValue.type === "JSXExpressionContainer") {
63+
/* padding={{xs: "16", sm: "32"}} */
64+
const expression = attrValue.expression;
65+
if (expression.type === "ObjectExpression") {
66+
/* xs, md, sm */
67+
expression.properties.forEach((property) => {
68+
if (property.type === "ObjectProperty") {
69+
if (property.value.type === "StringLiteral") {
70+
property.value.value = convertSpacingToSpace(
71+
property.value.value,
72+
);
73+
}
74+
}
75+
});
76+
}
77+
}
78+
});
79+
}
80+
});
81+
}
82+
83+
return root.toSource(getLineTerminator(file.source));
84+
}
85+
86+
/**
87+
* Takes an old valid spacing-token and returns the new converted space-token
88+
* oldValue: "8", "8 10", "8 auto", "auto auto", "full px"
89+
* @returns "space-32", "space-32 space-40"
90+
*/
91+
function convertSpacingToSpace(oldValue: string): string {
92+
const spacingTokens = oldValue.split(" ");
93+
94+
const newSpacing = [];
95+
for (const spacingToken of spacingTokens) {
96+
if (spacingToken === "px") {
97+
/* We replace "px" with new `space-1` */
98+
newSpacing.push(`space-1`);
99+
} else if (
100+
["auto", "full", "px"].includes(spacingToken) ||
101+
spacingToken.startsWith("space-")
102+
) {
103+
newSpacing.push(spacingToken);
104+
} else if (!legacySpacingTokenMap.get(spacingToken)) {
105+
console.warn(`Possibly invalid spacing token found: ${spacingToken}\n`);
106+
newSpacing.push(spacingToken);
107+
} else {
108+
newSpacing.push(`space-${legacySpacingTokenMap.get(spacingToken)}`);
109+
}
110+
}
111+
112+
return newSpacing.join(" ");
113+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Box } from "@navikt/ds-react/Box";
2+
3+
export const Demo = () => (
4+
<Box
5+
before="before"
6+
marginInline={{ xs: "auto", sm: "24", md: "32 11" }}
7+
padding="24"
8+
paddingInline="12 32"
9+
after="after"
10+
>
11+
<div>ELEMENT</div>
12+
</Box>
13+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Box } from "@navikt/ds-react/Box";
2+
3+
export const Demo = () => (
4+
<Box
5+
before="before"
6+
marginInline={{ xs: "auto", sm: "space-96", md: "space-128 space-44" }}
7+
padding="space-96"
8+
paddingInline="space-48 space-128"
9+
after="after"
10+
>
11+
<div>ELEMENT</div>
12+
</Box>
13+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Box } from "@navikt/ds-react";
2+
3+
export const Demo = () => (
4+
<Box
5+
before="before"
6+
marginInline={{ xs: "auto", sm: "space-96", md: "space-128 space-44" }}
7+
padding="space-96"
8+
paddingInline="space-48 space-128"
9+
after="after"
10+
>
11+
<div>ELEMENT</div>
12+
</Box>
13+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Box } from "@navikt/ds-react";
2+
3+
export const Demo = () => (
4+
<Box
5+
before="before"
6+
marginInline={{ xs: "auto", sm: "space-96", md: "space-128 space-44" }}
7+
padding="space-96"
8+
paddingInline="space-48 space-128"
9+
after="after"
10+
>
11+
<div>ELEMENT</div>
12+
</Box>
13+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Box } from "@navikt/ds-react";
2+
3+
export const Demo = () => (
4+
<Box
5+
before="before"
6+
marginInline={{ xs: "auto", sm: "24", md: "32 11" }}
7+
padding="24"
8+
paddingInline="12 32"
9+
after="after"
10+
>
11+
<div>ELEMENT</div>
12+
</Box>
13+
);

0 commit comments

Comments
 (0)