@xtrek/ts-migrate-plugins
Version:
Set of codemods, which are doing transformation of js/jsx to ts/tsx
427 lines • 21.1 kB
JavaScript
"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