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