UNPKG

@xtrek/ts-migrate-plugins

Version:

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

120 lines 7.29 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-use-before-define, @typescript-eslint/no-use-before-define */ const typescript_1 = __importDefault(require("typescript")); const updateSourceText_1 = __importDefault(require("../utils/updateSourceText")); const identifiers_1 = require("./utils/identifiers"); const validateOptions_1 = require("../utils/validateOptions"); const hoistClassStaticsPlugin = { name: 'hoist-class-statics', run({ sourceFile, text, options }) { return hoistStaticClassProperties(sourceFile, text, options); }, validate: validateOptions_1.validateAnyAliasOptions, }; exports.default = hoistClassStaticsPlugin; /** * Determines whether or not we can hoist this identifier * @param identifier * @param hoistToPos -- the position we would hoist this identifier to * @param knownDefinitions -- a map describing any known imports or variable declarations */ function canHoistIdentifier(identifier, hoistToPos, knownDefinitions) { const globalWhitelist = ['Number', 'String', 'Object', 'Date', 'window', 'global']; const id = identifier.text; const isDefined = knownDefinitions[id] && knownDefinitions[id].end <= hoistToPos; const isGlobal = globalWhitelist.includes(id); return (isDefined || isGlobal || // e.g. in 'PropTypes.string.isRequired' allow the accessing identifiers 'string' and 'isRequired' (typescript_1.default.isPropertyAccessExpression(identifier.parent) && identifier.parent.name === identifier) || // e.g. in { foo: 'bar' } allow the assigned identifier key 'foo' (typescript_1.default.isPropertyAssignment(identifier.parent) && identifier.parent.name === identifier) || // e.g. in { foo() {} } allow foo (typescript_1.default.isMethodDeclaration(identifier.parent) && identifier.parent.name === identifier)); } /** * Determines whether or not we can hoist this expression * @param expression * @param hoistToPos -- the position we would hoist this expression to * @param knownDefinitions -- a map describing any known imports or variable declarations */ function canHoistExpression(expression, hoistToPos, knownDefinitions) { const allIdentifiers = identifiers_1.collectIdentifierNodes(expression); return allIdentifiers.every((identifier) => canHoistIdentifier(identifier, hoistToPos, knownDefinitions)); } /** * Determines whether or not this assignment was already hoisted to this class * @param statment -- a static binary expresison statement * @param classDeclaration -- the class declaration to hoist to */ function isAlreadyHoisted(statement, classDeclaration) { if (!typescript_1.default.isBinaryExpression(statement.expression) || !typescript_1.default.isPropertyAccessExpression(statement.expression.left)) { return false; } const propertyToHoist = statement.expression.left.name.text; return classDeclaration.members.some((member) => member.name && typescript_1.default.isIdentifier(member.name) && member.name.text === propertyToHoist); } function hoistStaticClassProperties(sourceFile, sourceText, options) { const printer = typescript_1.default.createPrinter(); const updates = []; const classDeclarations = sourceFile.statements.filter(typescript_1.default.isClassDeclaration); const knownDefinitions = Object.assign(Object.assign({}, identifiers_1.findKnownImports(sourceFile)), identifiers_1.findKnownVariables(sourceFile)); classDeclarations.forEach((classDeclaration) => { const className = classDeclaration.name; if (!className) return; const properties = []; sourceFile.statements.forEach((statement) => { if (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 === className.text && statement.expression.operatorToken.kind === typescript_1.default.SyntaxKind.EqualsToken) { if (isAlreadyHoisted(statement, classDeclaration)) { return; } if (canHoistExpression(statement.expression.right, classDeclaration.pos, knownDefinitions)) { properties.push(typescript_1.default.factory.createPropertyDeclaration(undefined, [typescript_1.default.factory.createModifier(typescript_1.default.SyntaxKind.StaticKeyword)], statement.expression.left.name.text, undefined, undefined, statement.expression.right)); updates.push({ kind: 'delete', index: statement.pos, length: statement.end - statement.pos, }); } else { // otherwise add a static type annotation for this expression properties.push(typescript_1.default.factory.createPropertyDeclaration(undefined, [typescript_1.default.factory.createModifier(typescript_1.default.SyntaxKind.StaticKeyword)], statement.expression.left.name.text, undefined, options.anyAlias != null ? typescript_1.default.factory.createTypeReferenceNode(options.anyAlias, undefined) : typescript_1.default.factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.AnyKeyword), undefined)); } } }); if (properties.length > 0) { if (classDeclaration.members.length === 0) { const updatedClassDeclaration = typescript_1.default.factory.updateClassDeclaration(classDeclaration, classDeclaration.decorators, classDeclaration.modifiers, classDeclaration.name, classDeclaration.typeParameters, classDeclaration.heritageClauses, typescript_1.default.factory.createNodeArray(properties)); let index = classDeclaration.pos; while (index < sourceText.length && /\s/.test(sourceText[index])) index += 1; const length = classDeclaration.end - index; const text = printer.printNode(typescript_1.default.EmitHint.Unspecified, updatedClassDeclaration, sourceFile); updates.push({ kind: 'replace', index, length, text }); } else { const text = typescript_1.default.sys.newLine + properties .map((property) => printer.printNode(typescript_1.default.EmitHint.Unspecified, property, sourceFile)) .join(typescript_1.default.sys.newLine + typescript_1.default.sys.newLine) + typescript_1.default.sys.newLine; updates.push({ kind: 'insert', index: classDeclaration.members[0].pos, text }); } } }); return updateSourceText_1.default(sourceText, updates); } //# sourceMappingURL=hoist-class-statics.js.map