@wise/wds-codemods 0.0.1 → 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 +365 -6
- package/dist/helpers-auDAwIcO.js +2020 -0
- package/dist/helpers-auDAwIcO.js.map +1 -0
- package/dist/index.js +114 -0
- package/dist/index.js.map +1 -0
- package/dist/transforms/button/config.json +28 -0
- package/dist/transforms/button/transformer.js +746 -0
- package/dist/transforms/button/transformer.js.map +1 -0
- package/package.json +37 -18
- package/.changeset/config.json +0 -13
- package/.github/CODEOWNERS +0 -1
- package/.github/actions/bootstrap/action.yml +0 -26
- package/.github/actions/commitlint/action.yml +0 -34
- package/.github/workflows/cd-cd.yml +0 -82
- package/.github/workflows/renovate.yml +0 -16
- package/.husky/commit-msg +0 -1
- package/.husky/pre-commit +0 -1
- package/.nvmrc +0 -1
- package/.prettierignore +0 -1
- package/.prettierrc.js +0 -5
- package/commitlint.config.js +0 -3
- package/eslint.config.js +0 -10
- package/jest.config.js +0 -5
- package/mkdocs.yml +0 -4
- package/renovate.json +0 -9
- package/src/__tests__/index.test.ts +0 -10
- package/src/index.ts +0 -2
- package/tsconfig.json +0 -14
|
@@ -0,0 +1,746 @@
|
|
|
1
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
2
|
+
const require_helpers = require('../../helpers-auDAwIcO.js');
|
|
3
|
+
|
|
4
|
+
//#region src/helpers/addImport.ts
|
|
5
|
+
/**
|
|
6
|
+
* Adds a named import if it doesn't already exist.
|
|
7
|
+
*/
|
|
8
|
+
function addImport(root, sourceValue, importName, j) {
|
|
9
|
+
const existingImports = root.find(j.ImportDeclaration, { source: { value: sourceValue } });
|
|
10
|
+
if (existingImports.size() > 0) {
|
|
11
|
+
if (existingImports.find(j.ImportSpecifier, { imported: { name: importName } }).size() > 0) return;
|
|
12
|
+
existingImports.forEach((path) => {
|
|
13
|
+
if (path.node.specifiers) path.node.specifiers.push(j.importSpecifier(j.identifier(importName)));
|
|
14
|
+
});
|
|
15
|
+
} else {
|
|
16
|
+
const newImport = j.importDeclaration([j.importSpecifier(j.identifier(importName))], j.literal(sourceValue));
|
|
17
|
+
const firstImport = root.find(j.ImportDeclaration).at(0);
|
|
18
|
+
if (firstImport.size() > 0) firstImport.insertBefore(newImport);
|
|
19
|
+
else {
|
|
20
|
+
const program = root.find(j.Program);
|
|
21
|
+
if (program.size() > 0) program.get("body", 0).insertBefore(newImport);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
var addImport_default = addImport;
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/helpers/hasImport.ts
|
|
29
|
+
/**
|
|
30
|
+
* Checks if a specific import exists in the given root collection and provides
|
|
31
|
+
* a method to remove it if found.
|
|
32
|
+
*/
|
|
33
|
+
function hasImport(root, sourceValue, importName, j) {
|
|
34
|
+
const importDeclarations = root.find(j.ImportDeclaration, { source: { value: sourceValue } });
|
|
35
|
+
/**
|
|
36
|
+
* Finds all ImportSpecifier nodes that expose `importName` but
|
|
37
|
+
* from a different source than `sourceValue`.
|
|
38
|
+
*/
|
|
39
|
+
const conflictingImports = (() => {
|
|
40
|
+
const result = [];
|
|
41
|
+
root.find(j.ImportDeclaration).filter((path) => path.node.source.value !== sourceValue).forEach((path) => {
|
|
42
|
+
for (const specifier of path.node.specifiers ?? []) if (specifier.type === "ImportSpecifier" && specifier.imported.name === importName && specifier.local?.name === importName) result.push(specifier);
|
|
43
|
+
});
|
|
44
|
+
return result;
|
|
45
|
+
})();
|
|
46
|
+
if (importDeclarations.size() === 0) return {
|
|
47
|
+
exists: false,
|
|
48
|
+
remove: () => {},
|
|
49
|
+
resolvedName: importName,
|
|
50
|
+
conflictingImports
|
|
51
|
+
};
|
|
52
|
+
const namedImport = importDeclarations.find(j.ImportSpecifier, { imported: { name: importName } });
|
|
53
|
+
const defaultImport = importDeclarations.find(j.ImportDefaultSpecifier, { local: { name: importName } });
|
|
54
|
+
const aliasImport = importDeclarations.find(j.ImportSpecifier).filter((path) => {
|
|
55
|
+
return path.node.imported.name === importName && path.node.imported.name !== path.node.local?.name;
|
|
56
|
+
});
|
|
57
|
+
const exists = namedImport.size() > 0 || defaultImport.size() > 0;
|
|
58
|
+
const resolveName = () => {
|
|
59
|
+
if (aliasImport.size() > 0) {
|
|
60
|
+
const localName = aliasImport.get(0).node.local?.name;
|
|
61
|
+
if (typeof localName === "string") return localName;
|
|
62
|
+
if (localName && typeof localName === "object" && "name" in localName && typeof localName.name === "string") return localName.name;
|
|
63
|
+
return importName;
|
|
64
|
+
}
|
|
65
|
+
return importName;
|
|
66
|
+
};
|
|
67
|
+
const remove = () => {
|
|
68
|
+
importDeclarations.forEach((path) => {
|
|
69
|
+
const filteredSpecifiers = path.node.specifiers?.filter((specifier) => {
|
|
70
|
+
if (specifier.type === "ImportSpecifier" && specifier.imported.name === importName) return false;
|
|
71
|
+
if (specifier.type === "ImportDefaultSpecifier" && specifier.local?.name === importName) return false;
|
|
72
|
+
return true;
|
|
73
|
+
}) ?? [];
|
|
74
|
+
if (filteredSpecifiers.length === 0) path.prune();
|
|
75
|
+
else j(path).replaceWith(j.importDeclaration(filteredSpecifiers, path.node.source, path.node.importKind));
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
exists,
|
|
80
|
+
remove,
|
|
81
|
+
aliases: aliasImport,
|
|
82
|
+
resolvedName: resolveName(),
|
|
83
|
+
conflictingImports
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
var hasImport_default = hasImport;
|
|
87
|
+
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/helpers/iconUtils.ts
|
|
90
|
+
/**
|
|
91
|
+
* Process children of a JSX element to detect icon components and add iconStart or iconEnd attributes accordingly.
|
|
92
|
+
* This is specific to icon handling but can be reused in codemods dealing with icon children.
|
|
93
|
+
*/
|
|
94
|
+
const processIconChildren = (j, children, iconImports, openingElement) => {
|
|
95
|
+
if (!children || !openingElement.attributes) return;
|
|
96
|
+
const unwrapJsxElement = (node) => {
|
|
97
|
+
if (typeof node === "object" && node !== null && "type" in node && node.type === "JSXExpressionContainer" && j.JSXElement.check(node.expression)) return node.expression;
|
|
98
|
+
return node;
|
|
99
|
+
};
|
|
100
|
+
const totalChildren = children.length;
|
|
101
|
+
const iconChildIndex = children.findIndex((child) => {
|
|
102
|
+
const unwrapped = unwrapJsxElement(child);
|
|
103
|
+
return j.JSXElement.check(unwrapped) && unwrapped.openingElement.name.type === "JSXIdentifier" && iconImports.has(unwrapped.openingElement.name.name);
|
|
104
|
+
});
|
|
105
|
+
if (iconChildIndex === -1) return;
|
|
106
|
+
const iconChild = unwrapJsxElement(children[iconChildIndex]);
|
|
107
|
+
if (!iconChild || iconChild.openingElement.name.type !== "JSXIdentifier") return;
|
|
108
|
+
iconChild.openingElement.name.name;
|
|
109
|
+
const distanceToStart = iconChildIndex;
|
|
110
|
+
const distanceToEnd = totalChildren - 1 - iconChildIndex;
|
|
111
|
+
const iconPropName = distanceToStart <= distanceToEnd ? "addonStart" : "addonEnd";
|
|
112
|
+
const iconObject = j.objectExpression([j.property("init", j.identifier("type"), j.literal("icon")), j.property("init", j.identifier("value"), iconChild)]);
|
|
113
|
+
const iconProp = j.jsxAttribute(j.jsxIdentifier(iconPropName), j.jsxExpressionContainer(iconObject));
|
|
114
|
+
openingElement.attributes.push(iconProp);
|
|
115
|
+
children.splice(iconChildIndex, 1);
|
|
116
|
+
const isWhitespaceJsxText = (node) => {
|
|
117
|
+
return typeof node === "object" && node !== null && node.type === "JSXText" && typeof node.value === "string" && node.value.trim() === "";
|
|
118
|
+
};
|
|
119
|
+
if (iconChildIndex - 1 >= 0 && isWhitespaceJsxText(children[iconChildIndex - 1])) children.splice(iconChildIndex - 1, 1);
|
|
120
|
+
else if (isWhitespaceJsxText(children[iconChildIndex])) children.splice(iconChildIndex, 1);
|
|
121
|
+
};
|
|
122
|
+
var iconUtils_default = processIconChildren;
|
|
123
|
+
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region src/helpers/jsxElementUtils.ts
|
|
126
|
+
/**
|
|
127
|
+
* Rename a JSX element name if it is a JSXIdentifier.
|
|
128
|
+
*/
|
|
129
|
+
const setNameIfJSXIdentifier = (elementName, newName) => {
|
|
130
|
+
if (elementName && elementName.type === "JSXIdentifier") return {
|
|
131
|
+
...elementName,
|
|
132
|
+
name: newName
|
|
133
|
+
};
|
|
134
|
+
return elementName;
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* Check if a list of attributes contains a specific attribute by name.
|
|
138
|
+
*/
|
|
139
|
+
const hasAttribute = (attributes, attributeName) => {
|
|
140
|
+
return Array.isArray(attributes) && attributes.some((attr) => attr.type === "JSXAttribute" && attr.name.type === "JSXIdentifier" && attr.name.name === attributeName);
|
|
141
|
+
};
|
|
142
|
+
/**
|
|
143
|
+
* Check if a JSX element's openingElement has a specific attribute.
|
|
144
|
+
*/
|
|
145
|
+
const hasAttributeOnElement = (element, attributeName) => {
|
|
146
|
+
return hasAttribute(element.attributes, attributeName);
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* Add specified attributes to a JSX element's openingElement if they are not already present.
|
|
150
|
+
*/
|
|
151
|
+
const addAttributesIfMissing = (j, openingElement, attributesToAdd) => {
|
|
152
|
+
if (!Array.isArray(openingElement.attributes)) return;
|
|
153
|
+
const attrs = openingElement.attributes;
|
|
154
|
+
attributesToAdd.forEach(({ attribute, name }) => {
|
|
155
|
+
if (!hasAttributeOnElement(openingElement, name)) attrs.push(attribute);
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Returns a collection of JSX elements that match the specified
|
|
160
|
+
* exported name or names of the found aliases.
|
|
161
|
+
*/
|
|
162
|
+
const findJSXElementsByName = (root, j) => (exportedName, aliases) => {
|
|
163
|
+
const aliasNames = aliases?.size() ? aliases.paths().map((path) => path.node.local?.name) : [];
|
|
164
|
+
return root.find(j.JSXElement).filter((path) => {
|
|
165
|
+
const { name } = path.node.openingElement;
|
|
166
|
+
return name.type === "JSXIdentifier" && (name.name === exportedName || aliasNames.includes(name.name));
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
/**
|
|
170
|
+
* Removes an attribute by name from a JSX element's openingElement.
|
|
171
|
+
*/
|
|
172
|
+
const removeAttributeByName = (j, openingElement, attributeName) => {
|
|
173
|
+
if (!Array.isArray(openingElement.attributes)) return;
|
|
174
|
+
openingElement.attributes = openingElement.attributes.filter((attr) => {
|
|
175
|
+
return !(attr.type === "JSXAttribute" && attr.name.type === "JSXIdentifier" && attr.name.name === attributeName);
|
|
176
|
+
});
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
//#endregion
|
|
180
|
+
//#region src/helpers/jsxReportingUtils.ts
|
|
181
|
+
/**
|
|
182
|
+
* CodemodReporter is a utility class for reporting issues found during codemod transformations.
|
|
183
|
+
* It provides methods to report issues related to JSX elements, props, and attributes.
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```typescript
|
|
187
|
+
* const issues: string[] = [];
|
|
188
|
+
* const reporter = createReporter(j, issues);
|
|
189
|
+
*
|
|
190
|
+
* // Report a deprecated prop
|
|
191
|
+
* reporter.reportDeprecatedProp(buttonElement, 'flat', 'variant="text"');
|
|
192
|
+
*
|
|
193
|
+
* // Report complex expression that needs review
|
|
194
|
+
* reporter.reportAmbiguousExpression(element, 'size');
|
|
195
|
+
*
|
|
196
|
+
* // Auto-detect common issues
|
|
197
|
+
* reporter.reportAttributeIssues(element);
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
200
|
+
var CodemodReporter = class {
|
|
201
|
+
j;
|
|
202
|
+
issues;
|
|
203
|
+
constructor(options) {
|
|
204
|
+
this.j = options.jscodeshift;
|
|
205
|
+
this.issues = options.issues;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Reports an issue with a JSX element
|
|
209
|
+
*/
|
|
210
|
+
reportElement(element, reason) {
|
|
211
|
+
const node = this.getNode(element);
|
|
212
|
+
const componentName = this.getComponentName(node);
|
|
213
|
+
const line = this.getLineNumber(node);
|
|
214
|
+
this.addIssue(`Manual review required: <${componentName}> at line ${line} ${reason}.`);
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Reports an issue with a specific prop
|
|
218
|
+
*/
|
|
219
|
+
reportProp(element, propName, reason) {
|
|
220
|
+
const node = this.getNode(element);
|
|
221
|
+
const componentName = this.getComponentName(node);
|
|
222
|
+
const line = this.getLineNumber(node);
|
|
223
|
+
this.addIssue(`Manual review required: prop "${propName}" on <${componentName}> at line ${line} ${reason}.`);
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Reports an issue with a JSX attribute directly
|
|
227
|
+
*/
|
|
228
|
+
reportAttribute(attr, element, reason) {
|
|
229
|
+
const node = this.getNode(element);
|
|
230
|
+
const componentName = this.getComponentName(node);
|
|
231
|
+
const propName = this.getAttributeName(attr);
|
|
232
|
+
const line = this.getLineNumber(attr) || this.getLineNumber(node);
|
|
233
|
+
const defaultReason = this.getAttributeReason(attr);
|
|
234
|
+
const finalReason = reason || defaultReason;
|
|
235
|
+
this.addIssue(`Manual review required: prop "${propName}" on <${componentName}> at line ${line} ${finalReason}.`);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Reports spread props on an element
|
|
239
|
+
*/
|
|
240
|
+
reportSpreadProps(element) {
|
|
241
|
+
this.reportElement(element, "contains spread props that need manual review");
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Reports conflicting prop and children
|
|
245
|
+
*/
|
|
246
|
+
reportPropWithChildren(element, propName) {
|
|
247
|
+
this.reportProp(element, propName, `conflicts with children - both "${propName}" prop and children are present`);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Reports unsupported prop value
|
|
251
|
+
*/
|
|
252
|
+
reportUnsupportedValue(element, propName, value) {
|
|
253
|
+
this.reportProp(element, propName, `has unsupported value "${value}"`);
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Reports ambiguous expression in prop
|
|
257
|
+
*/
|
|
258
|
+
reportAmbiguousExpression(element, propName) {
|
|
259
|
+
this.reportProp(element, propName, "contains a complex expression that needs manual review");
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Reports ambiguous children (like dynamic icons)
|
|
263
|
+
*/
|
|
264
|
+
reportAmbiguousChildren(element, childType = "content") {
|
|
265
|
+
this.reportElement(element, `contains ambiguous ${childType} that needs manual review`);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Reports deprecated prop usage
|
|
269
|
+
*/
|
|
270
|
+
reportDeprecatedProp(element, propName, alternative) {
|
|
271
|
+
const suggestion = alternative ? ` Use ${alternative} instead` : "";
|
|
272
|
+
this.reportProp(element, propName, `is deprecated${suggestion}`);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Reports missing required prop
|
|
276
|
+
*/
|
|
277
|
+
reportMissingRequiredProp(element, propName) {
|
|
278
|
+
this.reportProp(element, propName, "is required but missing");
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Reports conflicting props
|
|
282
|
+
*/
|
|
283
|
+
reportConflictingProps(element, propNames) {
|
|
284
|
+
const propList = propNames.map((name) => `"${name}"`).join(", ");
|
|
285
|
+
this.reportElement(element, `has conflicting props: ${propList} cannot be used together`);
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Auto-detects and reports common attribute issues
|
|
289
|
+
*/
|
|
290
|
+
reportAttributeIssues(element) {
|
|
291
|
+
const { attributes } = this.getNode(element).openingElement;
|
|
292
|
+
if (!attributes) return;
|
|
293
|
+
if (attributes.some((attr) => attr.type === "JSXSpreadAttribute")) this.reportSpreadProps(element);
|
|
294
|
+
attributes.forEach((attr) => {
|
|
295
|
+
if (attr.type === "JSXAttribute" && attr.value?.type === "JSXExpressionContainer") this.reportAttribute(attr, element);
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Finds and reports instances of components that are under an alias (imported with a different name)
|
|
300
|
+
*/
|
|
301
|
+
reportAliases(element) {
|
|
302
|
+
this.reportElement(element, "is used via an import alias and needs manual review");
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Finds and reports instances of non-DS import declarations that conflict with the component name
|
|
306
|
+
*/
|
|
307
|
+
reportConflictingImports(node) {
|
|
308
|
+
this.addIssue(`Manual review required: Non-WDS package resulting in an import conflict at line ${this.getLineNumber(node)}.`);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Reports enum usage for future conversion tracking
|
|
312
|
+
*/
|
|
313
|
+
reportEnumUsage(element, propName, enumValue) {
|
|
314
|
+
this.reportProp(element, propName, `uses enum value "${enumValue}" which has been preserved but should be migrated to a string literal in the future`);
|
|
315
|
+
}
|
|
316
|
+
getNode(element) {
|
|
317
|
+
return "node" in element ? element.node : element;
|
|
318
|
+
}
|
|
319
|
+
getComponentName(node) {
|
|
320
|
+
const { name } = node.openingElement;
|
|
321
|
+
if (name.type === "JSXIdentifier") return name.name;
|
|
322
|
+
return this.j(name).toSource();
|
|
323
|
+
}
|
|
324
|
+
getLineNumber(node) {
|
|
325
|
+
return node.loc?.start.line?.toString() || "unknown";
|
|
326
|
+
}
|
|
327
|
+
getAttributeName(attr) {
|
|
328
|
+
if (attr.name.type === "JSXIdentifier") return attr.name.name;
|
|
329
|
+
return this.j(attr.name).toSource();
|
|
330
|
+
}
|
|
331
|
+
getAttributeReason(attr) {
|
|
332
|
+
if (!attr.value) return "has no value";
|
|
333
|
+
if (attr.value.type === "JSXExpressionContainer") {
|
|
334
|
+
const expr = attr.value.expression;
|
|
335
|
+
const expressionType = expr.type.replace("Expression", "").toLowerCase();
|
|
336
|
+
if (expr.type === "Identifier" || expr.type === "MemberExpression") {
|
|
337
|
+
const valueText = this.j(expr).toSource();
|
|
338
|
+
return `contains a ${expressionType} (${valueText})`;
|
|
339
|
+
}
|
|
340
|
+
return `contains a complex ${expressionType} expression`;
|
|
341
|
+
}
|
|
342
|
+
return "needs manual review";
|
|
343
|
+
}
|
|
344
|
+
addIssue(message) {
|
|
345
|
+
this.issues.push(message);
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
const createReporter = (j, issues) => {
|
|
349
|
+
return new CodemodReporter({
|
|
350
|
+
jscodeshift: j,
|
|
351
|
+
issues
|
|
352
|
+
});
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
//#endregion
|
|
356
|
+
//#region src/transforms/button/transformer.ts
|
|
357
|
+
const parser = "tsx";
|
|
358
|
+
const buildPriorityMapping = (opts) => {
|
|
359
|
+
const extendedOpts = opts;
|
|
360
|
+
const accentSecondary = extendedOpts.accentSecondaryMapping || "secondary-neutral";
|
|
361
|
+
const positiveSecondary = extendedOpts.positiveSecondaryMapping || "secondary-neutral";
|
|
362
|
+
return {
|
|
363
|
+
accent: {
|
|
364
|
+
primary: "primary",
|
|
365
|
+
secondary: accentSecondary,
|
|
366
|
+
tertiary: "tertiary"
|
|
367
|
+
},
|
|
368
|
+
positive: {
|
|
369
|
+
primary: "primary",
|
|
370
|
+
secondary: positiveSecondary,
|
|
371
|
+
tertiary: positiveSecondary
|
|
372
|
+
},
|
|
373
|
+
negative: {
|
|
374
|
+
primary: "primary",
|
|
375
|
+
secondary: "secondary",
|
|
376
|
+
tertiary: "secondary"
|
|
377
|
+
},
|
|
378
|
+
primary: {
|
|
379
|
+
primary: "primary",
|
|
380
|
+
secondary: accentSecondary,
|
|
381
|
+
tertiary: "tertiary"
|
|
382
|
+
},
|
|
383
|
+
pay: {
|
|
384
|
+
primary: "primary",
|
|
385
|
+
secondary: accentSecondary,
|
|
386
|
+
tertiary: accentSecondary
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
};
|
|
390
|
+
const sizeMap = {
|
|
391
|
+
EXTRA_SMALL: "xs",
|
|
392
|
+
SMALL: "sm",
|
|
393
|
+
MEDIUM: "md",
|
|
394
|
+
LARGE: "lg",
|
|
395
|
+
EXTRA_LARGE: "xl",
|
|
396
|
+
xs: "sm",
|
|
397
|
+
sm: "sm",
|
|
398
|
+
md: "md",
|
|
399
|
+
lg: "lg",
|
|
400
|
+
xl: "xl"
|
|
401
|
+
};
|
|
402
|
+
const resolveSize = (size) => {
|
|
403
|
+
if (!size) return size;
|
|
404
|
+
const match = /^Size\.(EXTRA_SMALL|SMALL|MEDIUM|LARGE|EXTRA_LARGE)$/u.exec(size);
|
|
405
|
+
if (match) return sizeMap[match[1]];
|
|
406
|
+
return sizeMap[size] || size;
|
|
407
|
+
};
|
|
408
|
+
const legacyButtonTypes = [
|
|
409
|
+
"accent",
|
|
410
|
+
"negative",
|
|
411
|
+
"positive",
|
|
412
|
+
"primary",
|
|
413
|
+
"pay",
|
|
414
|
+
"secondary",
|
|
415
|
+
"danger",
|
|
416
|
+
"link"
|
|
417
|
+
];
|
|
418
|
+
const getConsistentTypeConversions = (type) => {
|
|
419
|
+
return {
|
|
420
|
+
secondary: { priority: "secondary-neutral" },
|
|
421
|
+
link: { priority: "tertiary" },
|
|
422
|
+
danger: {
|
|
423
|
+
priority: "secondary",
|
|
424
|
+
sentiment: "negative"
|
|
425
|
+
}
|
|
426
|
+
}[type || ""] || null;
|
|
427
|
+
};
|
|
428
|
+
const convertEnumValue = (value) => {
|
|
429
|
+
if (!value) return value;
|
|
430
|
+
const strippedValue = value.replace(/^['"]|['"]$/gu, "");
|
|
431
|
+
return {
|
|
432
|
+
"Priority.SECONDARY": "secondary",
|
|
433
|
+
"Priority.PRIMARY": "primary",
|
|
434
|
+
"Priority.TERTIARY": "tertiary",
|
|
435
|
+
"ControlType.NEGATIVE": "negative",
|
|
436
|
+
"ControlType.POSITIVE": "positive",
|
|
437
|
+
"ControlType.ACCENT": "accent"
|
|
438
|
+
}[strippedValue] || strippedValue;
|
|
439
|
+
};
|
|
440
|
+
/**
|
|
441
|
+
* Detects if a value is an enum pattern (e.g., Priority.PRIMARY, ControlType.ACCENT, Size.LARGE, Type.PRIMARY)
|
|
442
|
+
*/
|
|
443
|
+
const isEnumValue = (value) => {
|
|
444
|
+
return [
|
|
445
|
+
/^Priority\.(PRIMARY|SECONDARY|TERTIARY|SECONDARY_NEUTRAL)$/u,
|
|
446
|
+
/^ControlType\.(ACCENT|NEGATIVE|POSITIVE)$/u,
|
|
447
|
+
/^Size\.(EXTRA_SMALL|SMALL|MEDIUM|LARGE|EXTRA_LARGE)$/u,
|
|
448
|
+
/^Type\.(PRIMARY|SECONDARY|TERTIARY|PAY|DANGER|LINK|ACCENT|POSITIVE|NEGATIVE)$/u
|
|
449
|
+
].some((pattern) => pattern.test(value));
|
|
450
|
+
};
|
|
451
|
+
/**
|
|
452
|
+
* Maps enum values to their expected string equivalents for validation purposes
|
|
453
|
+
* This is ONLY used to validate the enum maps to a supported value
|
|
454
|
+
*/
|
|
455
|
+
const getEnumEquivalent = (value) => {
|
|
456
|
+
return {
|
|
457
|
+
"Priority.PRIMARY": "primary",
|
|
458
|
+
"Priority.SECONDARY": "secondary",
|
|
459
|
+
"Priority.TERTIARY": "tertiary",
|
|
460
|
+
"Priority.SECONDARY_NEUTRAL": "secondary-neutral",
|
|
461
|
+
"ControlType.NEGATIVE": "negative",
|
|
462
|
+
"ControlType.POSITIVE": "positive",
|
|
463
|
+
"ControlType.ACCENT": "accent",
|
|
464
|
+
"Size.EXTRA_SMALL": "sm",
|
|
465
|
+
"Size.SMALL": "sm",
|
|
466
|
+
"Size.MEDIUM": "md",
|
|
467
|
+
"Size.LARGE": "lg",
|
|
468
|
+
"Size.EXTRA_LARGE": "xl"
|
|
469
|
+
}[value];
|
|
470
|
+
};
|
|
471
|
+
/**
|
|
472
|
+
* This transform function modifies the Button and ActionButton components from the @transferwise/components library.
|
|
473
|
+
* It updates the ActionButton component to use the Button component with specific attributes and mappings.
|
|
474
|
+
* It also processes icon children and removes legacy props.
|
|
475
|
+
*
|
|
476
|
+
* @param {FileInfo} file - The file information object.
|
|
477
|
+
* @param {API} api - The API object for jscodeshift.
|
|
478
|
+
* @param {Options} options - The options object for jscodeshift.
|
|
479
|
+
* @returns {string} - The transformed source code.
|
|
480
|
+
*/
|
|
481
|
+
const transformer = (file, api, options) => {
|
|
482
|
+
const j = api.jscodeshift;
|
|
483
|
+
const root = j(file.source);
|
|
484
|
+
const manualReviewIssues = [];
|
|
485
|
+
const priorityMapping = buildPriorityMapping(options);
|
|
486
|
+
const resolvePriority = (type, priority) => {
|
|
487
|
+
if (type && priority) return priorityMapping[type]?.[priority] || priority;
|
|
488
|
+
return priority;
|
|
489
|
+
};
|
|
490
|
+
const reporter = createReporter(j, manualReviewIssues);
|
|
491
|
+
const { exists: hasButtonImport, aliases: buttonAliases, resolvedName: buttonName, conflictingImports: conflictingButtonImport } = hasImport_default(root, "@transferwise/components", "Button", j);
|
|
492
|
+
if (conflictingButtonImport.length) conflictingButtonImport.forEach((node) => reporter.reportConflictingImports(node));
|
|
493
|
+
const { exists: hasActionButtonImport, remove: removeActionButtonImport, aliases: actionButtonAliases } = hasImport_default(root, "@transferwise/components", "ActionButton", j);
|
|
494
|
+
if (!hasButtonImport && !hasActionButtonImport) return file.source;
|
|
495
|
+
const iconImports = /* @__PURE__ */ new Set();
|
|
496
|
+
root.find(j.ImportDeclaration, { source: { value: "@transferwise/icons" } }).forEach((path) => {
|
|
497
|
+
path.node.specifiers?.forEach((specifier) => {
|
|
498
|
+
if ((specifier.type === "ImportDefaultSpecifier" || specifier.type === "ImportSpecifier") && specifier.local) {
|
|
499
|
+
const localName = specifier.local.name;
|
|
500
|
+
iconImports.add(localName);
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
if (hasActionButtonImport) {
|
|
505
|
+
if (!hasButtonImport) addImport_default(root, "@transferwise/components", "Button", j);
|
|
506
|
+
findJSXElementsByName(root, j)("ActionButton", actionButtonAliases).forEach((path) => {
|
|
507
|
+
const { openingElement, closingElement } = path.node;
|
|
508
|
+
openingElement.name = setNameIfJSXIdentifier(openingElement.name, buttonName);
|
|
509
|
+
if (closingElement) closingElement.name = setNameIfJSXIdentifier(closingElement.name, buttonName);
|
|
510
|
+
iconUtils_default(j, path.node.children, iconImports, openingElement);
|
|
511
|
+
if ((openingElement.attributes ?? []).some((attr) => attr.type === "JSXSpreadAttribute")) reporter.reportSpreadProps(path);
|
|
512
|
+
const legacyPropNames = [
|
|
513
|
+
"priority",
|
|
514
|
+
"text",
|
|
515
|
+
"size"
|
|
516
|
+
];
|
|
517
|
+
const legacyProps = {};
|
|
518
|
+
openingElement.attributes?.forEach((attr) => {
|
|
519
|
+
if (attr.type === "JSXAttribute" && attr.name && attr.name.type === "JSXIdentifier") {
|
|
520
|
+
const { name } = attr.name;
|
|
521
|
+
if (legacyPropNames.includes(name)) {
|
|
522
|
+
if (attr.value) {
|
|
523
|
+
if (attr.value.type === "StringLiteral") legacyProps[name] = attr.value.value;
|
|
524
|
+
else if (attr.value.type === "JSXExpressionContainer") legacyProps[name] = convertEnumValue(String(j(attr.value.expression).toSource()));
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
const hasTextProp = "text" in legacyProps;
|
|
530
|
+
const hasChildren = path.node.children?.some((child) => child.type === "JSXText" && child.value.trim() !== "" || child.type === "JSXElement" || child.type === "JSXExpressionContainer") || path.node.children && path.node.children?.length > 0;
|
|
531
|
+
if (hasTextProp && hasChildren) reporter.reportPropWithChildren(path, "text");
|
|
532
|
+
else if (hasTextProp && !hasChildren && openingElement.selfClosing) path.replace(j.jsxElement(j.jsxOpeningElement(openingElement.name, openingElement.attributes), j.jsxClosingElement(openingElement.name), [j.jsxText(legacyProps.text || "")]));
|
|
533
|
+
addAttributesIfMissing(j, path.node.openingElement, [{
|
|
534
|
+
attribute: j.jsxAttribute(j.jsxIdentifier("v2")),
|
|
535
|
+
name: "v2"
|
|
536
|
+
}, {
|
|
537
|
+
attribute: j.jsxAttribute(j.jsxIdentifier("size"), j.literal("sm")),
|
|
538
|
+
name: "size"
|
|
539
|
+
}]);
|
|
540
|
+
(path.node.children || []).forEach((child) => {
|
|
541
|
+
if (child.type === "JSXExpressionContainer") {
|
|
542
|
+
const expr = child.expression;
|
|
543
|
+
if (expr.type === "ConditionalExpression" || expr.type === "CallExpression" || expr.type === "Identifier" || expr.type === "MemberExpression") reporter.reportAmbiguousChildren(path, "icon");
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
});
|
|
547
|
+
removeActionButtonImport();
|
|
548
|
+
}
|
|
549
|
+
if (hasButtonImport) findJSXElementsByName(root, j)("Button", buttonAliases).forEach((path) => {
|
|
550
|
+
const { openingElement } = path.node;
|
|
551
|
+
if (hasAttributeOnElement(openingElement, "v2")) return;
|
|
552
|
+
const hasJSXChildren = path.node.children?.some((child) => child.type === "JSXText" && child.value.trim() !== "" || child.type === "JSXElement" || child.type === "JSXFragment" && child.children && child.children.length > 0 || child.type === "JSXExpressionContainer" && child.expression.type !== "JSXEmptyExpression");
|
|
553
|
+
const hasChildrenAsProp = openingElement.attributes?.some((attr) => attr.type === "JSXAttribute" && attr.name?.type === "JSXIdentifier" && attr.name.name === "children");
|
|
554
|
+
if (!hasJSXChildren && !hasChildrenAsProp) return;
|
|
555
|
+
addAttributesIfMissing(j, openingElement, [{
|
|
556
|
+
attribute: j.jsxAttribute(j.jsxIdentifier("v2")),
|
|
557
|
+
name: "v2"
|
|
558
|
+
}]);
|
|
559
|
+
iconUtils_default(j, path.node.children, iconImports, openingElement);
|
|
560
|
+
const legacyProps = {};
|
|
561
|
+
const legacyPropNames = [
|
|
562
|
+
"priority",
|
|
563
|
+
"size",
|
|
564
|
+
"type",
|
|
565
|
+
"htmlType",
|
|
566
|
+
"sentiment"
|
|
567
|
+
];
|
|
568
|
+
openingElement.attributes?.forEach((attr) => {
|
|
569
|
+
if (attr.type === "JSXAttribute" && attr.name && attr.name.type === "JSXIdentifier") {
|
|
570
|
+
const { name } = attr.name;
|
|
571
|
+
if (legacyPropNames.includes(name)) if (attr.value) {
|
|
572
|
+
if (attr.value.type === "StringLiteral") legacyProps[name] = attr.value.value;
|
|
573
|
+
else if (attr.value.type === "JSXExpressionContainer") legacyProps[name] = String(j(attr.value.expression).toSource());
|
|
574
|
+
} else legacyProps[name] = void 0;
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
if ("size" in legacyProps) {
|
|
578
|
+
const rawValue = legacyProps.size;
|
|
579
|
+
if (typeof rawValue === "string" && isEnumValue(rawValue)) {
|
|
580
|
+
const equivalent = getEnumEquivalent(rawValue);
|
|
581
|
+
if (equivalent && [
|
|
582
|
+
"sm",
|
|
583
|
+
"md",
|
|
584
|
+
"lg",
|
|
585
|
+
"xl"
|
|
586
|
+
].includes(equivalent)) {} else reporter.reportUnsupportedValue(path, "size", rawValue);
|
|
587
|
+
} else {
|
|
588
|
+
const resolved = resolveSize(rawValue);
|
|
589
|
+
if (typeof rawValue === "string" && typeof resolved === "string" && [
|
|
590
|
+
"sm",
|
|
591
|
+
"md",
|
|
592
|
+
"lg",
|
|
593
|
+
"xl"
|
|
594
|
+
].includes(resolved)) {
|
|
595
|
+
removeAttributeByName(j, openingElement, "size");
|
|
596
|
+
openingElement.attributes?.push(j.jsxAttribute(j.jsxIdentifier("size"), j.literal(resolved)));
|
|
597
|
+
} else if (typeof rawValue === "string") reporter.reportUnsupportedValue(path, "size", rawValue);
|
|
598
|
+
else if (rawValue !== void 0) reporter.reportAmbiguousExpression(path, "size");
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
if ("priority" in legacyProps) {
|
|
602
|
+
const rawValue = legacyProps.priority;
|
|
603
|
+
if (typeof rawValue === "string" && isEnumValue(rawValue)) {
|
|
604
|
+
const equivalent = getEnumEquivalent(rawValue);
|
|
605
|
+
if (equivalent && [
|
|
606
|
+
"primary",
|
|
607
|
+
"secondary",
|
|
608
|
+
"tertiary",
|
|
609
|
+
"secondary-neutral"
|
|
610
|
+
].includes(equivalent)) {} else reporter.reportUnsupportedValue(path, "priority", rawValue);
|
|
611
|
+
} else {
|
|
612
|
+
const converted = convertEnumValue(rawValue);
|
|
613
|
+
const mapped = resolvePriority(legacyProps.type, converted);
|
|
614
|
+
if (typeof rawValue === "string" && typeof mapped === "string" && [
|
|
615
|
+
"primary",
|
|
616
|
+
"secondary",
|
|
617
|
+
"tertiary",
|
|
618
|
+
"secondary-neutral"
|
|
619
|
+
].includes(mapped)) {
|
|
620
|
+
removeAttributeByName(j, openingElement, "priority");
|
|
621
|
+
openingElement.attributes?.push(j.jsxAttribute(j.jsxIdentifier("priority"), j.literal(mapped)));
|
|
622
|
+
} else if (typeof rawValue === "string") reporter.reportUnsupportedValue(path, "priority", rawValue);
|
|
623
|
+
else if (rawValue !== void 0) reporter.reportAmbiguousExpression(path, "priority");
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
if ("type" in legacyProps || "htmlType" in legacyProps) {
|
|
627
|
+
const rawType = legacyProps.type;
|
|
628
|
+
const rawHtmlType = legacyProps.htmlType;
|
|
629
|
+
let resolvedType;
|
|
630
|
+
let isTypeEnum = false;
|
|
631
|
+
let isControlTypeEnum = false;
|
|
632
|
+
if (typeof rawType === "string" && isEnumValue(rawType)) {
|
|
633
|
+
isTypeEnum = true;
|
|
634
|
+
resolvedType = getEnumEquivalent(rawType);
|
|
635
|
+
isControlTypeEnum = rawType.startsWith("ControlType.");
|
|
636
|
+
if (!isControlTypeEnum) reporter.reportUnsupportedValue(path, "type", rawType);
|
|
637
|
+
} else {
|
|
638
|
+
let typeValue;
|
|
639
|
+
if (typeof rawType === "string") typeValue = rawType;
|
|
640
|
+
else if (rawType && typeof rawType === "object") typeValue = convertEnumValue(j(rawType).toSource());
|
|
641
|
+
resolvedType = typeValue;
|
|
642
|
+
}
|
|
643
|
+
let finalHtmlType = null;
|
|
644
|
+
if (resolvedType && !legacyButtonTypes.includes(resolvedType)) finalHtmlType = resolvedType;
|
|
645
|
+
if (rawHtmlType) finalHtmlType = rawHtmlType;
|
|
646
|
+
const htmlTypes = [
|
|
647
|
+
"submit",
|
|
648
|
+
"button",
|
|
649
|
+
"reset"
|
|
650
|
+
];
|
|
651
|
+
if (resolvedType === "negative" && isControlTypeEnum) {
|
|
652
|
+
removeAttributeByName(j, openingElement, "type");
|
|
653
|
+
if (hasAttributeOnElement(openingElement, "sentiment")) removeAttributeByName(j, openingElement, "sentiment");
|
|
654
|
+
addAttributesIfMissing(j, openingElement, [{
|
|
655
|
+
attribute: j.jsxAttribute(j.jsxIdentifier("priority"), j.literal("primary")),
|
|
656
|
+
name: "priority"
|
|
657
|
+
}]);
|
|
658
|
+
if (rawType) openingElement.attributes?.push(j.jsxAttribute(j.jsxIdentifier("sentiment"), j.jsxExpressionContainer(j.identifier(rawType))));
|
|
659
|
+
} else if (resolvedType === "negative" && !isTypeEnum) {
|
|
660
|
+
removeAttributeByName(j, openingElement, "type");
|
|
661
|
+
if (hasAttributeOnElement(openingElement, "sentiment")) removeAttributeByName(j, openingElement, "sentiment");
|
|
662
|
+
openingElement.attributes?.push(j.jsxAttribute(j.jsxIdentifier("sentiment"), j.literal("negative")));
|
|
663
|
+
} else if (isControlTypeEnum && resolvedType && ["positive", "accent"].includes(resolvedType)) {
|
|
664
|
+
removeAttributeByName(j, openingElement, "type");
|
|
665
|
+
addAttributesIfMissing(j, openingElement, [{
|
|
666
|
+
attribute: j.jsxAttribute(j.jsxIdentifier("priority"), j.literal("primary")),
|
|
667
|
+
name: "priority"
|
|
668
|
+
}]);
|
|
669
|
+
}
|
|
670
|
+
if (resolvedType && typeof resolvedType === "string" && legacyButtonTypes.includes(resolvedType) && !isTypeEnum) {
|
|
671
|
+
const consistentConversion = getConsistentTypeConversions(resolvedType);
|
|
672
|
+
if (consistentConversion) {
|
|
673
|
+
removeAttributeByName(j, openingElement, "type");
|
|
674
|
+
removeAttributeByName(j, openingElement, "priority");
|
|
675
|
+
removeAttributeByName(j, openingElement, "sentiment");
|
|
676
|
+
if (consistentConversion.priority) openingElement.attributes?.push(j.jsxAttribute(j.jsxIdentifier("priority"), j.literal(consistentConversion.priority)));
|
|
677
|
+
if (consistentConversion.sentiment) openingElement.attributes?.push(j.jsxAttribute(j.jsxIdentifier("sentiment"), j.literal(consistentConversion.sentiment)));
|
|
678
|
+
} else {
|
|
679
|
+
removeAttributeByName(j, openingElement, "type");
|
|
680
|
+
addAttributesIfMissing(j, openingElement, [{
|
|
681
|
+
attribute: j.jsxAttribute(j.jsxIdentifier("priority"), j.literal("primary")),
|
|
682
|
+
name: "priority"
|
|
683
|
+
}]);
|
|
684
|
+
}
|
|
685
|
+
} else if (isTypeEnum) {}
|
|
686
|
+
if (typeof finalHtmlType === "string" && htmlTypes.includes(finalHtmlType)) {
|
|
687
|
+
removeAttributeByName(j, openingElement, "htmlType");
|
|
688
|
+
openingElement.attributes?.push(j.jsxAttribute(j.jsxIdentifier("type"), j.literal(finalHtmlType)));
|
|
689
|
+
} else if (typeof rawType === "string" || typeof rawHtmlType === "string") {
|
|
690
|
+
const valueToCheck = rawType ?? rawHtmlType ?? "";
|
|
691
|
+
if (![
|
|
692
|
+
"accent",
|
|
693
|
+
"positive",
|
|
694
|
+
"negative",
|
|
695
|
+
"primary",
|
|
696
|
+
"secondary",
|
|
697
|
+
"danger",
|
|
698
|
+
"link"
|
|
699
|
+
].includes(valueToCheck)) reporter.reportUnsupportedValue(path, "type", valueToCheck);
|
|
700
|
+
} else if (rawType !== void 0 || rawHtmlType !== void 0) reporter.reportAmbiguousExpression(path, typeof rawType === "string" ? "type" : "htmlType");
|
|
701
|
+
}
|
|
702
|
+
if ("sentiment" in legacyProps) {
|
|
703
|
+
if (!openingElement.attributes?.some((attr) => attr.type === "JSXAttribute" && attr.name && attr.name.name === "sentiment")) {
|
|
704
|
+
const rawValue = legacyProps.sentiment;
|
|
705
|
+
if (rawValue === "negative") {
|
|
706
|
+
removeAttributeByName(j, openingElement, "sentiment");
|
|
707
|
+
openingElement.attributes?.push(j.jsxAttribute(j.jsxIdentifier("sentiment"), j.literal("negative")));
|
|
708
|
+
} else if (typeof rawValue === "string") reporter.reportUnsupportedValue(path, "sentiment", rawValue);
|
|
709
|
+
else if (rawValue !== void 0) reporter.reportAmbiguousExpression(path, "sentiment");
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
let asIndex = -1;
|
|
713
|
+
let asValue = null;
|
|
714
|
+
let hrefExists = false;
|
|
715
|
+
openingElement.attributes?.forEach((attr, index) => {
|
|
716
|
+
if (attr.type === "JSXAttribute" && attr.name) {
|
|
717
|
+
if (attr.name.name === "as") {
|
|
718
|
+
if (attr.value) {
|
|
719
|
+
if (attr.value.type === "StringLiteral") asValue = attr.value.value;
|
|
720
|
+
else if (attr.value.type === "JSXExpressionContainer") reporter.reportAttribute(attr, path);
|
|
721
|
+
}
|
|
722
|
+
asIndex = index;
|
|
723
|
+
}
|
|
724
|
+
if (attr.name.name === "href") {
|
|
725
|
+
hrefExists = true;
|
|
726
|
+
if (attr.value && attr.value.type !== "StringLiteral") reporter.reportAttribute(attr, path);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
if (asValue === "a") {
|
|
731
|
+
if (asIndex !== -1) openingElement.attributes = openingElement.attributes?.filter((_attr, idx) => idx !== asIndex);
|
|
732
|
+
if (!hrefExists) openingElement.attributes = [...openingElement.attributes ?? [], j.jsxAttribute(j.jsxIdentifier("href"), j.literal("#"))];
|
|
733
|
+
}
|
|
734
|
+
if ((openingElement.attributes ?? []).some((attr) => attr.type === "JSXSpreadAttribute")) reporter.reportSpreadProps(path);
|
|
735
|
+
});
|
|
736
|
+
if (manualReviewIssues.length > 0) manualReviewIssues.forEach(async (issue) => {
|
|
737
|
+
await require_helpers.reportManualReview_default(file.path, issue);
|
|
738
|
+
});
|
|
739
|
+
return root.toSource();
|
|
740
|
+
};
|
|
741
|
+
var transformer_default = transformer;
|
|
742
|
+
|
|
743
|
+
//#endregion
|
|
744
|
+
exports.default = transformer_default;
|
|
745
|
+
exports.parser = parser;
|
|
746
|
+
//# sourceMappingURL=transformer.js.map
|