@wise/wds-codemods 0.0.1-experimental-6c2101b
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/.changeset/better-impalas-drop.md +5 -0
- package/.changeset/config.json +13 -0
- package/.github/CODEOWNERS +1 -0
- package/.github/actions/bootstrap/action.yml +49 -0
- package/.github/actions/commitlint/action.yml +27 -0
- package/.github/actions/test/action.yml +23 -0
- package/.github/workflows/cd-cd.yml +127 -0
- package/.github/workflows/renovate.yml +16 -0
- package/.husky/commit-msg +1 -0
- package/.husky/pre-commit +1 -0
- package/.nvmrc +1 -0
- package/.prettierignore +1 -0
- package/.prettierrc.js +5 -0
- package/README.md +184 -0
- package/babel.config.js +28 -0
- package/codemod-report.md +81 -0
- package/commitlint.config.js +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2448 -0
- package/dist/index.js.map +1 -0
- package/dist/transforms/button.d.ts +20 -0
- package/dist/transforms/button.js +640 -0
- package/dist/transforms/button.js.map +1 -0
- package/eslint.config.js +15 -0
- package/jest.config.js +9 -0
- package/mkdocs.yml +4 -0
- package/package.json +68 -0
- package/renovate.json +9 -0
- package/scripts/build.sh +10 -0
- package/src/__tests__/runCodemod.test.ts +109 -0
- package/src/index.ts +4 -0
- package/src/runCodemod.ts +149 -0
- package/src/transforms/button/__tests__/button.test.tsx +175 -0
- package/src/transforms/button/button.ts +453 -0
- package/src/transforms/helpers/__tests__/createTestTransform.test.ts +27 -0
- package/src/transforms/helpers/__tests__/hasImport.test.ts +52 -0
- package/src/transforms/helpers/__tests__/iconUtils.test.ts +207 -0
- package/src/transforms/helpers/__tests__/jsxElementUtils.test.ts +130 -0
- package/src/transforms/helpers/__tests__/jsxReportingUtils.test.ts +265 -0
- package/src/transforms/helpers/__tests__/packageValidation.test.ts +45 -0
- package/src/transforms/helpers/createTestTransform.ts +59 -0
- package/src/transforms/helpers/hasImport.ts +60 -0
- package/src/transforms/helpers/iconUtils.ts +87 -0
- package/src/transforms/helpers/index.ts +5 -0
- package/src/transforms/helpers/jsxElementUtils.ts +67 -0
- package/src/transforms/helpers/jsxReportingUtils.ts +224 -0
- package/src/transforms/helpers/packageValidation.ts +53 -0
- package/src/utils/__tests__/getOptions.test.ts +219 -0
- package/src/utils/__tests__/handleError.test.ts +18 -0
- package/src/utils/__tests__/hasPackageVersion.test.ts +191 -0
- package/src/utils/__tests__/loadTransformModules.test.ts +51 -0
- package/src/utils/__tests__/reportManualReview.test.ts +42 -0
- package/src/utils/getOptions.ts +78 -0
- package/src/utils/handleError.ts +6 -0
- package/src/utils/hasPackageVersion.ts +482 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/loadTransformModules.ts +28 -0
- package/src/utils/reportManualReview.ts +17 -0
- package/test-button.tsx +230 -0
- package/test-file.js +2 -0
- package/tsconfig.json +14 -0
- package/tsup.config.js +13 -0
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
// src/utils/reportManualReview.ts
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
var REPORT_PATH = path.resolve(process.cwd(), "codemod-report.txt");
|
|
5
|
+
var reportManualReview = async (filePath, message) => {
|
|
6
|
+
const lineMatch = /at line (\d+)/u.exec(message);
|
|
7
|
+
const lineNumber = lineMatch?.[1];
|
|
8
|
+
const cleanMessage = message.replace(/ at line \d+/u, "");
|
|
9
|
+
const lineInfo = lineNumber ? `:${lineNumber}` : "";
|
|
10
|
+
await fs.appendFile(REPORT_PATH, `[${filePath}${lineInfo}] ${cleanMessage}
|
|
11
|
+
`, "utf8");
|
|
12
|
+
};
|
|
13
|
+
var reportManualReview_default = reportManualReview;
|
|
14
|
+
|
|
15
|
+
// src/transforms/helpers/hasImport.ts
|
|
16
|
+
function hasImport(root, sourceValue, importName, j) {
|
|
17
|
+
const importDeclarations = root.find(j.ImportDeclaration, {
|
|
18
|
+
source: { value: sourceValue }
|
|
19
|
+
});
|
|
20
|
+
if (importDeclarations.size() === 0) {
|
|
21
|
+
return {
|
|
22
|
+
exists: false,
|
|
23
|
+
remove: () => {
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const namedImport = importDeclarations.find(j.ImportSpecifier, {
|
|
28
|
+
imported: { name: importName }
|
|
29
|
+
});
|
|
30
|
+
const defaultImport = importDeclarations.find(j.ImportDefaultSpecifier, {
|
|
31
|
+
local: { name: importName }
|
|
32
|
+
});
|
|
33
|
+
const exists = namedImport.size() > 0 || defaultImport.size() > 0;
|
|
34
|
+
const remove = () => {
|
|
35
|
+
importDeclarations.forEach((path2) => {
|
|
36
|
+
const filteredSpecifiers = path2.node.specifiers?.filter((specifier) => {
|
|
37
|
+
if (specifier.type === "ImportSpecifier" && specifier.imported.name === importName) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
if (specifier.type === "ImportDefaultSpecifier" && specifier.local?.name === importName) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
}) ?? [];
|
|
45
|
+
if (filteredSpecifiers.length === 0) {
|
|
46
|
+
path2.prune();
|
|
47
|
+
} else {
|
|
48
|
+
j(path2).replaceWith(
|
|
49
|
+
j.importDeclaration(filteredSpecifiers, path2.node.source, path2.node.importKind)
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
return { exists, remove };
|
|
55
|
+
}
|
|
56
|
+
var hasImport_default = hasImport;
|
|
57
|
+
|
|
58
|
+
// src/transforms/helpers/iconUtils.ts
|
|
59
|
+
var processIconChildren = (j, children, iconImports, openingElement) => {
|
|
60
|
+
if (!children || !openingElement.attributes) return;
|
|
61
|
+
const unwrapJsxElement = (node) => {
|
|
62
|
+
if (typeof node === "object" && node !== null && "type" in node && node.type === "JSXExpressionContainer" && j.JSXElement.check(node.expression)) {
|
|
63
|
+
return node.expression;
|
|
64
|
+
}
|
|
65
|
+
return node;
|
|
66
|
+
};
|
|
67
|
+
const totalChildren = children.length;
|
|
68
|
+
const iconChildIndex = children.findIndex((child) => {
|
|
69
|
+
const unwrapped = unwrapJsxElement(child);
|
|
70
|
+
return j.JSXElement.check(unwrapped) && unwrapped.openingElement.name.type === "JSXIdentifier" && iconImports.has(unwrapped.openingElement.name.name);
|
|
71
|
+
});
|
|
72
|
+
if (iconChildIndex === -1) return;
|
|
73
|
+
const iconChild = unwrapJsxElement(children[iconChildIndex]);
|
|
74
|
+
if (!iconChild || iconChild.openingElement.name.type !== "JSXIdentifier") return;
|
|
75
|
+
const iconName = iconChild.openingElement.name.name;
|
|
76
|
+
const distanceToStart = iconChildIndex;
|
|
77
|
+
const distanceToEnd = totalChildren - 1 - iconChildIndex;
|
|
78
|
+
const iconPropName = distanceToStart <= distanceToEnd ? "addonStart" : "addonEnd";
|
|
79
|
+
const iconObject = j.objectExpression([
|
|
80
|
+
j.property("init", j.identifier("type"), j.literal("icon")),
|
|
81
|
+
j.property("init", j.identifier("value"), iconChild)
|
|
82
|
+
]);
|
|
83
|
+
const iconProp = j.jsxAttribute(
|
|
84
|
+
j.jsxIdentifier(iconPropName),
|
|
85
|
+
j.jsxExpressionContainer(iconObject)
|
|
86
|
+
);
|
|
87
|
+
openingElement.attributes.push(iconProp);
|
|
88
|
+
children.splice(iconChildIndex, 1);
|
|
89
|
+
const isWhitespaceJsxText = (node) => {
|
|
90
|
+
return typeof node === "object" && node !== null && node.type === "JSXText" && typeof node.value === "string" && node.value.trim() === "";
|
|
91
|
+
};
|
|
92
|
+
if (iconChildIndex - 1 >= 0 && isWhitespaceJsxText(children[iconChildIndex - 1])) {
|
|
93
|
+
children.splice(iconChildIndex - 1, 1);
|
|
94
|
+
} else if (isWhitespaceJsxText(children[iconChildIndex])) {
|
|
95
|
+
children.splice(iconChildIndex, 1);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
var iconUtils_default = processIconChildren;
|
|
99
|
+
|
|
100
|
+
// src/transforms/helpers/jsxElementUtils.ts
|
|
101
|
+
var setNameIfJSXIdentifier = (elementName, newName) => {
|
|
102
|
+
if (elementName && elementName.type === "JSXIdentifier") {
|
|
103
|
+
return { ...elementName, name: newName };
|
|
104
|
+
}
|
|
105
|
+
return elementName;
|
|
106
|
+
};
|
|
107
|
+
var hasAttribute = (attributes, attributeName) => {
|
|
108
|
+
return Array.isArray(attributes) && attributes.some(
|
|
109
|
+
(attr) => attr.type === "JSXAttribute" && attr.name.type === "JSXIdentifier" && attr.name.name === attributeName
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
var hasAttributeOnElement = (element, attributeName) => {
|
|
113
|
+
return hasAttribute(element.attributes, attributeName);
|
|
114
|
+
};
|
|
115
|
+
var addAttributesIfMissing = (j, openingElement, attributesToAdd) => {
|
|
116
|
+
if (!Array.isArray(openingElement.attributes)) return;
|
|
117
|
+
const attrs = openingElement.attributes;
|
|
118
|
+
attributesToAdd.forEach(({ attribute, name }) => {
|
|
119
|
+
if (!hasAttributeOnElement(openingElement, name)) {
|
|
120
|
+
attrs.push(attribute);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// src/transforms/helpers/jsxReportingUtils.ts
|
|
126
|
+
var CodemodReporter = class {
|
|
127
|
+
j;
|
|
128
|
+
issues;
|
|
129
|
+
constructor(options) {
|
|
130
|
+
this.j = options.jscodeshift;
|
|
131
|
+
this.issues = options.issues;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Reports an issue with a JSX element
|
|
135
|
+
*/
|
|
136
|
+
reportElement(element, reason) {
|
|
137
|
+
const node = this.getNode(element);
|
|
138
|
+
const componentName = this.getComponentName(node);
|
|
139
|
+
const line = this.getLineNumber(node);
|
|
140
|
+
this.addIssue(`Manual review required: <${componentName}> at line ${line} ${reason}.`);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Reports an issue with a specific prop
|
|
144
|
+
*/
|
|
145
|
+
reportProp(element, propName, reason) {
|
|
146
|
+
const node = this.getNode(element);
|
|
147
|
+
const componentName = this.getComponentName(node);
|
|
148
|
+
const line = this.getLineNumber(node);
|
|
149
|
+
this.addIssue(
|
|
150
|
+
`Manual review required: prop "${propName}" on <${componentName}> at line ${line} ${reason}.`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Reports an issue with a JSX attribute directly
|
|
155
|
+
*/
|
|
156
|
+
reportAttribute(attr, element, reason) {
|
|
157
|
+
const node = this.getNode(element);
|
|
158
|
+
const componentName = this.getComponentName(node);
|
|
159
|
+
const propName = this.getAttributeName(attr);
|
|
160
|
+
const line = this.getLineNumber(attr) || this.getLineNumber(node);
|
|
161
|
+
const defaultReason = this.getAttributeReason(attr);
|
|
162
|
+
const finalReason = reason || defaultReason;
|
|
163
|
+
this.addIssue(
|
|
164
|
+
`Manual review required: prop "${propName}" on <${componentName}> at line ${line} ${finalReason}.`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Reports spread props on an element
|
|
169
|
+
*/
|
|
170
|
+
reportSpreadProps(element) {
|
|
171
|
+
this.reportElement(element, "contains spread props that need manual review");
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Reports conflicting prop and children
|
|
175
|
+
*/
|
|
176
|
+
reportPropWithChildren(element, propName) {
|
|
177
|
+
this.reportProp(
|
|
178
|
+
element,
|
|
179
|
+
propName,
|
|
180
|
+
`conflicts with children - both "${propName}" prop and children are present`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Reports unsupported prop value
|
|
185
|
+
*/
|
|
186
|
+
reportUnsupportedValue(element, propName, value) {
|
|
187
|
+
this.reportProp(element, propName, `has unsupported value "${value}"`);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Reports ambiguous expression in prop
|
|
191
|
+
*/
|
|
192
|
+
reportAmbiguousExpression(element, propName) {
|
|
193
|
+
this.reportProp(element, propName, "contains a complex expression that needs manual review");
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Reports ambiguous children (like dynamic icons)
|
|
197
|
+
*/
|
|
198
|
+
reportAmbiguousChildren(element, childType = "content") {
|
|
199
|
+
this.reportElement(element, `contains ambiguous ${childType} that needs manual review`);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Reports deprecated prop usage
|
|
203
|
+
*/
|
|
204
|
+
reportDeprecatedProp(element, propName, alternative) {
|
|
205
|
+
const suggestion = alternative ? ` Use ${alternative} instead` : "";
|
|
206
|
+
this.reportProp(element, propName, `is deprecated${suggestion}`);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Reports missing required prop
|
|
210
|
+
*/
|
|
211
|
+
reportMissingRequiredProp(element, propName) {
|
|
212
|
+
this.reportProp(element, propName, "is required but missing");
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Reports conflicting props
|
|
216
|
+
*/
|
|
217
|
+
reportConflictingProps(element, propNames) {
|
|
218
|
+
const propList = propNames.map((name) => `"${name}"`).join(", ");
|
|
219
|
+
this.reportElement(element, `has conflicting props: ${propList} cannot be used together`);
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Auto-detects and reports common attribute issues
|
|
223
|
+
*/
|
|
224
|
+
reportAttributeIssues(element) {
|
|
225
|
+
const node = this.getNode(element);
|
|
226
|
+
const { attributes } = node.openingElement;
|
|
227
|
+
if (!attributes) return;
|
|
228
|
+
if (attributes.some((attr) => attr.type === "JSXSpreadAttribute")) {
|
|
229
|
+
this.reportSpreadProps(element);
|
|
230
|
+
}
|
|
231
|
+
attributes.forEach((attr) => {
|
|
232
|
+
if (attr.type === "JSXAttribute" && attr.value?.type === "JSXExpressionContainer") {
|
|
233
|
+
this.reportAttribute(attr, element);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
// Private helper methods
|
|
238
|
+
getNode(element) {
|
|
239
|
+
return "node" in element ? element.node : element;
|
|
240
|
+
}
|
|
241
|
+
getComponentName(node) {
|
|
242
|
+
const { name } = node.openingElement;
|
|
243
|
+
if (name.type === "JSXIdentifier") {
|
|
244
|
+
return name.name;
|
|
245
|
+
}
|
|
246
|
+
return this.j(name).toSource();
|
|
247
|
+
}
|
|
248
|
+
getLineNumber(node) {
|
|
249
|
+
return node.loc?.start.line?.toString() || "unknown";
|
|
250
|
+
}
|
|
251
|
+
getAttributeName(attr) {
|
|
252
|
+
if (attr.name.type === "JSXIdentifier") {
|
|
253
|
+
return attr.name.name;
|
|
254
|
+
}
|
|
255
|
+
return this.j(attr.name).toSource();
|
|
256
|
+
}
|
|
257
|
+
getAttributeReason(attr) {
|
|
258
|
+
if (!attr.value) return "has no value";
|
|
259
|
+
if (attr.value.type === "JSXExpressionContainer") {
|
|
260
|
+
const expr = attr.value.expression;
|
|
261
|
+
const expressionType = expr.type.replace("Expression", "").toLowerCase();
|
|
262
|
+
if (expr.type === "Identifier" || expr.type === "MemberExpression") {
|
|
263
|
+
const valueText = this.j(expr).toSource();
|
|
264
|
+
return `contains a ${expressionType} (${valueText})`;
|
|
265
|
+
}
|
|
266
|
+
return `contains a complex ${expressionType} expression`;
|
|
267
|
+
}
|
|
268
|
+
return "needs manual review";
|
|
269
|
+
}
|
|
270
|
+
addIssue(message) {
|
|
271
|
+
this.issues.push(message);
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
var createReporter = (j, issues) => {
|
|
275
|
+
return new CodemodReporter({ jscodeshift: j, issues });
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
// src/transforms/helpers/packageValidation.ts
|
|
279
|
+
function validatePackageRequirements(options, requirements) {
|
|
280
|
+
let packageResults = {};
|
|
281
|
+
if (options.packageResults) {
|
|
282
|
+
if (typeof options.packageResults === "string") {
|
|
283
|
+
try {
|
|
284
|
+
if (options.packageResults.trim() && options.packageResults.trim().startsWith("{")) {
|
|
285
|
+
packageResults = JSON.parse(options.packageResults);
|
|
286
|
+
}
|
|
287
|
+
} catch (error) {
|
|
288
|
+
console.debug("Failed to parse packageResults:", error);
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
} else if (typeof options.packageResults === "object") {
|
|
292
|
+
packageResults = options.packageResults;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const allRequirementsMet = requirements.every((req) => {
|
|
296
|
+
const key = `${req.name}@${req.version}`;
|
|
297
|
+
return packageResults[key];
|
|
298
|
+
});
|
|
299
|
+
if (!allRequirementsMet) {
|
|
300
|
+
const missing = requirements.filter((req) => {
|
|
301
|
+
const key = `${req.name}@${req.version}`;
|
|
302
|
+
return !packageResults[key];
|
|
303
|
+
});
|
|
304
|
+
const missingPackages = missing.map((pkg) => `${pkg.name}@${pkg.version}`).join(", ");
|
|
305
|
+
console.debug(`Skipping transform - missing required packages: ${missingPackages}`);
|
|
306
|
+
}
|
|
307
|
+
return allRequirementsMet;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// src/transforms/button/button.ts
|
|
311
|
+
var parser = "tsx";
|
|
312
|
+
var packageRequirements = [{ name: "@transferwise/components", version: ">=46.5.0" }];
|
|
313
|
+
var priorityMapping = {
|
|
314
|
+
accent: {
|
|
315
|
+
primary: "primary",
|
|
316
|
+
secondary: "secondary-neutral",
|
|
317
|
+
tertiary: "tertiary"
|
|
318
|
+
},
|
|
319
|
+
positive: {
|
|
320
|
+
primary: "primary",
|
|
321
|
+
secondary: "secondary-neutral",
|
|
322
|
+
tertiary: "secondary-neutral"
|
|
323
|
+
},
|
|
324
|
+
negative: {
|
|
325
|
+
primary: "primary",
|
|
326
|
+
secondary: "secondary",
|
|
327
|
+
tertiary: "secondary"
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
var sizeMap = {
|
|
331
|
+
EXTRA_SMALL: "xs",
|
|
332
|
+
SMALL: "sm",
|
|
333
|
+
MEDIUM: "md",
|
|
334
|
+
LARGE: "lg",
|
|
335
|
+
EXTRA_LARGE: "xl",
|
|
336
|
+
xs: "sm",
|
|
337
|
+
sm: "sm",
|
|
338
|
+
md: "md",
|
|
339
|
+
lg: "lg",
|
|
340
|
+
xl: "xl"
|
|
341
|
+
};
|
|
342
|
+
var resolveSize = (size) => {
|
|
343
|
+
if (!size) return size;
|
|
344
|
+
const match = /^Size\.(EXTRA_SMALL|SMALL|MEDIUM|LARGE|EXTRA_LARGE)$/u.exec(size);
|
|
345
|
+
if (match) {
|
|
346
|
+
return sizeMap[match[1]];
|
|
347
|
+
}
|
|
348
|
+
return sizeMap[size] || size;
|
|
349
|
+
};
|
|
350
|
+
var resolvePriority = (type, priority) => {
|
|
351
|
+
if (type && priority) {
|
|
352
|
+
return priorityMapping[type]?.[priority] || priority;
|
|
353
|
+
}
|
|
354
|
+
return priority;
|
|
355
|
+
};
|
|
356
|
+
var resolveType = (type, htmlType) => {
|
|
357
|
+
if (htmlType) {
|
|
358
|
+
return htmlType;
|
|
359
|
+
}
|
|
360
|
+
const legacyButtonTypes = [
|
|
361
|
+
"accent",
|
|
362
|
+
"negative",
|
|
363
|
+
"positive",
|
|
364
|
+
"primary",
|
|
365
|
+
"pay",
|
|
366
|
+
"secondary",
|
|
367
|
+
"danger",
|
|
368
|
+
"link",
|
|
369
|
+
"button",
|
|
370
|
+
"reset",
|
|
371
|
+
"submit"
|
|
372
|
+
];
|
|
373
|
+
return type && legacyButtonTypes.includes(type) ? type : null;
|
|
374
|
+
};
|
|
375
|
+
var convertEnumValue = (value) => {
|
|
376
|
+
if (!value) return { converted: value, wasEnum: false };
|
|
377
|
+
const strippedValue = value.replace(/^['"]|['"]$/gu, "");
|
|
378
|
+
const enumMapping = {
|
|
379
|
+
"Priority.SECONDARY": "secondary",
|
|
380
|
+
"Priority.PRIMARY": "primary",
|
|
381
|
+
"Priority.TERTIARY": "tertiary",
|
|
382
|
+
"ControlType.NEGATIVE": "negative",
|
|
383
|
+
"ControlType.POSITIVE": "positive",
|
|
384
|
+
"ControlType.ACCENT": "accent",
|
|
385
|
+
"ControlType.BUTTON": "button",
|
|
386
|
+
"ControlType.RESET": "reset",
|
|
387
|
+
"HtmlType.SUBMIT": "submit",
|
|
388
|
+
"HtmlType.BUTTON": "button",
|
|
389
|
+
"HtmlType.RESET": "reset",
|
|
390
|
+
"Sentiment.NEGATIVE": "negative"
|
|
391
|
+
};
|
|
392
|
+
const wasEnum = strippedValue in enumMapping;
|
|
393
|
+
const converted = enumMapping[strippedValue] || strippedValue;
|
|
394
|
+
return { converted, wasEnum };
|
|
395
|
+
};
|
|
396
|
+
var transformer = (file, api, options) => {
|
|
397
|
+
if (!validatePackageRequirements(options, packageRequirements)) {
|
|
398
|
+
return file.source;
|
|
399
|
+
}
|
|
400
|
+
const j = api.jscodeshift;
|
|
401
|
+
const root = j(file.source);
|
|
402
|
+
const manualReviewIssues = [];
|
|
403
|
+
const reporter = createReporter(j, manualReviewIssues);
|
|
404
|
+
const { exists: hasButtonImport } = hasImport_default(root, "@transferwise/components", "Button", j);
|
|
405
|
+
const { exists: hasActionButtonImport, remove: removeActionButtonImport } = hasImport_default(
|
|
406
|
+
root,
|
|
407
|
+
"@transferwise/components",
|
|
408
|
+
"ActionButton",
|
|
409
|
+
j
|
|
410
|
+
);
|
|
411
|
+
const iconImports = /* @__PURE__ */ new Set();
|
|
412
|
+
root.find(j.ImportDeclaration, { source: { value: "@transferwise/icons" } }).forEach((path2) => {
|
|
413
|
+
path2.node.specifiers?.forEach((specifier) => {
|
|
414
|
+
if ((specifier.type === "ImportDefaultSpecifier" || specifier.type === "ImportSpecifier") && specifier.local) {
|
|
415
|
+
const localName = specifier.local.name;
|
|
416
|
+
iconImports.add(localName);
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
if (hasActionButtonImport) {
|
|
421
|
+
root.findJSXElements("ActionButton").forEach((path2) => {
|
|
422
|
+
const { openingElement, closingElement } = path2.node;
|
|
423
|
+
openingElement.name = setNameIfJSXIdentifier(openingElement.name, "Button");
|
|
424
|
+
if (closingElement) {
|
|
425
|
+
closingElement.name = setNameIfJSXIdentifier(closingElement.name, "Button");
|
|
426
|
+
}
|
|
427
|
+
addAttributesIfMissing(j, openingElement, [
|
|
428
|
+
{ attribute: j.jsxAttribute(j.jsxIdentifier("v2")), name: "v2" },
|
|
429
|
+
{ attribute: j.jsxAttribute(j.jsxIdentifier("size"), j.literal("sm")), name: "size" }
|
|
430
|
+
]);
|
|
431
|
+
iconUtils_default(j, path2.node.children, iconImports, openingElement);
|
|
432
|
+
if ((openingElement.attributes ?? []).some((attr) => attr.type === "JSXSpreadAttribute")) {
|
|
433
|
+
reporter.reportSpreadProps(path2);
|
|
434
|
+
}
|
|
435
|
+
const legacyPropNames = ["priority", "text"];
|
|
436
|
+
const legacyProps = {};
|
|
437
|
+
openingElement.attributes?.forEach((attr) => {
|
|
438
|
+
if (attr.type === "JSXAttribute" && attr.name && attr.name.type === "JSXIdentifier") {
|
|
439
|
+
const { name } = attr.name;
|
|
440
|
+
if (legacyPropNames.includes(name)) {
|
|
441
|
+
if (attr.value) {
|
|
442
|
+
if (attr.value.type === "StringLiteral") {
|
|
443
|
+
legacyProps[name] = attr.value.value;
|
|
444
|
+
} else if (attr.value.type === "JSXExpressionContainer") {
|
|
445
|
+
reporter.reportAttribute(attr, path2);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
const hasTextProp = openingElement.attributes?.some(
|
|
452
|
+
(attr) => attr.type === "JSXAttribute" && attr.name.type === "JSXIdentifier" && attr.name.name === "text"
|
|
453
|
+
);
|
|
454
|
+
const hasChildren = path2.node.children?.some(
|
|
455
|
+
(child) => child.type === "JSXText" && child.value.trim() !== "" || child.type === "JSXElement" || child.type === "JSXExpressionContainer"
|
|
456
|
+
);
|
|
457
|
+
if (hasTextProp && hasChildren) {
|
|
458
|
+
reporter.reportPropWithChildren(path2, "text");
|
|
459
|
+
}
|
|
460
|
+
(path2.node.children || []).forEach((child) => {
|
|
461
|
+
if (child.type === "JSXExpressionContainer") {
|
|
462
|
+
const expr = child.expression;
|
|
463
|
+
if (expr.type === "ConditionalExpression" || expr.type === "CallExpression" || expr.type === "Identifier" || expr.type === "MemberExpression") {
|
|
464
|
+
reporter.reportAmbiguousChildren(path2, "icon");
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
removeActionButtonImport();
|
|
470
|
+
}
|
|
471
|
+
if (hasButtonImport) {
|
|
472
|
+
root.findJSXElements("Button").forEach((path2) => {
|
|
473
|
+
const { openingElement } = path2.node;
|
|
474
|
+
if (hasAttributeOnElement(openingElement, "v2")) return;
|
|
475
|
+
addAttributesIfMissing(j, openingElement, [
|
|
476
|
+
{ attribute: j.jsxAttribute(j.jsxIdentifier("v2")), name: "v2" }
|
|
477
|
+
]);
|
|
478
|
+
iconUtils_default(j, path2.node.children, iconImports, openingElement);
|
|
479
|
+
const legacyProps = {};
|
|
480
|
+
const legacyPropNames = ["priority", "size", "type", "htmlType", "sentiment"];
|
|
481
|
+
const keepAttributes = [];
|
|
482
|
+
(openingElement.attributes ?? []).forEach((attr) => {
|
|
483
|
+
if (attr.type === "JSXAttribute" && attr.name && attr.name.type === "JSXIdentifier" && legacyPropNames.includes(attr.name.name)) {
|
|
484
|
+
const { name } = attr.name;
|
|
485
|
+
let migrated = false;
|
|
486
|
+
let rawStringValue;
|
|
487
|
+
if (attr.value) {
|
|
488
|
+
if (attr.value.type === "StringLiteral") {
|
|
489
|
+
rawStringValue = attr.value.value;
|
|
490
|
+
legacyProps[name] = attr.value.value;
|
|
491
|
+
} else if (attr.value.type === "JSXExpressionContainer") {
|
|
492
|
+
rawStringValue = String(j(attr.value.expression).toSource());
|
|
493
|
+
const { converted } = convertEnumValue(rawStringValue);
|
|
494
|
+
legacyProps[name] = converted;
|
|
495
|
+
}
|
|
496
|
+
} else {
|
|
497
|
+
legacyProps[name] = void 0;
|
|
498
|
+
}
|
|
499
|
+
const { wasEnum } = convertEnumValue(rawStringValue);
|
|
500
|
+
if (rawStringValue && wasEnum) {
|
|
501
|
+
migrated = true;
|
|
502
|
+
} else if (name === "size") {
|
|
503
|
+
const rawValue = legacyProps.size;
|
|
504
|
+
const resolved2 = resolveSize(rawValue);
|
|
505
|
+
const supportedSizes = ["xs", "sm", "md", "lg", "xl"];
|
|
506
|
+
if (typeof rawValue === "string" && typeof resolved2 === "string" && supportedSizes.includes(resolved2)) {
|
|
507
|
+
migrated = true;
|
|
508
|
+
} else if (typeof rawValue === "string") {
|
|
509
|
+
reporter.reportUnsupportedValue(path2, "size", rawValue);
|
|
510
|
+
} else if (rawValue !== void 0) {
|
|
511
|
+
reporter.reportAmbiguousExpression(path2, "size");
|
|
512
|
+
}
|
|
513
|
+
} else if (name === "priority") {
|
|
514
|
+
const rawValue = legacyProps.priority;
|
|
515
|
+
const { converted } = convertEnumValue(rawValue);
|
|
516
|
+
const mapped = resolvePriority(legacyProps.type, converted);
|
|
517
|
+
const supportedPriorities = ["primary", "secondary", "tertiary", "secondary-neutral"];
|
|
518
|
+
if (typeof rawValue === "string" && typeof mapped === "string" && supportedPriorities.includes(mapped)) {
|
|
519
|
+
migrated = true;
|
|
520
|
+
} else if (typeof rawValue === "string") {
|
|
521
|
+
reporter.reportUnsupportedValue(path2, "priority", rawValue);
|
|
522
|
+
} else if (rawValue !== void 0) {
|
|
523
|
+
reporter.reportAmbiguousExpression(path2, "priority");
|
|
524
|
+
}
|
|
525
|
+
} else if (name === "type" || name === "htmlType") {
|
|
526
|
+
const rawType2 = legacyProps.type;
|
|
527
|
+
const rawHtmlType2 = legacyProps.htmlType;
|
|
528
|
+
const resolvedType2 = typeof rawType2 === "string" ? rawType2 : rawType2 && typeof rawType2 === "object" ? convertEnumValue(j(rawType2).toSource()).converted : void 0;
|
|
529
|
+
const resolved2 = resolveType(resolvedType2, rawHtmlType2);
|
|
530
|
+
const supportedTypes2 = ["button", "reset", "submit"];
|
|
531
|
+
if (typeof resolved2 === "string" && supportedTypes2.includes(resolved2)) {
|
|
532
|
+
migrated = true;
|
|
533
|
+
} else if (typeof rawType2 === "string" || typeof rawHtmlType2 === "string") {
|
|
534
|
+
reporter.reportUnsupportedValue(path2, "type", rawType2 ?? rawHtmlType2 ?? "");
|
|
535
|
+
} else if (rawType2 !== void 0 || rawHtmlType2 !== void 0) {
|
|
536
|
+
reporter.reportAmbiguousExpression(path2, "type");
|
|
537
|
+
}
|
|
538
|
+
} else if (name === "sentiment") {
|
|
539
|
+
const rawValue = legacyProps.sentiment;
|
|
540
|
+
if (rawValue === "negative") {
|
|
541
|
+
migrated = true;
|
|
542
|
+
} else if (typeof rawValue === "string") {
|
|
543
|
+
reporter.reportUnsupportedValue(path2, "sentiment", rawValue);
|
|
544
|
+
} else if (rawValue !== void 0) {
|
|
545
|
+
reporter.reportAmbiguousExpression(path2, "sentiment");
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
if (!migrated) keepAttributes.push(attr);
|
|
549
|
+
} else {
|
|
550
|
+
keepAttributes.push(attr);
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
const newAttributes = [];
|
|
554
|
+
const rawSize = legacyProps.size;
|
|
555
|
+
const resolvedSize = resolveSize(rawSize);
|
|
556
|
+
if (typeof rawSize === "string" && typeof resolvedSize === "string" && ["xs", "sm", "md", "lg", "xl"].includes(resolvedSize)) {
|
|
557
|
+
newAttributes.push(j.jsxAttribute(j.jsxIdentifier("size"), j.literal(resolvedSize)));
|
|
558
|
+
}
|
|
559
|
+
const rawPriority = legacyProps.priority;
|
|
560
|
+
const { converted: convertedPriority } = convertEnumValue(rawPriority);
|
|
561
|
+
const mappedPriority = resolvePriority(legacyProps.type, convertedPriority);
|
|
562
|
+
if (typeof rawPriority === "string" && typeof mappedPriority === "string" && ["primary", "secondary", "tertiary", "secondary-neutral"].includes(mappedPriority)) {
|
|
563
|
+
newAttributes.push(j.jsxAttribute(j.jsxIdentifier("priority"), j.literal(mappedPriority)));
|
|
564
|
+
}
|
|
565
|
+
const rawType = legacyProps.type;
|
|
566
|
+
const rawHtmlType = legacyProps.htmlType;
|
|
567
|
+
const resolvedType = typeof rawType === "string" ? rawType : rawType && typeof rawType === "object" ? convertEnumValue(j(rawType).toSource()).converted : void 0;
|
|
568
|
+
const resolved = resolveType(resolvedType, rawHtmlType);
|
|
569
|
+
const supportedTypes = ["button", "reset", "submit", "negative"];
|
|
570
|
+
if (typeof resolved === "string" && supportedTypes.includes(resolved)) {
|
|
571
|
+
if (resolved !== "negative") {
|
|
572
|
+
newAttributes.push(j.jsxAttribute(j.jsxIdentifier("type"), j.literal(resolved)));
|
|
573
|
+
}
|
|
574
|
+
if (resolved === "negative") {
|
|
575
|
+
newAttributes.push(j.jsxAttribute(j.jsxIdentifier("sentiment"), j.literal("negative")));
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
const rawSentiment = legacyProps.sentiment;
|
|
579
|
+
if (rawSentiment === "negative") {
|
|
580
|
+
newAttributes.push(j.jsxAttribute(j.jsxIdentifier("sentiment"), j.literal("negative")));
|
|
581
|
+
}
|
|
582
|
+
Array.prototype.push.apply(newAttributes, keepAttributes);
|
|
583
|
+
let asIndex = -1;
|
|
584
|
+
let asValue = null;
|
|
585
|
+
let hrefExists = false;
|
|
586
|
+
let asAmbiguous = false;
|
|
587
|
+
let hrefAmbiguous = false;
|
|
588
|
+
newAttributes.forEach((attr, index) => {
|
|
589
|
+
if (attr.type === "JSXAttribute" && attr.name) {
|
|
590
|
+
if (attr.name.name === "as") {
|
|
591
|
+
if (attr.value) {
|
|
592
|
+
if (attr.value.type === "StringLiteral") {
|
|
593
|
+
asValue = attr.value.value;
|
|
594
|
+
} else if (attr.value.type === "JSXExpressionContainer") {
|
|
595
|
+
asAmbiguous = true;
|
|
596
|
+
reporter.reportAttribute(attr, path2);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
asIndex = index;
|
|
600
|
+
}
|
|
601
|
+
if (attr.name.name === "href") {
|
|
602
|
+
hrefExists = true;
|
|
603
|
+
if (attr.value && attr.value.type !== "StringLiteral") {
|
|
604
|
+
hrefAmbiguous = true;
|
|
605
|
+
reporter.reportAttribute(attr, path2);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
if (asValue && asValue !== "a") {
|
|
611
|
+
reporter.reportUnsupportedValue(path2, "as", asValue);
|
|
612
|
+
}
|
|
613
|
+
if (asValue === "a") {
|
|
614
|
+
if (asIndex !== -1) {
|
|
615
|
+
newAttributes.splice(asIndex, 1);
|
|
616
|
+
}
|
|
617
|
+
if (!hrefExists) {
|
|
618
|
+
newAttributes.push(j.jsxAttribute(j.jsxIdentifier("href"), j.literal("#")));
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
openingElement.attributes = newAttributes;
|
|
622
|
+
if ((openingElement.attributes ?? []).some((attr) => attr.type === "JSXSpreadAttribute")) {
|
|
623
|
+
reporter.reportSpreadProps(path2);
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
if (manualReviewIssues.length > 0) {
|
|
628
|
+
manualReviewIssues.forEach(async (issue) => {
|
|
629
|
+
await reportManualReview_default(file.path, issue);
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
return root.toSource();
|
|
633
|
+
};
|
|
634
|
+
var button_default = transformer;
|
|
635
|
+
export {
|
|
636
|
+
button_default as default,
|
|
637
|
+
packageRequirements,
|
|
638
|
+
parser
|
|
639
|
+
};
|
|
640
|
+
//# sourceMappingURL=button.js.map
|