UNPKG

@xtrek/ts-migrate-plugins

Version:

Set of codemods, which are doing transformation of js/jsx to ts/tsx

167 lines 11.2 kB
"use strict"; 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