Skip to content

Commit 32f0e85

Browse files
authored
feat: codemod to replace slug prop to decorator (#18535)
1 parent 6a145c0 commit 32f0e85

File tree

7 files changed

+270
-0
lines changed

7 files changed

+270
-0
lines changed

packages/upgrade/src/upgrades.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,53 @@ export const upgrades = [
331331
});
332332
},
333333
},
334+
{
335+
name: 'slug-prop-to-decorator-prop',
336+
description: `
337+
Replace slug prop with decorator
338+
339+
Transforms:
340+
<Component slug="value">
341+
content
342+
</Component>
343+
344+
Into:
345+
<Component decorator="value">
346+
content
347+
</Component>
348+
`,
349+
migrate: async (options) => {
350+
const transform = path.join(
351+
TRANSFORM_DIR,
352+
'slug-prop-to-decorator-prop.js'
353+
);
354+
const paths =
355+
Array.isArray(options.paths) && options.paths.length > 0
356+
? options.paths
357+
: await glob(['**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx'], {
358+
cwd: options.workspaceDir,
359+
ignore: [
360+
'**/es/**',
361+
'**/lib/**',
362+
'**/umd/**',
363+
'**/node_modules/**',
364+
'**/storybook-static/**',
365+
'**/dist/**',
366+
'**/build/**',
367+
'**/*.d.ts',
368+
'**/coverage/**',
369+
],
370+
});
371+
372+
await run({
373+
dry: !options.write,
374+
transform,
375+
paths,
376+
verbose: options.verbose,
377+
parser: 'tsx',
378+
});
379+
},
380+
},
334381
{
335382
name: 'refactor-light-to-layer',
336383
description: `
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React from 'react';
2+
import { Dropdown, Checkbox, Tag } from '@carbon/react';
3+
4+
function TestComponent() {
5+
return (
6+
//prettier-ignore
7+
<div>
8+
{/* Basic Dropdown usage */}
9+
<Dropdown
10+
label="Select an option"
11+
slug="dropdown-1"
12+
items={['Option 1', 'Option 2']}
13+
id="dropdown-1"
14+
titleText="Dropdown"
15+
/>
16+
{/* Checkbox with expression */}
17+
<Checkbox
18+
labelText="Check me"
19+
slug={'checkbox-1'}
20+
id="checkbox-1"
21+
/>
22+
{/* Tag with string literal */}
23+
<Tag slug={'static-tag'} type="red">
24+
Important
25+
</Tag>
26+
{/* Nested structure */}
27+
<div>
28+
<Tag slug="tag-1" type="blue">
29+
Active
30+
</Tag>
31+
<Checkbox
32+
slug="checkbox-2"
33+
labelText="Enable feature"
34+
id="checkbox-2"
35+
/>
36+
</div>
37+
</div>
38+
);
39+
}
40+
41+
export default TestComponent;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React from 'react';
2+
import { Dropdown, Checkbox, Tag } from '@carbon/react';
3+
4+
const TestComponent: React.FC = () => {
5+
return (
6+
//prettier-ignore
7+
<div>
8+
{/* Basic Dropdown usage */}
9+
<Dropdown
10+
label="Select an option"
11+
slug="dropdown-1"
12+
items={['Option 1', 'Option 2']}
13+
id="dropdown-1"
14+
titleText="Dropdown"
15+
/>
16+
{/* Checkbox with expression */}
17+
<Checkbox
18+
labelText="Check me"
19+
slug={'checkbox-1'}
20+
id="checkbox-1"
21+
/>
22+
{/* Tag with string literal */}
23+
<Tag slug={'static-tag'} type="red">
24+
Important
25+
</Tag>
26+
{/* Nested structure */}
27+
<div>
28+
<Tag slug="tag-1" type="blue">
29+
Active
30+
</Tag>
31+
<Checkbox
32+
slug="checkbox-2"
33+
labelText="Enable feature"
34+
id="checkbox-2"
35+
/>
36+
</div>
37+
</div>
38+
);
39+
};
40+
41+
export default TestComponent;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React from 'react';
2+
import { Dropdown, Checkbox, Tag } from '@carbon/react';
3+
4+
function TestComponent() {
5+
return (
6+
//prettier-ignore
7+
(<div>
8+
{/* Basic Dropdown usage */}
9+
<Dropdown
10+
label="Select an option"
11+
decorator="dropdown-1"
12+
items={['Option 1', 'Option 2']}
13+
id="dropdown-1"
14+
titleText="Dropdown"
15+
/>
16+
{/* Checkbox with expression */}
17+
<Checkbox
18+
labelText="Check me"
19+
decorator={'checkbox-1'}
20+
id="checkbox-1"
21+
/>
22+
{/* Tag with string literal */}
23+
<Tag decorator={'static-tag'} type="red">
24+
Important
25+
</Tag>
26+
{/* Nested structure */}
27+
<div>
28+
<Tag decorator="tag-1" type="blue">
29+
Active
30+
</Tag>
31+
<Checkbox
32+
decorator="checkbox-2"
33+
labelText="Enable feature"
34+
id="checkbox-2"
35+
/>
36+
</div>
37+
</div>)
38+
);
39+
}
40+
41+
export default TestComponent;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React from 'react';
2+
import { Dropdown, Checkbox, Tag } from '@carbon/react';
3+
4+
const TestComponent: React.FC = () => {
5+
return (
6+
//prettier-ignore
7+
(<div>
8+
{/* Basic Dropdown usage */}
9+
<Dropdown
10+
label="Select an option"
11+
decorator="dropdown-1"
12+
items={['Option 1', 'Option 2']}
13+
id="dropdown-1"
14+
titleText="Dropdown"
15+
/>
16+
{/* Checkbox with expression */}
17+
<Checkbox
18+
labelText="Check me"
19+
decorator={'checkbox-1'}
20+
id="checkbox-1"
21+
/>
22+
{/* Tag with string literal */}
23+
<Tag decorator={'static-tag'} type="red">
24+
Important
25+
</Tag>
26+
{/* Nested structure */}
27+
<div>
28+
<Tag decorator="tag-1" type="blue">
29+
Active
30+
</Tag>
31+
<Checkbox
32+
decorator="checkbox-2"
33+
labelText="Enable feature"
34+
id="checkbox-2"
35+
/>
36+
</div>
37+
</div>)
38+
);
39+
};
40+
41+
export default TestComponent;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* Copyright IBM Corp. 2025
3+
*
4+
* This source code is licensed under the Apache-2.0 license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
'use strict';
9+
10+
const { defineTest } = require('jscodeshift/dist/testUtils');
11+
12+
defineTest(__dirname, 'slug-prop-to-decorator-prop');
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* Copyright IBM Corp. 2025
3+
*
4+
* This source code is licensed under the Apache-2.0 license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* Replace slug prop with decorator
8+
*/
9+
10+
'use strict';
11+
12+
const defaultOptions = {
13+
quote: 'single',
14+
trailingComma: true,
15+
};
16+
17+
function transform(fileInfo, api, options) {
18+
const j = api.jscodeshift;
19+
const root = j(fileInfo.source);
20+
const printOptions = options.printOptions || defaultOptions;
21+
22+
// Early return if no JSX elements with slug prop found
23+
if (!root.find(j.JSXAttribute, { name: { name: 'slug' } }).size()) {
24+
return null;
25+
}
26+
27+
// Replace slug with decorator
28+
root
29+
.find(j.JSXAttribute, {
30+
name: { name: 'slug' },
31+
})
32+
.forEach((path) => {
33+
// Create new decorator attribute with same value as slug
34+
const newAttribute = j.jsxAttribute(
35+
j.jsxIdentifier('decorator'),
36+
path.node.value
37+
);
38+
39+
// Replace the slug attribute with decorator
40+
j(path).replaceWith(newAttribute);
41+
});
42+
43+
return root.toSource(printOptions);
44+
}
45+
46+
module.exports = transform;
47+
module.exports.parser = 'tsx';

0 commit comments

Comments
 (0)