UNPKG

@xtrek/ts-migrate-plugins

Version:

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

427 lines 21.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; 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 react_1 = require("./utils/react"); const isNotNull_1 = __importDefault(require("../utils/isNotNull")); const updateSourceText_1 = __importDefault(require("../utils/updateSourceText")); const react_props_1 = __importStar(require("./utils/react-props")); const text_1 = require("./utils/text"); const imports_1 = require("./utils/imports"); const validateOptions_1 = require("../utils/validateOptions"); const optionProperties = Object.assign(Object.assign(Object.assign({}, validateOptions_1.anyAliasProperty), validateOptions_1.anyFunctionAliasProperty), { shouldUpdateAirbnbImports: { type: 'boolean' }, shouldKeepPropTypes: { type: 'boolean' } }); const reactPropsPlugin = { name: 'react-props', run({ fileName, sourceFile, options }) { var _a; if (!fileName.endsWith('.tsx')) return undefined; const updates = []; const getPropsTypeName = react_props_1.createPropsTypeNameGetter(sourceFile); const propTypeIdentifiers = {}; for (const node of sourceFile.statements) { // Scan for prop type imports and build a map // Assumes import statements are higher up in the file than react components if (typescript_1.default.isImportDeclaration(node) && /prop-types/.test(node.moduleSpecifier.getText())) { const importBindings = (_a = node.importClause) === null || _a === void 0 ? void 0 : _a.namedBindings; if (importBindings && typescript_1.default.isNamedImports(importBindings)) { importBindings.elements.forEach((specifier) => { if (!specifier.propertyName) { propTypeIdentifiers[specifier.name.getText()] = specifier.name.getText(); } else { propTypeIdentifiers[specifier.name.getText()] = specifier.propertyName.getText(); } }); } } if (isReactNode(node)) { const componentName = getComponentName(node); const propsTypeName = getPropsTypeName(componentName); updates.push(...updatePropTypes(node, propsTypeName, sourceFile, propTypeIdentifiers, options)); } } const updatedSourceText = updateSourceText_1.default(sourceFile.text, updates); const updatedSourceFile = typescript_1.default.createSourceFile(fileName, updatedSourceText, sourceFile.languageVersion); const importUpdates = !options.shouldKeepPropTypes ? imports_1.updateImports(updatedSourceFile, spreadReplacements.map((cur) => cur.typeImport), [ { moduleSpecifier: 'prop-types' }, ...(options.shouldUpdateAirbnbImports ? importReplacements : []), ...(options.shouldUpdateAirbnbImports ? spreadReplacements.map((cur) => cur.spreadImport) : []), ]) : []; return updateSourceText_1.default(updatedSourceText, importUpdates); }, validate: validateOptions_1.createValidate(optionProperties), }; exports.default = reactPropsPlugin; // airbnb related imports const importReplacements = [{ moduleSpecifier: 'airbnb-prop-types' }]; const spreadReplacements = [ { spreadId: 'withStylesPropTypes', spreadImport: { namedImport: 'withStylesPropTypes', moduleSpecifier: ':dls-themes/withStyles', }, typeRef: typescript_1.default.factory.createTypeReferenceNode('WithStylesProps', undefined), typeImport: { namedImport: 'WithStylesProps', moduleSpecifier: ':dls-themes/withStyles', }, }, { spreadId: 'withBreakpointPropTypes', spreadImport: { namedImport: 'withBreakpointPropTypes', moduleSpecifier: ':dls-core/components/breakpoints/withBreakpoint', }, typeRef: typescript_1.default.factory.createTypeReferenceNode('WithBreakpointProps', undefined), typeImport: { namedImport: 'WithBreakpointProps', moduleSpecifier: ':dls-core/components/breakpoints/withBreakpoint', }, }, { spreadId: 'withRouterPropTypes', spreadImport: { defaultImport: 'withRouterPropTypes', moduleSpecifier: ':routing/shapes/RR4PropTypes', }, typeRef: typescript_1.default.factory.createTypeReferenceNode('RouteConfigComponentProps', [ typescript_1.default.factory.createTypeLiteralNode([]), ]), typeImport: { namedImport: 'RouteConfigComponentProps', moduleSpecifier: 'react-router-config', }, }, ]; function isReactNode(node) { return ((typescript_1.default.isClassDeclaration(node) && react_1.isReactClassComponent(node)) || (typescript_1.default.isFunctionDeclaration(node) && react_1.isReactSfcFunctionDeclaration(node)) || (typescript_1.default.isVariableStatement(node) && react_1.isReactSfcArrowFunction(node))); } function isReactSfcNode(node) { return typescript_1.default.isFunctionDeclaration(node) || typescript_1.default.isVariableStatement(node); } function updatePropTypes(node, propsTypeName, sourceFile, propTypeIdentifiers, options) { var _a; const updates = []; const printer = typescript_1.default.createPrinter(); if (isReactSfcNode(node)) { const propsParam = getPropsParam(node); const forwardRefComponent = getReactForwardRefFuncExpression(node); if (propsParam && !propsParam.type) { const propTypesNode = findSfcPropTypesNode(node, sourceFile); const objectLiteral = propTypesNode && findPropTypesObjectLiteral(propTypesNode, sourceFile); if (objectLiteral) { updates.push(...updateObjectLiteral(node, objectLiteral, propsTypeName, sourceFile, propTypeIdentifiers, options, false)); if (forwardRefComponent) { updates.push({ kind: 'replace', index: forwardRefComponent.expression.pos, length: forwardRefComponent.expression.end - forwardRefComponent.expression.pos, text: ` ${printer.printNode(typescript_1.default.EmitHint.Unspecified, typescript_1.default.factory.updateExpressionWithTypeArguments(forwardRefComponent, forwardRefComponent.expression, [ typescript_1.default.factory.createTypeReferenceNode(options.anyAlias || 'any', undefined), typescript_1.default.factory.createTypeReferenceNode(propsTypeName, undefined), ].filter(isNotNull_1.default)), sourceFile)}`, }); } else { let updateText = printer.printNode(typescript_1.default.EmitHint.Unspecified, typescript_1.default.factory.updateParameterDeclaration(propsParam, propsParam.decorators, propsParam.modifiers, propsParam.dotDotDotToken, propsParam.name, propsParam.questionToken, typescript_1.default.factory.createTypeReferenceNode(propsTypeName, undefined), propsParam.initializer), sourceFile); const signature = propsParam.parent; if (typescript_1.default.isArrowFunction(signature) && signature.parameters.length === 1 && ((_a = signature.getFirstToken()) === null || _a === void 0 ? void 0 : _a.kind) !== typescript_1.default.SyntaxKind.OpenParenToken) { // Special case: when an arrow function has a single parameter and no parentheses, // add parentheses. updateText = `(${updateText})`; } updates.push({ kind: 'replace', index: propsParam.pos, length: propsParam.end - propsParam.pos, text: updateText, }); } if (!options.shouldKeepPropTypes) { updates.push(...deleteSfcPropTypes(node, sourceFile)); } } } } else { const heritageType = react_1.getReactComponentHeritageType(node); const heritageTypeArgs = heritageType.typeArguments || []; const propsType = heritageTypeArgs[0]; const stateType = heritageTypeArgs[1]; if (!propsType || isEmptyTypeLiteral(propsType)) { const propTypesNode = findClassPropTypesNode(node, sourceFile); const objectLiteral = propTypesNode && findPropTypesObjectLiteral(propTypesNode, sourceFile); if (objectLiteral) { updates.push(...updateObjectLiteral(node, objectLiteral, propsTypeName, sourceFile, {}, options, true)); updates.push({ kind: 'replace', index: heritageType.pos, length: heritageType.end - heritageType.pos, text: ` ${printer.printNode(typescript_1.default.EmitHint.Unspecified, typescript_1.default.factory.updateExpressionWithTypeArguments(heritageType, heritageType.expression, [typescript_1.default.factory.createTypeReferenceNode(propsTypeName, undefined), stateType].filter(isNotNull_1.default)), sourceFile)}`, }); if (!options.shouldKeepPropTypes) { updates.push(...deleteClassPropTypes(node, sourceFile)); } } } } return updates; } function isEmptyTypeLiteral(node) { return typescript_1.default.isTypeLiteralNode(node) && node.members.length === 0; } function updateObjectLiteral(node, objectLiteral, propsTypeName, sourceFile, propTypeIdentifiers, options, implicitChildren) { const updates = []; const printer = typescript_1.default.createPrinter(); const propsTypeNode = react_props_1.default(objectLiteral, sourceFile, { anyAlias: options.anyAlias, anyFunctionAlias: options.anyFunctionAlias, implicitChildren, spreadReplacements, propTypeIdentifiers, }); let propsTypeAlias = typescript_1.default.factory.createTypeAliasDeclaration(undefined, undefined, propsTypeName, undefined, propsTypeNode); propsTypeAlias = typescript_1.default.moveSyntheticComments(propsTypeAlias, propsTypeNode); const varStatement = getParentVariableStatement(objectLiteral, sourceFile); if (varStatement && !options.shouldKeepPropTypes) { updates.push({ kind: 'replace', index: varStatement.pos, length: varStatement.end - varStatement.pos, text: text_1.getTextPreservingWhitespace(varStatement, propsTypeAlias, sourceFile), }); } else { updates.push({ kind: 'insert', index: node.pos, text: `\n\n${printer.printNode(typescript_1.default.EmitHint.Unspecified, propsTypeAlias, sourceFile)}`, }); } return updates; } function getComponentName(node) { if (typescript_1.default.isClassDeclaration(node) || typescript_1.default.isFunctionDeclaration(node)) { return node.name && node.name.text; } if (typescript_1.default.isVariableStatement(node)) { const declaration = node.declarationList.declarations[0]; return declaration && declaration.name && typescript_1.default.isIdentifier(declaration.name) ? declaration.name.text : undefined; } return undefined; } function getPropsParam(node) { if (typescript_1.default.isFunctionDeclaration(node)) { return node.parameters[0]; } if (typescript_1.default.isVariableStatement(node)) { const forwardRefComponent = getReactForwardRefFuncExpression(node); const forwardRefArgument = forwardRefComponent && forwardRefComponent.arguments && forwardRefComponent.arguments[0]; const forwardRefProps = forwardRefArgument && typescript_1.default.isFunctionLike(forwardRefArgument) && forwardRefArgument.parameters[0]; const declaration = node.declarationList.declarations[0]; const init = declaration && declaration.initializer; const arrowFunction = init && typescript_1.default.isArrowFunction(init) ? init : undefined; const arrowFunctionProps = arrowFunction && arrowFunction.parameters[0]; return forwardRefProps || arrowFunctionProps; } return undefined; } function getReactForwardRefFuncExpression(node) { var _a, _b, _c, _d, _e, _f, _g, _h; return (!!typescript_1.default.isVariableStatement(node) && !!((_b = (_a = node.declarationList) === null || _a === void 0 ? void 0 : _a.declarations[0]) === null || _b === void 0 ? void 0 : _b.initializer) && typescript_1.default.isCallExpression((_d = (_c = node.declarationList) === null || _c === void 0 ? void 0 : _c.declarations[0]) === null || _d === void 0 ? void 0 : _d.initializer) && react_1.isReactForwardRefName((_f = (_e = node.declarationList) === null || _e === void 0 ? void 0 : _e.declarations[0]) === null || _f === void 0 ? void 0 : _f.initializer) && ((_h = (_g = node.declarationList) === null || _g === void 0 ? void 0 : _g.declarations[0]) === null || _h === void 0 ? void 0 : _h.initializer)); } function getParentVariableStatement(objectLiteral, sourceFile) { let cur = objectLiteral; while (cur !== sourceFile) { if (typescript_1.default.isVariableStatement(cur)) { return cur; } cur = cur.parent; } return undefined; } function deleteClassPropTypes(classDeclaration, sourceFile) { const updates = []; for (const member of classDeclaration.members) { if (isPropTypesStatic(member)) { updates.push({ kind: 'delete', index: member.pos, length: member.end - member.pos, }); if (member.initializer && typescript_1.default.isIdentifier(member.initializer)) { updates.push(...deleteIdRef(member.initializer, sourceFile)); } } } const className = classDeclaration.name && classDeclaration.name.text; if (className) { updates.push(...deletePropTypesStatements(className, sourceFile)); } return updates; } function deleteSfcPropTypes(node, sourceFile) { const componentName = getComponentName(node); return componentName ? deletePropTypesStatements(componentName, sourceFile) : []; } function deletePropTypesStatements(componentName, sourceFile) { const updates = []; for (const statement of sourceFile.statements) { if (isPropTypesStatement(statement, componentName)) { updates.push({ kind: 'delete', index: statement.pos, length: statement.end - statement.pos, }); if (typescript_1.default.isBinaryExpression(statement.expression) && typescript_1.default.isIdentifier(statement.expression.right)) { updates.push(...deleteIdRef(statement.expression.right, sourceFile)); } } } return updates; } function deleteIdRef(idenifier, sourceFile) { const updates = []; for (const statement of sourceFile.statements) { if (typescript_1.default.isVariableDeclarationList(statement) && statement.declarations.length === 1) { const declaration = statement.declarations[0]; if (typescript_1.default.isVariableDeclaration(declaration) && typescript_1.default.isIdentifier(declaration.name) && declaration.name.text === idenifier.text) { if (declaration.initializer && typescript_1.default.isIdentifier(declaration.initializer)) { updates.push({ kind: 'delete', index: declaration.pos, length: declaration.end - declaration.pos, }, ...deleteIdRef(declaration.initializer, sourceFile)); } } } } return updates; } function isPropTypesStatic(member) { return (typescript_1.default.isPropertyDeclaration(member) && member.modifiers != null && member.modifiers.some((modifier) => modifier.kind === typescript_1.default.SyntaxKind.StaticKeyword) && typescript_1.default.isIdentifier(member.name) && member.name.text === 'propTypes' && member.initializer != null); } function isPropTypesStatement(statement, componentName) { return (typescript_1.default.isExpressionStatement(statement) && typescript_1.default.isBinaryExpression(statement.expression) && typescript_1.default.isPropertyAccessExpression(statement.expression.left) && typescript_1.default.isIdentifier(statement.expression.left.expression) && statement.expression.left.expression.text === componentName && typescript_1.default.isIdentifier(statement.expression.left.name) && statement.expression.left.name.text === 'propTypes'); } function findClassPropTypesNode(classDeclaration, sourceFile) { for (const member of classDeclaration.members) { if (isPropTypesStatic(member)) { return member; } } const componentName = classDeclaration.name && classDeclaration.name.text; for (const statement of sourceFile.statements) { if (componentName && isPropTypesStatement(statement, componentName)) { return statement; } } return undefined; } function findSfcPropTypesNode(node, sourceFile) { const componentName = getComponentName(node); for (const statement of sourceFile.statements) { if (componentName && isPropTypesStatement(statement, componentName)) { return statement; } } return undefined; } function findPropTypesObjectLiteral(node, sourceFile) { if (!node) return undefined; let expression; if (typescript_1.default.isPropertyDeclaration(node) && node.initializer != null) { expression = node.initializer; } else if (typescript_1.default.isExpressionStatement(node) && typescript_1.default.isBinaryExpression(node.expression)) { expression = node.expression.right; } return unpackInitializer(expression, sourceFile); } function unpackInitializer(initializer, sourceFile) { if (!initializer) { return undefined; } if (typescript_1.default.isObjectLiteralExpression(initializer)) { return initializer; } if (typescript_1.default.isCallExpression(initializer) && typescript_1.default.isIdentifier(initializer.expression) && initializer.expression.text === 'forbidExtraProps' && initializer.arguments.length === 1) { const arg = initializer.arguments[0]; if (typescript_1.default.isObjectLiteralExpression(arg)) { return arg; } } if (typescript_1.default.isIdentifier(initializer)) { for (const statement of sourceFile.statements) { if (typescript_1.default.isVariableStatement(statement) && statement.declarationList.declarations.length === 1) { const declaration = statement.declarationList.declarations[0]; if (typescript_1.default.isVariableDeclaration(declaration) && typescript_1.default.isIdentifier(declaration.name) && declaration.name.text === initializer.text) { return unpackInitializer(declaration.initializer, sourceFile); } } } } return undefined; } //# sourceMappingURL=react-props.js.map