@commercetools-frontend/codemod 21.24.3 → 21.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -41,3 +41,11 @@ Rename `.mod.css` files to `.module.css` and update imports.
41
41
  ```
42
42
  $ npx @commercetools-frontend/codemod@latest rename-mod-css-to-module-css 'src/**/*.{js,jsx,ts,tsx}'
43
43
  ```
44
+
45
+ ### `redesign-cleanup`
46
+
47
+ Remove code related to the old design when using the `useTheme` hook, for example the usage of `themedValue`.
48
+
49
+ ```
50
+ $ npx @commercetools-frontend/codemod@latest redesign-cleanup 'src/**/*.{jsx,tsx}'
51
+ ```
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commercetools-frontend/codemod",
3
- "version": "21.24.3",
3
+ "version": "21.25.1",
4
4
  "description": "Codemod transformations for Custom Applications",
5
5
  "bugs": "https://github.com/commercetools/merchant-center-application-kit/issues",
6
6
  "repository": {
@@ -26,13 +26,14 @@
26
26
  "dependencies": {
27
27
  "cac": "6.7.14",
28
28
  "glob": "8.1.0",
29
- "jscodeshift": "0.14.0"
29
+ "jscodeshift": "0.14.0",
30
+ "prettier": "2.8.4"
30
31
  },
31
32
  "devDependencies": {
32
33
  "@tsconfig/node16": "^1.0.3",
33
34
  "@types/jscodeshift": "0.11.6",
34
35
  "rimraf": "3.0.2",
35
- "typescript": "4.9.5"
36
+ "typescript": "5.0.4"
36
37
  },
37
38
  "engines": {
38
39
  "node": "14.x || >=16.0.0"
package/build/src/cli.js CHANGED
@@ -24,6 +24,10 @@ const transforms = [
24
24
  name: 'rename-mod-css-to-module-css',
25
25
  description: 'Rename ".mod.css" files to ".module.css" and update imports.',
26
26
  },
27
+ {
28
+ name: 'redesign-cleanup',
29
+ description: 'Remove code related to the old design when using the "useTheme" hook, for example the usage of "themedValue".',
30
+ },
27
31
  ];
28
32
  const executeCodemod = async (transform, globPattern, globalOptions) => {
29
33
  const files = glob_1.default.sync(globPattern);
@@ -31,6 +35,7 @@ const executeCodemod = async (transform, globPattern, globalOptions) => {
31
35
  await Runner_1.default.run(transformPath, filePaths, options);
32
36
  };
33
37
  switch (transform) {
38
+ case 'redesign-cleanup':
34
39
  case 'remove-deprecated-modal-level-props':
35
40
  case 'rename-js-to-jsx':
36
41
  case 'rename-mod-css-to-module-css': {
@@ -0,0 +1,389 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const prettier_1 = __importDefault(require("prettier"));
7
+ function getArgumentValue(argument, j) {
8
+ let computedArgumentValue;
9
+ switch (argument.type) {
10
+ case 'StringLiteral':
11
+ computedArgumentValue = argument;
12
+ break;
13
+ default:
14
+ computedArgumentValue = j.jsxExpressionContainer(argument);
15
+ break;
16
+ }
17
+ return computedArgumentValue;
18
+ }
19
+ function isArgumentValueNullish(argumentValue, j) {
20
+ return (!argumentValue ||
21
+ (j.StringLiteral.check(argumentValue) && !argumentValue.value) ||
22
+ (j.JSXExpressionContainer.check(argumentValue) &&
23
+ j.Identifier.check(argumentValue.expression) &&
24
+ argumentValue.expression.name === 'undefined') ||
25
+ (j.JSXExpressionContainer.check(argumentValue) &&
26
+ j.NullLiteral.check(argumentValue.expression)));
27
+ }
28
+ // Update props in components which use `themedValue` helper to always use new theme value
29
+ // Remove the prop in case the new theme value is undefined
30
+ // Some examples to process:
31
+ // <Spacings.Stack scale={themedValue('m', 'l')}>
32
+ //
33
+ // <Spacings.Stack scale={themedValue(designTokens.A, designTokensB)}>
34
+ //
35
+ // <div
36
+ // css={css`
37
+ // color: ${customProperties.colorSurface};
38
+ // text-align: ${themedValue('left', 'center')};
39
+ // `}
40
+ // >
41
+ function updateThemedValueUsages(tree, j) {
42
+ // Props using themedValue helper
43
+ // Example:
44
+ // <CheckboxIconWrapper
45
+ // width={themedValue('auto', '26px')}
46
+ // height={themedValue('auto', '26px')}
47
+ // isHovered={canForcedHoverEffect}
48
+ // >
49
+ tree
50
+ .find(j.JSXAttribute, {
51
+ value: {
52
+ expression: {
53
+ callee: {
54
+ name: 'themedValue',
55
+ },
56
+ },
57
+ },
58
+ })
59
+ .replaceWith((attribute) => {
60
+ const { node } = attribute;
61
+ if (node.value?.type === 'JSXExpressionContainer' &&
62
+ node.value?.expression.type === 'CallExpression') {
63
+ const secondArgument = node.value.expression.arguments[1];
64
+ let computedArgumentValue = getArgumentValue(secondArgument, j);
65
+ // In case the value for the new theme is nullish,
66
+ // we remove the property altogether
67
+ if (isArgumentValueNullish(computedArgumentValue, j)) {
68
+ return null;
69
+ }
70
+ else {
71
+ attribute.node.value = computedArgumentValue;
72
+ }
73
+ }
74
+ return attribute.node;
75
+ });
76
+ // Variables used to proxy a themed value.
77
+ // This section removes the proxy component and updates its references/
78
+ //
79
+ // Example:
80
+ // const Button = themedValue(IconButton, SecondaryIconButton);
81
+ // <Button
82
+ // label={intl.formatMessage(messages.hideNotification)}
83
+ // onClick={props.onCloseClick}
84
+ // icon={<CloseBoldIcon />}
85
+ // size="medium"
86
+ // />
87
+ const variablesToUpdate = [];
88
+ tree
89
+ .find(j.VariableDeclaration)
90
+ .filter((nodePath) => {
91
+ const declaration = nodePath.node.declarations[0];
92
+ return (j.VariableDeclarator.check(declaration) &&
93
+ j.CallExpression.check(declaration.init) &&
94
+ j.Identifier.check(declaration.init.callee) &&
95
+ declaration.init.callee.name === 'themedValue');
96
+ })
97
+ .forEach((nodePath) => {
98
+ const declaration = nodePath.node.declarations[0];
99
+ const variableName = declaration.id.name;
100
+ const newThemeValue = declaration.init.arguments[1];
101
+ variablesToUpdate.push(declaration);
102
+ tree
103
+ .find(j.JSXElement, {
104
+ openingElement: {
105
+ name: {
106
+ name: variableName,
107
+ },
108
+ },
109
+ })
110
+ .forEach((nodePath) => {
111
+ if (j.Identifier.check(newThemeValue)) {
112
+ nodePath.node.openingElement.name = j.jsxIdentifier(newThemeValue.name);
113
+ }
114
+ if (j.MemberExpression.check(newThemeValue)) {
115
+ nodePath.node.openingElement.name = j.jsxMemberExpression(j.jsxIdentifier(newThemeValue.object.name), j.jsxIdentifier(newThemeValue.property.name));
116
+ }
117
+ if (nodePath.node.closingElement) {
118
+ if (j.Identifier.check(newThemeValue)) {
119
+ nodePath.node.closingElement.name = j.jsxIdentifier(newThemeValue.name);
120
+ }
121
+ if (j.MemberExpression.check(newThemeValue)) {
122
+ nodePath.node.closingElement.name = j.jsxMemberExpression(j.jsxIdentifier(newThemeValue.object.name), j.jsxIdentifier(newThemeValue.property.name));
123
+ }
124
+ }
125
+ });
126
+ tree
127
+ .find(j.Identifier, {
128
+ name: variableName,
129
+ })
130
+ .filter((nodePath) => {
131
+ return (j.JSXExpressionContainer.check(nodePath.parent.node) ||
132
+ j.CallExpression.check(nodePath.parent.node));
133
+ })
134
+ .replaceWith(() => {
135
+ return newThemeValue;
136
+ });
137
+ })
138
+ .remove();
139
+ // Rest of use cases like template literals or direct JSX
140
+ // Example 1:
141
+ // <div
142
+ // css={[
143
+ // css`
144
+ // width: ${themedValue('16px', '18px')};
145
+ // height: ${themedValue('16px', '18px')};
146
+ //
147
+ // Example 2:
148
+ // <li className={styles['list-item']} key={val.id}>
149
+ // {themedValue(
150
+ // <Spacings.Inset scale="s">
151
+ // <ValueCheckbox
152
+ // onSelection={props.onSelection}
153
+ // id={val.id}
154
+ // obj={val.obj}
155
+ // onRenderItemName={props.onRenderItemName}
156
+ // />
157
+ // </Spacings.Inset>,
158
+ // <ValueCheckbox
159
+ // onSelection={props.onSelection}
160
+ // id={val.id}
161
+ // obj={val.obj}
162
+ // onRenderItemName={props.onRenderItemName}
163
+ // />
164
+ // )}
165
+ // </li>
166
+ tree
167
+ .find(j.CallExpression, {
168
+ callee: {
169
+ name: 'themedValue',
170
+ },
171
+ })
172
+ .replaceWith((callExpression) => {
173
+ const secondArgument = callExpression.node.arguments[1];
174
+ let computedArgumentValue = getArgumentValue(secondArgument, j);
175
+ if (isArgumentValueNullish(computedArgumentValue, j)) {
176
+ return null;
177
+ }
178
+ else {
179
+ return callExpression.node.arguments[1];
180
+ }
181
+ });
182
+ }
183
+ // Process the useTheme hook to remove it or updated it
184
+ // (maybe we're also using "theme" property from the hook)
185
+ function processUseThemeHook(tree, j) {
186
+ let wasHookRemoved = false;
187
+ // Skip if there are calls to "themedValue" function
188
+ const themedValueCalls = tree.find(j.CallExpression, {
189
+ callee: {
190
+ name: 'themedValue',
191
+ },
192
+ });
193
+ // Skip if "themeValue" is passed as a parameter to another function
194
+ const themeValueArguments = tree.find(j.CallExpression, {
195
+ arguments(values) {
196
+ return values.some((value) => j.Identifier.check(value) && value.name === 'themedValue');
197
+ },
198
+ });
199
+ if (themedValueCalls.length > 0 || themeValueArguments.length > 0) {
200
+ return wasHookRemoved;
201
+ }
202
+ tree
203
+ .find(j.VariableDeclaration, {
204
+ declarations(values) {
205
+ const [declaration] = values;
206
+ return (declaration.type === 'VariableDeclarator' &&
207
+ declaration.init?.type === 'CallExpression' &&
208
+ declaration.init.callee.type === 'Identifier' &&
209
+ declaration.init.callee.name === 'useTheme' &&
210
+ declaration.id.type === 'ObjectPattern');
211
+ },
212
+ })
213
+ .replaceWith((value) => {
214
+ const declaration = value.node.declarations[0];
215
+ if (declaration.id.type === 'ObjectPattern') {
216
+ // Remove the declaration altogether if we were only getting the `themedValue`
217
+ // helper from the `useTheme` hook
218
+ // Ex: const { themedValue } = useTheme();
219
+ if (declaration.id.properties.length === 1 &&
220
+ declaration.id.properties[0].type === 'ObjectProperty' &&
221
+ declaration.id.properties[0].value.type === 'Identifier' &&
222
+ declaration.id.properties[0].value.name === 'themedValue') {
223
+ wasHookRemoved = true;
224
+ return null;
225
+ }
226
+ // Update the destructuring of the `useTheme` hook to remove the `themedValue`
227
+ // const when there were more helpers in the destructuring
228
+ // Ex: const { theme, themedValue } = useTheme();
229
+ const index = declaration.id.properties.findIndex((prop) => prop.type === 'ObjectProperty' &&
230
+ prop.key.type === 'Identifier' &&
231
+ prop.key.name === 'themedValue');
232
+ if (index !== -1) {
233
+ declaration.id.properties.splice(index, 1);
234
+ }
235
+ }
236
+ return value.node;
237
+ });
238
+ return wasHookRemoved;
239
+ }
240
+ function removeUnusedImports(tree, j) {
241
+ let isUseThemeHookRemoved = false;
242
+ // Find all the used identifiers in the file
243
+ const usedIdentifiersMap = new Map();
244
+ tree.find(j.Identifier).forEach((identifier) => {
245
+ if ((j.Identifier.check(identifier.node) &&
246
+ !j.ImportSpecifier.check(identifier.parent.node) &&
247
+ !j.ImportDefaultSpecifier.check(identifier.parent.node)) ||
248
+ j.JSXIdentifier.check(identifier.node)) {
249
+ const previousCount = usedIdentifiersMap.get(identifier.node.name) || 0;
250
+ usedIdentifiersMap.set(identifier.node.name, previousCount + 1);
251
+ }
252
+ });
253
+ // Find types annotations
254
+ tree
255
+ .find(j.CallExpression)
256
+ .filter((callExpression) => {
257
+ return j.TSTypeParameterInstantiation.check(callExpression.node
258
+ .typeParameters);
259
+ })
260
+ .forEach((callExpression) => {
261
+ callExpression.node.typeParameters?.params.forEach((param) => {
262
+ if (j.TSTypeReference.check(param) &&
263
+ j.Identifier.check(param.typeName)) {
264
+ const previousCount = usedIdentifiersMap.get(param.typeName.name) || 0;
265
+ usedIdentifiersMap.set(param.typeName.name, previousCount + 1);
266
+ }
267
+ if (j.TSArrayType.check(param) &&
268
+ j.TSTypeReference.check(param.elementType) &&
269
+ j.Identifier.check(param.elementType.typeName)) {
270
+ const name = param.elementType.typeName.name;
271
+ const previousCount = usedIdentifiersMap.get(name) || 0;
272
+ usedIdentifiersMap.set(name, previousCount + 1);
273
+ }
274
+ // Declaration of a custom type for a function parameter
275
+ if (j.TSTypeLiteral.check(param)) {
276
+ param.members.forEach((propSignature) => {
277
+ // TODO: This is to satisfy TS. Can we do it simpler?
278
+ if (j.TSPropertySignature.check(propSignature) &&
279
+ j.TSTypeAnnotation.check(propSignature.typeAnnotation) &&
280
+ j.TSTypeReference.check(propSignature.typeAnnotation.typeAnnotation) &&
281
+ j.Identifier.check(propSignature.typeAnnotation.typeAnnotation.typeName)) {
282
+ const previousCount = usedIdentifiersMap.get(propSignature.typeAnnotation.typeAnnotation.typeName.name) || 0;
283
+ usedIdentifiersMap.set(propSignature.typeAnnotation.typeAnnotation.typeName.name, previousCount + 1);
284
+ }
285
+ });
286
+ }
287
+ // Return types in function types
288
+ if (j.TSFunctionType.check(param) &&
289
+ j.TSTypeReference.check(param.typeAnnotation?.typeAnnotation)) {
290
+ const typeName = (param.typeAnnotation?.typeAnnotation.typeName).name;
291
+ const previousCount = usedIdentifiersMap.get(typeName) || 0;
292
+ usedIdentifiersMap.set(typeName, previousCount + 1);
293
+ param.typeAnnotation?.typeAnnotation.typeParameters?.params.forEach((annotationParam) => {
294
+ const annotationParamName = j.TSTypeReference.check(annotationParam) &&
295
+ annotationParam.typeName.name;
296
+ if (annotationParamName) {
297
+ const previousCount = usedIdentifiersMap.get(annotationParamName) || 0;
298
+ usedIdentifiersMap.set(annotationParamName, previousCount + 1);
299
+ }
300
+ });
301
+ }
302
+ // Find TS types in function types parameters signatures
303
+ if (j.TSFunctionType.check(param)) {
304
+ param.parameters.forEach((fnParam) => {
305
+ let typeName = undefined;
306
+ if (j.Identifier.check(fnParam) &&
307
+ j.TSTypeReference.check(fnParam.typeAnnotation?.typeAnnotation) &&
308
+ j.Identifier.check(fnParam.typeAnnotation?.typeAnnotation.typeName)) {
309
+ typeName = fnParam.typeAnnotation?.typeAnnotation.typeName.name;
310
+ }
311
+ if (j.Identifier.check(fnParam) &&
312
+ j.TSArrayType.check(fnParam.typeAnnotation?.typeAnnotation) &&
313
+ j.TSTypeReference.check(fnParam.typeAnnotation?.typeAnnotation.elementType) &&
314
+ j.Identifier.check(fnParam.typeAnnotation?.typeAnnotation.elementType.typeName)) {
315
+ typeName =
316
+ fnParam.typeAnnotation?.typeAnnotation.elementType.typeName
317
+ .name;
318
+ }
319
+ if (typeName) {
320
+ const previousCount = usedIdentifiersMap.get(typeName) || 0;
321
+ usedIdentifiersMap.set(typeName, previousCount + 1);
322
+ }
323
+ });
324
+ }
325
+ });
326
+ });
327
+ // Remove unused imports
328
+ tree
329
+ .find(j.ImportDeclaration)
330
+ .forEach((importDeclaration) => {
331
+ const importSpecifiers = importDeclaration.node.specifiers;
332
+ // Skip if this is an import just to auto-initialize any module
333
+ // or it's a css import
334
+ if (!importSpecifiers ||
335
+ importSpecifiers.length < 1 ||
336
+ importDeclaration.node.source.value.endsWith('.css')) {
337
+ return;
338
+ }
339
+ const usedSpecifiers = importSpecifiers.filter((specifier) => (usedIdentifiersMap.get(specifier.local?.name || '') || 0) > 0);
340
+ if (usedSpecifiers.length === 0) {
341
+ // remove the entire import declaration
342
+ j(importDeclaration).remove();
343
+ isUseThemeHookRemoved = importSpecifiers.some((specifier) => specifier.local?.name === 'useTheme');
344
+ }
345
+ else if (usedSpecifiers.length < importSpecifiers.length) {
346
+ // remove unused specifiers from the import declaration
347
+ importDeclaration.node.specifiers = usedSpecifiers;
348
+ isUseThemeHookRemoved = importSpecifiers.some((specifier) => specifier.local?.name === 'useTheme');
349
+ }
350
+ });
351
+ return isUseThemeHookRemoved;
352
+ }
353
+ function removeMigrationCleanupComment(tree, j) {
354
+ tree
355
+ // @ts-ignore
356
+ .find(j.Comment)
357
+ .filter((path) => j.CommentLine.check(path.value) &&
358
+ path.value.value.indexOf('@redesign cleanup') !== -1)
359
+ .remove();
360
+ }
361
+ async function themeMigrationCleanup(file, api, options) {
362
+ const j = api.jscodeshift;
363
+ const root = j(file.source, { comment: true });
364
+ const originalSource = root.toSource();
365
+ // 1. Update props which use `themedValue` helper to always use new theme value
366
+ updateThemedValueUsages(root, j);
367
+ // 2. Process the useTheme hook to remove it or updated it
368
+ processUseThemeHook(root, j);
369
+ // 3. After updating the useTheme usages, let's potentially remove unnecessary imports
370
+ const hasUseThemeHookBeenRemoved = removeUnusedImports(root, j);
371
+ // 4. Remove migration comment -> TODO: @redesign cleanup
372
+ if (hasUseThemeHookBeenRemoved) {
373
+ removeMigrationCleanupComment(root, j);
374
+ }
375
+ // Do not return anything if no changes were applied
376
+ // so we don't rewrite the file
377
+ if (originalSource === root.toSource()) {
378
+ return null;
379
+ }
380
+ if (!options.dry) {
381
+ // Format output code with prettier
382
+ const prettierConfig = await prettier_1.default.resolveConfig(file.path);
383
+ return prettier_1.default.format(root.toSource(), prettierConfig);
384
+ }
385
+ else {
386
+ return null;
387
+ }
388
+ }
389
+ exports.default = themeMigrationCleanup;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commercetools-frontend/codemod",
3
- "version": "21.24.3",
3
+ "version": "21.25.1",
4
4
  "description": "Codemod transformations for Custom Applications",
5
5
  "bugs": "https://github.com/commercetools/merchant-center-application-kit/issues",
6
6
  "repository": {
@@ -26,13 +26,14 @@
26
26
  "dependencies": {
27
27
  "cac": "6.7.14",
28
28
  "glob": "8.1.0",
29
- "jscodeshift": "0.14.0"
29
+ "jscodeshift": "0.14.0",
30
+ "prettier": "2.8.4"
30
31
  },
31
32
  "devDependencies": {
32
33
  "@tsconfig/node16": "^1.0.3",
33
34
  "@types/jscodeshift": "0.11.6",
34
35
  "rimraf": "3.0.2",
35
- "typescript": "4.9.5"
36
+ "typescript": "5.0.4"
36
37
  },
37
38
  "engines": {
38
39
  "node": "14.x || >=16.0.0"