@octaviaflow/upgrade 1.0.0
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 +97 -0
- package/bin/carbon-upgrade.js +48 -0
- package/cli.js +53573 -0
- package/package.json +56 -0
- package/telemetry.yml +17 -0
- package/transforms/ARCHITECTURE.md +47 -0
- package/transforms/__testfixtures__/featureflag-deprecate-flags-prop.input.js +143 -0
- package/transforms/__testfixtures__/featureflag-deprecate-flags-prop.output.js +133 -0
- package/transforms/__testfixtures__/ibm-products-update-userprofileimage.input.js +57 -0
- package/transforms/__testfixtures__/ibm-products-update-userprofileimage.output.js +50 -0
- package/transforms/__testfixtures__/icons-react-size-prop-object-key.input.js +25 -0
- package/transforms/__testfixtures__/icons-react-size-prop-object-key.output.js +25 -0
- package/transforms/__testfixtures__/icons-react-size-prop-rename.input.js +53 -0
- package/transforms/__testfixtures__/icons-react-size-prop-rename.output.js +53 -0
- package/transforms/__testfixtures__/icons-react-size-prop-with-prop.input.js +25 -0
- package/transforms/__testfixtures__/icons-react-size-prop-with-prop.output.js +28 -0
- package/transforms/__testfixtures__/refactor-light-to-layer.input.js +23 -0
- package/transforms/__testfixtures__/refactor-light-to-layer.output.js +23 -0
- package/transforms/__testfixtures__/refactor-to-callout.input.js +34 -0
- package/transforms/__testfixtures__/refactor-to-callout.output.js +32 -0
- package/transforms/__testfixtures__/refactor-to-callout2.input.js +13 -0
- package/transforms/__testfixtures__/refactor-to-callout2.output.js +13 -0
- package/transforms/__testfixtures__/refactor-to-callout3.input.js +14 -0
- package/transforms/__testfixtures__/refactor-to-callout3.output.js +14 -0
- package/transforms/__testfixtures__/refactor-to-callout4.input.js +12 -0
- package/transforms/__testfixtures__/refactor-to-callout4.output.js +12 -0
- package/transforms/__testfixtures__/size-prop-update.input.js +152 -0
- package/transforms/__testfixtures__/size-prop-update.output.js +152 -0
- package/transforms/__testfixtures__/small-to-size-prop.input.js +20 -0
- package/transforms/__testfixtures__/small-to-size-prop.output.js +19 -0
- package/transforms/__testfixtures__/sort-prop-types.input.js +16 -0
- package/transforms/__testfixtures__/sort-prop-types.output.js +16 -0
- package/transforms/__testfixtures__/sort-prop-types2.input.js +16 -0
- package/transforms/__testfixtures__/sort-prop-types2.output.js +16 -0
- package/transforms/__testfixtures__/update-carbon-components-react-import-to-scoped.input.js +17 -0
- package/transforms/__testfixtures__/update-carbon-components-react-import-to-scoped.output.js +17 -0
- package/transforms/__testfixtures__/update-carbon-icons-react-import-to-carbon-react.input.js +17 -0
- package/transforms/__testfixtures__/update-carbon-icons-react-import-to-carbon-react.output.js +17 -0
- package/transforms/__tests__/featureflag-deprecate-flags-prop-test.js +15 -0
- package/transforms/__tests__/ibm-products-update-userprofileimage-test.js +21 -0
- package/transforms/__tests__/icons-react-size-prop.js +67 -0
- package/transforms/__tests__/refactor-light-to-layer-test.js +14 -0
- package/transforms/__tests__/refactor-to-callout.js +18 -0
- package/transforms/__tests__/size-prop-update-test.js +15 -0
- package/transforms/__tests__/small-to-size-test.js +15 -0
- package/transforms/__tests__/sort-prop-types-test.js +16 -0
- package/transforms/__tests__/update-carbon-components-react-import-to-scoped.js +15 -0
- package/transforms/__tests__/update-carbon-icons-react-import-to-carbon-react.js +15 -0
- package/transforms/featureflag-deprecate-flags-prop.js +89 -0
- package/transforms/ibm-products-update-userprofileimage.js +134 -0
- package/transforms/icons-react-size-prop.js +327 -0
- package/transforms/refactor-light-to-layer.js +117 -0
- package/transforms/refactor-to-callout.js +160 -0
- package/transforms/size-prop-update.js +143 -0
- package/transforms/small-to-size-prop.js +59 -0
- package/transforms/sort-prop-types.js +91 -0
- package/transforms/update-carbon-components-react-import-to-scoped.js +42 -0
- package/transforms/update-carbon-icons-react-import-to-carbon-react.js +42 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright OctaviaFlow
|
|
3
|
+
* Author: Vishal Kumar
|
|
4
|
+
* Created: 11/November/2025
|
|
5
|
+
*
|
|
6
|
+
* This source code is licensed under the Apache-2.0 license found in the
|
|
7
|
+
* LICENSE file in the root directory of this source tree.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const defaultOptions = {
|
|
14
|
+
quote: 'auto',
|
|
15
|
+
trailingComma: true,
|
|
16
|
+
};
|
|
17
|
+
const defaultSize = 16;
|
|
18
|
+
|
|
19
|
+
function transform(fileInfo, api, options) {
|
|
20
|
+
const printOptions = options.printOptions || defaultOptions;
|
|
21
|
+
const j = api.jscodeshift;
|
|
22
|
+
const root = j(fileInfo.source);
|
|
23
|
+
|
|
24
|
+
// Find all the import declarations that come from `@octaviaflow/icons-react`
|
|
25
|
+
const matches = root.find(j.ImportDeclaration, {
|
|
26
|
+
source: {
|
|
27
|
+
value: '@octaviaflow/icons-react',
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// If we cannot find any, then there is no work to do
|
|
32
|
+
if (matches.size() === 0) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (matches.size() > 1) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Expected only one import to @octaviaflow/icons-react, instead found: ` +
|
|
39
|
+
`${matches.size()} imports`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// For now, these icons are available under @octaviaflow/icons-react/next
|
|
44
|
+
// TODO: remove in v11
|
|
45
|
+
matches.forEach((path) => {
|
|
46
|
+
path.get('source').get('value').replace('@octaviaflow/icons-react');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Otherwise, we will get our import to icons and update the imported icons to
|
|
50
|
+
// use the new format
|
|
51
|
+
const iconsImport = matches.get();
|
|
52
|
+
const importSpecifiers = new Set();
|
|
53
|
+
|
|
54
|
+
// Iterate through each of the imported icons, get their size and name, and
|
|
55
|
+
// update the import and all matches in the file
|
|
56
|
+
j(iconsImport)
|
|
57
|
+
.find(j.ImportSpecifier)
|
|
58
|
+
.forEach((path) => {
|
|
59
|
+
const rootScope = path.scope;
|
|
60
|
+
const SIZE_REGEX = /(.+)(\d\d)$/g;
|
|
61
|
+
const { imported, local } = path.node;
|
|
62
|
+
const match = SIZE_REGEX.exec(imported.name);
|
|
63
|
+
|
|
64
|
+
if (match === null) {
|
|
65
|
+
throw new Error(`Expected to find size for: ${imported.name}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const name = match[1];
|
|
69
|
+
const size = parseInt(match[2], 10);
|
|
70
|
+
|
|
71
|
+
if (isNaN(size)) {
|
|
72
|
+
throw new Error(`Unable to parse size for ${imported.name}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// If they renamed the icon in the import, use that as the local name
|
|
76
|
+
// import { IconName32 as CustomName } from '@octaviaflow/icons-react';
|
|
77
|
+
const newBinding =
|
|
78
|
+
imported.name === local.name ? j.identifier(getSafeBinding()) : local;
|
|
79
|
+
|
|
80
|
+
function getSafeBinding() {
|
|
81
|
+
if (rootScope.declares(name)) {
|
|
82
|
+
return `${name}Icon`;
|
|
83
|
+
}
|
|
84
|
+
return name;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Update the imported binding from IconName32 to IconName. Only do this
|
|
88
|
+
// replacement once if we have imports of the same icon but in different
|
|
89
|
+
// sizes.
|
|
90
|
+
if (!importSpecifiers.has(newBinding.name)) {
|
|
91
|
+
importSpecifiers.add(newBinding.name);
|
|
92
|
+
j(path).replaceWith(j.importSpecifier(j.identifier(name), newBinding));
|
|
93
|
+
} else {
|
|
94
|
+
j(path).remove();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Finally, find all instances where we refer to this import and update
|
|
98
|
+
// its binding
|
|
99
|
+
root
|
|
100
|
+
.find(j.Identifier, { name: local.name })
|
|
101
|
+
.filter((path) => {
|
|
102
|
+
const { node: parent } = path.parent;
|
|
103
|
+
|
|
104
|
+
if (
|
|
105
|
+
j.MemberExpression.check(parent) &&
|
|
106
|
+
parent.property === path.node &&
|
|
107
|
+
!parent.computed
|
|
108
|
+
) {
|
|
109
|
+
// obj.oldName
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (
|
|
114
|
+
j.ObjectProperty.check(parent) &&
|
|
115
|
+
parent.key === path.node &&
|
|
116
|
+
!parent.computed
|
|
117
|
+
) {
|
|
118
|
+
// { oldName: 3 }
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (
|
|
123
|
+
j.MethodDefinition.check(parent) &&
|
|
124
|
+
parent.key === path.node &&
|
|
125
|
+
!parent.computed
|
|
126
|
+
) {
|
|
127
|
+
// class A { oldName() {} }
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (
|
|
132
|
+
j.ClassProperty.check(parent) &&
|
|
133
|
+
parent.key === path.node &&
|
|
134
|
+
!parent.computed
|
|
135
|
+
) {
|
|
136
|
+
// class A { oldName = 3 }
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (
|
|
141
|
+
j.JSXAttribute.check(parent) &&
|
|
142
|
+
parent.name === path.node &&
|
|
143
|
+
!parent.computed
|
|
144
|
+
) {
|
|
145
|
+
// <Foo oldName={oldName} />
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
return true;
|
|
149
|
+
})
|
|
150
|
+
.forEach((path) => {
|
|
151
|
+
let scope = path.scope;
|
|
152
|
+
while (scope && scope !== rootScope) {
|
|
153
|
+
// If a scope already declares this binding, return early as it does
|
|
154
|
+
// not relate to our icon import
|
|
155
|
+
if (scope.declares(local.name)) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
scope = scope.parent;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!scope) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const { node: parent } = path.parent;
|
|
166
|
+
|
|
167
|
+
// Replace the identifier name with the new binding name. If the parent
|
|
168
|
+
// node is an `ImportSpecifier`, we won't do this replacement as it is
|
|
169
|
+
// handled above
|
|
170
|
+
if (!j.ImportSpecifier.check(parent)) {
|
|
171
|
+
path.replace(newBinding);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// If our identifier is inside of a JSXOpeningElement, then we need to
|
|
175
|
+
// check to see if we need to add in the `size` prop that is now
|
|
176
|
+
// needed
|
|
177
|
+
if (j.JSXOpeningElement.check(parent) && size !== defaultSize) {
|
|
178
|
+
parent.attributes.unshift(
|
|
179
|
+
j.jsxAttribute(
|
|
180
|
+
j.jsxIdentifier('size'),
|
|
181
|
+
j.jsxExpressionContainer(j.numericLiteral(size))
|
|
182
|
+
)
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Handle cases where the icon is referred to in an object, for
|
|
187
|
+
// example:
|
|
188
|
+
//
|
|
189
|
+
// from:
|
|
190
|
+
// const alias = { name: IconName24 };
|
|
191
|
+
//
|
|
192
|
+
// to:
|
|
193
|
+
// const alias = { name: (props) => <IconName size={24} {...props} /> };
|
|
194
|
+
//
|
|
195
|
+
// Since the `size` information needs to be provided, otherwise the
|
|
196
|
+
// default size will be used
|
|
197
|
+
if (j.ObjectProperty.check(parent)) {
|
|
198
|
+
let replacement = null;
|
|
199
|
+
|
|
200
|
+
// map to React.createElement instead of using as the JSX Opening
|
|
201
|
+
// Element directly
|
|
202
|
+
if (newBinding.name[0] === newBinding.name[0].toLowerCase()) {
|
|
203
|
+
// Builds up this structure:
|
|
204
|
+
// (props) => React.createElement(iconName, {
|
|
205
|
+
// size: 20,
|
|
206
|
+
// ...props,
|
|
207
|
+
// });
|
|
208
|
+
replacement = j.arrowFunctionExpression(
|
|
209
|
+
[j.identifier('props')],
|
|
210
|
+
j.callExpression(
|
|
211
|
+
j.memberExpression(
|
|
212
|
+
j.identifier('React'),
|
|
213
|
+
j.identifier('createElement')
|
|
214
|
+
),
|
|
215
|
+
[
|
|
216
|
+
newBinding,
|
|
217
|
+
j.objectExpression([
|
|
218
|
+
j.objectProperty(
|
|
219
|
+
j.identifier('size'),
|
|
220
|
+
j.numericLiteral(size)
|
|
221
|
+
),
|
|
222
|
+
j.spreadElement(j.identifier('props')),
|
|
223
|
+
]),
|
|
224
|
+
]
|
|
225
|
+
)
|
|
226
|
+
);
|
|
227
|
+
} else {
|
|
228
|
+
// Build up this structure:
|
|
229
|
+
// (props) => <IconName size={20} {...props} />
|
|
230
|
+
replacement = j.arrowFunctionExpression(
|
|
231
|
+
[j.identifier('props')],
|
|
232
|
+
j.jsxElement(
|
|
233
|
+
j.jsxOpeningElement(
|
|
234
|
+
j.jsxIdentifier(newBinding.name),
|
|
235
|
+
[
|
|
236
|
+
j.jsxAttribute(
|
|
237
|
+
j.jsxIdentifier('size'),
|
|
238
|
+
j.jsxExpressionContainer(j.numericLiteral(size))
|
|
239
|
+
),
|
|
240
|
+
j.jsxSpreadAttribute(j.identifier('props')),
|
|
241
|
+
],
|
|
242
|
+
true
|
|
243
|
+
)
|
|
244
|
+
)
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
warn(fileInfo.path);
|
|
249
|
+
|
|
250
|
+
// Sometimes consumers will use the icon module name as a shorthand
|
|
251
|
+
// in an object property.
|
|
252
|
+
//
|
|
253
|
+
// Input:
|
|
254
|
+
// const o = { Add16, Add32 };
|
|
255
|
+
// Output:
|
|
256
|
+
// const o = { Add16: Add, Add32: (props) => <Add size={32} {...props} />
|
|
257
|
+
if (
|
|
258
|
+
parent.key.name !== newBinding.name &&
|
|
259
|
+
parent.shorthand === true
|
|
260
|
+
) {
|
|
261
|
+
path.parent.get('shorthand').replace(false);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (size !== defaultSize) {
|
|
265
|
+
path.parent.get('value').replace(replacement);
|
|
266
|
+
} else {
|
|
267
|
+
path.parent.get('value').replace(newBinding);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Support `renderIcon` style props where you pass in an Icon by itself
|
|
272
|
+
// to the prop
|
|
273
|
+
//
|
|
274
|
+
// from:
|
|
275
|
+
// <Component renderIcon={Icon24} />
|
|
276
|
+
//
|
|
277
|
+
// to:
|
|
278
|
+
// <Component renderIcon={(props) => <Icon size={24} {...props} />} />
|
|
279
|
+
if (j.JSXExpressionContainer.check(parent) && size !== defaultSize) {
|
|
280
|
+
warn(fileInfo.path);
|
|
281
|
+
path.parentPath.replace(
|
|
282
|
+
j.jsxExpressionContainer(
|
|
283
|
+
j.arrowFunctionExpression(
|
|
284
|
+
[j.identifier('props')],
|
|
285
|
+
j.jsxElement(
|
|
286
|
+
j.jsxOpeningElement(
|
|
287
|
+
j.jsxIdentifier(path.node.name),
|
|
288
|
+
[
|
|
289
|
+
j.jsxAttribute(
|
|
290
|
+
j.jsxIdentifier('size'),
|
|
291
|
+
j.jsxExpressionContainer(j.numericLiteral(size))
|
|
292
|
+
),
|
|
293
|
+
j.jsxSpreadAttribute(j.identifier('props')),
|
|
294
|
+
],
|
|
295
|
+
true
|
|
296
|
+
)
|
|
297
|
+
)
|
|
298
|
+
)
|
|
299
|
+
)
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
return root.toSource(printOptions);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const manualCheckWarning = `[carbon] ${'='.repeat(71)}
|
|
309
|
+
We have updated the file: %s to the new icon API.
|
|
310
|
+
|
|
311
|
+
However, it may be that this update is missing a \`ref\` on the prop where the
|
|
312
|
+
icon is used. Please make sure to verify that this update is correct.
|
|
313
|
+
|
|
314
|
+
For more information, check out our migration guide: https://bit.ly/3o2vVQW\n`;
|
|
315
|
+
|
|
316
|
+
const files = new Set();
|
|
317
|
+
|
|
318
|
+
function warn(filepath) {
|
|
319
|
+
if (files.has(filepath)) {
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
files.add(filepath);
|
|
324
|
+
console.log(manualCheckWarning, filepath);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
module.exports = transform;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright OctaviaFlow
|
|
3
|
+
* Author: Vishal Kumar
|
|
4
|
+
* Created: 11/November/2025
|
|
5
|
+
*
|
|
6
|
+
* This source code is licensed under the Apache-2.0 license found in the
|
|
7
|
+
* LICENSE file in the root directory of this source tree.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Copyright IBM Corp. 2024
|
|
12
|
+
*
|
|
13
|
+
* This source code is licensed under the Apache-2.0 license found in the
|
|
14
|
+
* LICENSE file in the root directory of this source tree.
|
|
15
|
+
*
|
|
16
|
+
* Deprecate the `light` prop and wrap components with `Layer`
|
|
17
|
+
*
|
|
18
|
+
* Transforms:
|
|
19
|
+
*
|
|
20
|
+
* <Button light>Click me</Button>
|
|
21
|
+
*
|
|
22
|
+
* Into:
|
|
23
|
+
*
|
|
24
|
+
* <Layer>
|
|
25
|
+
* <Button>Click me</Button>
|
|
26
|
+
* </Layer>
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
'use strict';
|
|
30
|
+
|
|
31
|
+
const defaultOptions = {
|
|
32
|
+
quote: 'single',
|
|
33
|
+
trailingComma: true,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function transform(fileInfo, api, options) {
|
|
37
|
+
const { jscodeshift: j } = api;
|
|
38
|
+
const root = j(fileInfo.source);
|
|
39
|
+
const printOptions = options.printOptions || defaultOptions;
|
|
40
|
+
|
|
41
|
+
// Check if there are any components with the 'light' prop
|
|
42
|
+
const hasLightProp =
|
|
43
|
+
root.find(j.JSXAttribute, { name: { name: 'light' } }).size() > 0;
|
|
44
|
+
|
|
45
|
+
if (!hasLightProp) {
|
|
46
|
+
return null; // if no 'light' prop found, don't modify & return the file
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Import Layer component if not already imported
|
|
50
|
+
const layerImport = root.find(j.ImportDeclaration, {
|
|
51
|
+
source: { value: '@octaviaflow/react' },
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (layerImport.length) {
|
|
55
|
+
const specifiers = layerImport.get('specifiers');
|
|
56
|
+
const hasLayerImport = specifiers.value.some(
|
|
57
|
+
(specifier) => specifier.imported && specifier.imported.name === 'Layer'
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if (!hasLayerImport) {
|
|
61
|
+
specifiers.value.push(j.importSpecifier(j.identifier('Layer')));
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
const newImport = j.importDeclaration(
|
|
65
|
+
[j.importSpecifier(j.identifier('Layer'))],
|
|
66
|
+
j.literal('@octaviaflow/react')
|
|
67
|
+
);
|
|
68
|
+
// Find the first import declaration
|
|
69
|
+
const firstImport = root.find(j.ImportDeclaration).at(0);
|
|
70
|
+
|
|
71
|
+
if (firstImport.length) {
|
|
72
|
+
// Insert the new import before the first existing import
|
|
73
|
+
firstImport.insertAfter(newImport);
|
|
74
|
+
} else {
|
|
75
|
+
// If no imports, find the first non-comment node
|
|
76
|
+
const firstNonCommentNode = root
|
|
77
|
+
.find(j.Program)
|
|
78
|
+
.get('body')
|
|
79
|
+
.filter(
|
|
80
|
+
(path) =>
|
|
81
|
+
path.value.type !== 'CommentBlock' &&
|
|
82
|
+
path.value.type !== 'CommentLine'
|
|
83
|
+
)[0];
|
|
84
|
+
|
|
85
|
+
// Insert the new import before the first non-comment node
|
|
86
|
+
j(firstNonCommentNode).insertBefore(newImport);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Find all JSX elements with a 'light' prop
|
|
91
|
+
root.find(j.JSXElement).forEach((path) => {
|
|
92
|
+
const lightProp = path.node.openingElement.attributes.find(
|
|
93
|
+
(attr) => attr.type === 'JSXAttribute' && attr.name.name === 'light'
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if (lightProp) {
|
|
97
|
+
// Remove the 'light' prop
|
|
98
|
+
path.node.openingElement.attributes =
|
|
99
|
+
path.node.openingElement.attributes.filter(
|
|
100
|
+
(attr) => attr !== lightProp
|
|
101
|
+
);
|
|
102
|
+
// Wrap the component with Layer
|
|
103
|
+
const layerElement = j.jsxElement(
|
|
104
|
+
j.jsxOpeningElement(j.jsxIdentifier('Layer'), []),
|
|
105
|
+
j.jsxClosingElement(j.jsxIdentifier('Layer')),
|
|
106
|
+
[path.node]
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// Replace the original element with the wrapped version
|
|
110
|
+
j(path).replaceWith(layerElement);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
return root.toSource(printOptions);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = transform;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright OctaviaFlow
|
|
3
|
+
* Author: Vishal Kumar
|
|
4
|
+
* Created: 11/November/2025
|
|
5
|
+
*
|
|
6
|
+
* This source code is licensed under the Apache-2.0 license found in the
|
|
7
|
+
* LICENSE file in the root directory of this source tree.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
'use strict';
|
|
12
|
+
|
|
13
|
+
const defaultOptions = {
|
|
14
|
+
quote: 'auto',
|
|
15
|
+
trailingComma: true,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function transform(fileInfo, api, options) {
|
|
19
|
+
const printOptions = options.printOptions || defaultOptions;
|
|
20
|
+
const j = api.jscodeshift;
|
|
21
|
+
const root = j(fileInfo.source);
|
|
22
|
+
|
|
23
|
+
// Helper function to check if the import source is from '@octaviaflow/react' or its subpaths
|
|
24
|
+
function isCarbonReactImport(sourceValue) {
|
|
25
|
+
return (
|
|
26
|
+
sourceValue === '@octaviaflow/react' ||
|
|
27
|
+
sourceValue.startsWith('@octaviaflow/react/es') ||
|
|
28
|
+
sourceValue.startsWith('@octaviaflow/react/lib')
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Collect names of identifiers imported from '@octaviaflow/react' or its subpaths
|
|
33
|
+
const importedIdentifiers = new Map(); // Map of local name to transformed name
|
|
34
|
+
|
|
35
|
+
// Transform import declarations
|
|
36
|
+
root.find(j.ImportDeclaration).forEach((path) => {
|
|
37
|
+
const sourceValue = path.node.source.value;
|
|
38
|
+
|
|
39
|
+
// Only transform imports from '@octaviaflow/react' and its subpaths
|
|
40
|
+
if (!isCarbonReactImport(sourceValue)) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
path.node.specifiers.forEach((specifier) => {
|
|
45
|
+
if (specifier.type === 'ImportSpecifier') {
|
|
46
|
+
let importedName = specifier.imported.name;
|
|
47
|
+
let localName = specifier.local ? specifier.local.name : importedName;
|
|
48
|
+
let transformedImportedName = importedName;
|
|
49
|
+
let transformedLocalName = localName;
|
|
50
|
+
|
|
51
|
+
// Transform imported names and local names as necessary
|
|
52
|
+
if (importedName === 'unstable__StaticNotification') {
|
|
53
|
+
transformedImportedName = 'unstable__Callout';
|
|
54
|
+
specifier.imported.name = transformedImportedName;
|
|
55
|
+
|
|
56
|
+
if (localName === 'StaticNotification') {
|
|
57
|
+
transformedLocalName = 'Callout';
|
|
58
|
+
specifier.local.name = transformedLocalName;
|
|
59
|
+
} else if (localName === 'unstable__StaticNotification') {
|
|
60
|
+
transformedLocalName = 'unstable__Callout';
|
|
61
|
+
specifier.local.name = transformedLocalName;
|
|
62
|
+
}
|
|
63
|
+
// If local name is something else (e.g., SomeOtherName), leave it unchanged
|
|
64
|
+
} else if (importedName === 'StaticNotification') {
|
|
65
|
+
transformedImportedName = 'Callout';
|
|
66
|
+
specifier.imported.name = transformedImportedName;
|
|
67
|
+
|
|
68
|
+
if (localName === 'StaticNotification') {
|
|
69
|
+
transformedLocalName = 'Callout';
|
|
70
|
+
specifier.local.name = transformedLocalName;
|
|
71
|
+
}
|
|
72
|
+
// If local name is different, leave it unchanged
|
|
73
|
+
} else if (importedName === 'StaticNotificationProps') {
|
|
74
|
+
transformedImportedName = 'CalloutProps';
|
|
75
|
+
specifier.imported.name = transformedImportedName;
|
|
76
|
+
|
|
77
|
+
if (localName === 'StaticNotificationProps') {
|
|
78
|
+
transformedLocalName = 'CalloutProps';
|
|
79
|
+
specifier.local.name = transformedLocalName;
|
|
80
|
+
}
|
|
81
|
+
// If local name is different, leave it unchanged
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// If imported name and local name are the same after transformation, remove the alias
|
|
85
|
+
if (
|
|
86
|
+
specifier.local &&
|
|
87
|
+
specifier.local.name === specifier.imported.name
|
|
88
|
+
) {
|
|
89
|
+
delete specifier.local;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Update the mapping of imported identifiers
|
|
93
|
+
// Only add to the map if the local name or the transformed name is different
|
|
94
|
+
if (localName !== transformedLocalName) {
|
|
95
|
+
importedIdentifiers.set(localName, transformedLocalName);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Deduplicate imports
|
|
102
|
+
const importDeclarations = root.find(j.ImportDeclaration);
|
|
103
|
+
|
|
104
|
+
importDeclarations.forEach((path) => {
|
|
105
|
+
const sourceValue = path.node.source.value;
|
|
106
|
+
|
|
107
|
+
// Only deduplicate imports from '@octaviaflow/react' and its subpaths
|
|
108
|
+
if (!isCarbonReactImport(sourceValue)) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const specifiers = path.node.specifiers;
|
|
113
|
+
const uniqueSpecifiers = [];
|
|
114
|
+
const seen = new Set();
|
|
115
|
+
|
|
116
|
+
specifiers.forEach((specifier) => {
|
|
117
|
+
const importedName = specifier.imported.name;
|
|
118
|
+
const localName = specifier.local ? specifier.local.name : importedName;
|
|
119
|
+
const key = `${importedName}:${localName}`;
|
|
120
|
+
|
|
121
|
+
if (!seen.has(key)) {
|
|
122
|
+
seen.add(key);
|
|
123
|
+
uniqueSpecifiers.push(specifier);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
path.node.specifiers = uniqueSpecifiers;
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Remove empty import declarations
|
|
131
|
+
importDeclarations.forEach((path) => {
|
|
132
|
+
if (path.node.specifiers.length === 0) {
|
|
133
|
+
j(path).remove();
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Update usages in the code
|
|
138
|
+
root.find(j.Identifier).forEach((path) => {
|
|
139
|
+
const name = path.node.name;
|
|
140
|
+
|
|
141
|
+
// Skip if the identifier is part of an import specifier
|
|
142
|
+
if (
|
|
143
|
+
path.parent.node.type === 'ImportSpecifier' ||
|
|
144
|
+
path.parent.node.type === 'ImportDefaultSpecifier' ||
|
|
145
|
+
path.parent.node.type === 'ImportNamespaceSpecifier'
|
|
146
|
+
) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Only transform identifiers that match the imported identifiers
|
|
151
|
+
if (importedIdentifiers.has(name)) {
|
|
152
|
+
const transformedName = importedIdentifiers.get(name);
|
|
153
|
+
path.node.name = transformedName;
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
return root.toSource(printOptions);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = transform;
|