@xtrek/ts-migrate-plugins
Version:
Set of codemods, which are doing transformation of js/jsx to ts/tsx
167 lines • 11.2 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
/* eslint-disable no-restricted-syntax, no-use-before-define, @typescript-eslint/no-use-before-define */
const typescript_1 = __importDefault(require("typescript"));
const path_1 = __importDefault(require("path"));
const react_props_1 = __importDefault(require("./utils/react-props"));
const updateSourceText_1 = __importDefault(require("../utils/updateSourceText"));
const validateOptions_1 = require("../utils/validateOptions");
/**
* first we are checking if we have imports of `prop-types` or `react-validators`
* only if we have them - this file might have shapes
*/
const reactShapePlugin = {
name: 'react-shape',
run({ fileName, sourceFile, options, text }) {
const baseName = path_1.default.basename(fileName);
const importDeclarations = sourceFile.statements.filter(typescript_1.default.isImportDeclaration);
const hasPropTypesImport = importDeclarations.find((x) => /prop-types|react-validators/.test(x.moduleSpecifier.getText()));
if (hasPropTypesImport === undefined)
return undefined;
let shouldAddPropTypesImport = importDeclarations.find((x) => /prop-types/.test(x.moduleSpecifier.getText())) === undefined;
// we are adding a PropTypes.Requireable<FooShape> to shape types, need to be sure that we have a PropTypes import
const insertPropTypesRequireableNode = () => {
if (shouldAddPropTypesImport) {
updates.push({
kind: 'insert',
index: 0,
text: `${printer.printNode(typescript_1.default.EmitHint.Unspecified, getPropTypesImportNode(), sourceFile)}\n`,
});
shouldAddPropTypesImport = false;
}
};
// types are not exported in case if we direct export a variable, like export const Var = ...
// we need to split export to the separate named export and remove modifier from the variable declaration
const splitVariableExport = (node, shapeName) => {
const EXPORT_KEYWOARD = 'export';
const posOfExportKeyword = node.getFullText().indexOf(EXPORT_KEYWOARD);
updates.push({
kind: 'delete',
index: node.pos + posOfExportKeyword,
length: EXPORT_KEYWOARD.length + 1,
});
const newExport = typescript_1.default.factory.createExportDeclaration(undefined, undefined, false, typescript_1.default.factory.createNamedExports([
typescript_1.default.factory.createExportSpecifier(undefined, typescript_1.default.factory.createIdentifier(shapeName)),
]));
updates.push({
kind: 'insert',
index: node.end,
text: `\n${printer.printNode(typescript_1.default.EmitHint.Unspecified, newExport, sourceFile)}`,
});
};
const updates = [];
const printer = typescript_1.default.createPrinter();
// in current codebase we have some amout of cases, when shapes have an interface/type
// with the same name and the same export for both of them
const typesAndInterfaces = sourceFile.statements.filter((node) => typescript_1.default.isInterfaceDeclaration(node) || typescript_1.default.isTypeAliasDeclaration(node));
for (const node of sourceFile.statements) {
// const shapeName = PropTypes.shape({...})
if (typescript_1.default.isVariableStatement(node)) {
const variableDeclaration = node.declarationList.declarations[0];
if (variableDeclaration && variableDeclaration.initializer && !variableDeclaration.type) {
const exportModifier = node.modifiers &&
node.modifiers.find((modifier) => modifier.kind === typescript_1.default.SyntaxKind.ExportKeyword);
if (typescript_1.default.isCallExpression(variableDeclaration.initializer) &&
variableDeclaration.initializer.arguments.length > 0 &&
typescript_1.default.isObjectLiteralExpression(variableDeclaration.initializer.arguments[0]) &&
isPropTypesShapeCallExpression(variableDeclaration.initializer)) {
insertPropTypesRequireableNode();
const shapeNode = variableDeclaration.initializer;
const shapeName = variableDeclaration.name.getText();
// we are checking here, if there is existing interface/type with the same name in the file
if (!typesAndInterfaces.find((tNode) => tNode.name.text === variableDeclaration.name.getText())) {
updates.push({
kind: 'insert',
index: node.pos,
text: `\n\n${printer.printNode(typescript_1.default.EmitHint.Unspecified, getTypeForTheShape(shapeNode, shapeName, sourceFile, options), sourceFile)}`,
});
}
const updatedVariableDeclaration = typescript_1.default.factory.updateVariableDeclaration(variableDeclaration, variableDeclaration.name, undefined, getShapeTypeNode(shapeName), variableDeclaration.initializer);
const index = variableDeclaration.pos + 1;
const length = variableDeclaration.end - index;
const text = printer.printNode(typescript_1.default.EmitHint.Unspecified, updatedVariableDeclaration, sourceFile);
updates.push({ kind: 'replace', index, length, text });
if (exportModifier) {
splitVariableExport(node, shapeName);
}
}
// const shapeName = Types.arrayOf(Shape(...))
if (typescript_1.default.isCallExpression(variableDeclaration.initializer) &&
isPropTypesArrayOfShapes(variableDeclaration.initializer)) {
insertPropTypesRequireableNode();
const shapeNode = variableDeclaration.initializer.arguments[0];
const shapeName = variableDeclaration.name.getText();
updates.push({
kind: 'insert',
index: node.pos,
text: `\n\n${printer.printNode(typescript_1.default.EmitHint.Unspecified, getTypeForTheShape(shapeNode, shapeName, sourceFile, options, true), sourceFile)}`,
});
if (exportModifier) {
splitVariableExport(node, shapeName);
}
}
}
}
// export default PropTypes.shape({...})
// @TODO: export default PropTypes.arrayOf
if (typescript_1.default.isExportAssignment(node) &&
typescript_1.default.isCallExpression(node.expression) &&
typescript_1.default.isObjectLiteralExpression(node.expression.arguments[0]) &&
isPropTypesShapeCallExpression(node.expression)) {
insertPropTypesRequireableNode();
const shapeNode = node.expression;
const shapeName = baseName.split('.')[0];
updates.push({
kind: 'insert',
index: importDeclarations[importDeclarations.length - 1].end,
text: `\n\n${printer.printNode(typescript_1.default.EmitHint.Unspecified, getTypeForTheShape(shapeNode, shapeName, sourceFile, options), sourceFile)}`,
});
updates.push({
kind: 'replace',
index: node.pos,
length: node.end,
text: `${typescript_1.default.sys.newLine}${printer.printNode(typescript_1.default.EmitHint.Unspecified, typescript_1.default.factory.createVariableStatement([], typescript_1.default.factory.createVariableDeclarationList([
typescript_1.default.factory.createVariableDeclaration(shapeName, undefined, getShapeTypeNode(shapeName), shapeNode),
], typescript_1.default.NodeFlags.Const)), sourceFile)}`,
});
const exportShapeExpression = `${typescript_1.default.sys.newLine}${printer.printNode(typescript_1.default.EmitHint.Unspecified, typescript_1.default.factory.createExportAssignment(undefined, undefined, undefined, typescript_1.default.factory.createIdentifier(shapeName)), sourceFile)}`;
updates.push({
kind: 'insert',
index: node.end,
text: exportShapeExpression,
});
}
}
return updateSourceText_1.default(text, updates);
},
validate: validateOptions_1.createValidate(Object.assign(Object.assign({}, validateOptions_1.anyAliasProperty), validateOptions_1.anyFunctionAliasProperty)),
};
function getTypeForTheShape(shapeNode, shapeName, sourceFile, options, isArrayShapeType = false) {
const shapeTypeVariable = react_props_1.default(shapeNode.arguments[0], sourceFile, {
anyAlias: options.anyAlias,
anyFunctionAlias: options.anyFunctionAlias,
spreadReplacements: [],
});
const propsTypeAlias = typescript_1.default.factory.createTypeAliasDeclaration(undefined, undefined, shapeName, undefined, isArrayShapeType ? typescript_1.default.factory.createArrayTypeNode(shapeTypeVariable) : shapeTypeVariable);
return typescript_1.default.moveSyntheticComments(propsTypeAlias, shapeTypeVariable);
}
function isPropTypesShapeCallExpression(node) {
return /PropTypes.shape|Shape|Types.shape/.test(node.expression.getText());
}
function isPropTypesArrayOfShapes(node) {
return (/arrayOf/.test(node.expression.getText()) &&
typescript_1.default.isCallExpression(node.arguments[0]) &&
isPropTypesShapeCallExpression(node.arguments[0]));
}
function getPropTypesImportNode() {
return typescript_1.default.factory.createImportDeclaration(undefined, undefined, typescript_1.default.factory.createImportClause(false, typescript_1.default.factory.createIdentifier('PropTypes'), undefined), typescript_1.default.factory.createStringLiteral('prop-types'));
}
// @TODO: PropTypes.Requireable<ShapeType> doesn't works with react-validators Shapes
function getShapeTypeNode(shapeName) {
return typescript_1.default.factory.createTypeReferenceNode(typescript_1.default.factory.createQualifiedName(typescript_1.default.factory.createIdentifier('PropTypes'), typescript_1.default.factory.createIdentifier('Requireable')), [typescript_1.default.factory.createTypeReferenceNode(typescript_1.default.factory.createIdentifier(shapeName), undefined)]);
}
exports.default = reactShapePlugin;
//# sourceMappingURL=react-shape.js.map