@xtrek/ts-migrate-plugins
Version:
Set of codemods, which are doing transformation of js/jsx to ts/tsx
320 lines • 14.7 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-bitwise */
const typescript_1 = __importDefault(require("typescript"));
const validateOptions_1 = require("../utils/validateOptions");
const update_1 = __importDefault(require("./utils/update"));
const defaultTypeMap = {
String: {
tsName: 'string',
acceptsTypeParameters: false,
},
Boolean: {
tsName: 'boolean',
acceptsTypeParameters: false,
},
Number: {
tsName: 'number',
acceptsTypeParameters: false,
},
Object: {
tsName: 'object',
// Object<string, T> and Object<number, T> are handled as a special case.
acceptsTypeParameters: false,
},
date: {
tsName: 'Date',
acceptsTypeParameters: false,
},
array: 'Array',
promise: 'Promise',
};
const optionProperties = Object.assign(Object.assign({}, validateOptions_1.anyAliasProperty), { annotateReturns: { type: 'boolean' }, typeMap: {
oneOf: [
{ type: 'string' },
{
type: 'object',
properties: { tsName: { type: 'string' }, acceptsTypeParameters: { type: 'boolean' } },
additionalProperties: false,
},
],
} });
const jsDocPlugin = {
name: 'jsdoc',
run({ sourceFile, options }) {
const updates = new update_1.default(sourceFile);
typescript_1.default.transform(sourceFile, [jsDocTransformerFactory(updates, options)]);
return updates.apply();
},
validate: validateOptions_1.createValidate(optionProperties),
};
exports.default = jsDocPlugin;
const jsDocTransformerFactory = (updates, { annotateReturns, anyAlias, typeMap: optionsTypeMap }) => (context) => {
const { factory } = context;
const anyType = anyAlias
? factory.createTypeReferenceNode(anyAlias, undefined)
: factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.AnyKeyword);
const typeMap = Object.assign(Object.assign({}, defaultTypeMap), optionsTypeMap);
return (file) => {
visit(file);
return file;
};
function visit(origNode) {
origNode.forEachChild(visit);
if (typescript_1.default.isFunctionLike(origNode)) {
visitFunctionLike(origNode, typescript_1.default.isClassDeclaration(origNode.parent));
}
}
function visitFunctionLike(node, insideClass) {
var _a;
const modifiers = typescript_1.default.isMethodDeclaration(node) && insideClass
? modifiersFromJSDoc(node, factory)
: node.modifiers;
const parameters = visitParameters(node);
const returnType = annotateReturns ? visitReturnType(node) : node.type;
if (modifiers === node.modifiers &&
parameters === node.parameters &&
returnType === node.type) {
return;
}
const newModifiers = modifiers ? factory.createNodeArray(modifiers) : undefined;
if (newModifiers) {
if (node.modifiers) {
updates.replaceNodes(node.modifiers, newModifiers);
}
else {
const pos = node.name.getStart();
updates.insertNodes(pos, newModifiers);
}
}
const newParameters = factory.createNodeArray(parameters);
const addParens = typescript_1.default.isArrowFunction(node) && ((_a = node.getFirstToken()) === null || _a === void 0 ? void 0 : _a.kind) !== typescript_1.default.SyntaxKind.OpenParenToken;
updates.replaceNodes(node.parameters, newParameters, addParens);
const newType = returnType;
if (newType) {
updates.addReturnAnnotation(node, newType);
}
}
function visitParameters(functionDeclaration) {
if (!typescript_1.default.hasJSDocParameterTags(functionDeclaration)) {
return functionDeclaration.parameters;
}
// create a new function declaration with a new type
const newParams = functionDeclaration.parameters.map((param) => {
if (param.type) {
// Don't overwrite existing annotations.
return param;
}
const paramNode = typescript_1.default.getJSDocParameterTags(param).find((tag) => tag.typeExpression);
if (!paramNode || !paramNode.typeExpression) {
return param;
}
const typeNode = paramNode.typeExpression.type;
const type = visitJSDocType(typeNode, true);
const questionToken = !param.initializer &&
typescript_1.default.isIdentifier(param.name) &&
(paramNode.isBracketed || typescript_1.default.isJSDocOptionalType(typeNode))
? factory.createToken(typescript_1.default.SyntaxKind.QuestionToken)
: param.questionToken;
const newParam = factory.createParameterDeclaration(param.decorators, param.modifiers, param.dotDotDotToken, param.name, questionToken, type, param.initializer);
return newParam;
});
if (functionDeclaration.parameters.some((param, i) => param !== newParams[i])) {
// Only return the new array if something changed.
return newParams;
}
return functionDeclaration.parameters;
}
function visitReturnType(functionDeclaration) {
if (functionDeclaration.type) {
// Don't overwrite existing annotations.
return functionDeclaration.type;
}
const returnTypeNode = typescript_1.default.getJSDocReturnType(functionDeclaration);
if (!returnTypeNode) {
return functionDeclaration.type;
}
return visitJSDocType(returnTypeNode);
}
// All visitJSDoc functions are adapted from:
// https://github.com/microsoft/TypeScript/blob/v4.0.2/src/services/codefixes/annotateWithTypeFromJSDoc.ts
function visitJSDocType(node, topLevelParam = false) {
switch (node.kind) {
case typescript_1.default.SyntaxKind.JSDocAllType:
case typescript_1.default.SyntaxKind.JSDocUnknownType:
return anyType;
case typescript_1.default.SyntaxKind.JSDocOptionalType:
if (topLevelParam) {
// Ignore the optionality.
// We'll make the entire parameter optional inside visitParameters
return visitJSDocType(node.type);
}
return visitJSDocOptionalType(node);
case typescript_1.default.SyntaxKind.JSDocNonNullableType:
return visitJSDocType(node.type);
case typescript_1.default.SyntaxKind.JSDocNullableType:
return visitJSDocNullableType(node);
case typescript_1.default.SyntaxKind.JSDocVariadicType:
return visitJSDocVariadicType(node);
case typescript_1.default.SyntaxKind.JSDocFunctionType:
return visitJSDocFunctionType(node);
case typescript_1.default.SyntaxKind.JSDocTypeLiteral:
return visitJSDocTypeLiteral(node);
case typescript_1.default.SyntaxKind.TypeReference:
return visitJSDocTypeReference(node);
default: {
const visited = typescript_1.default.visitEachChild(node, visitJSDocType, context);
typescript_1.default.setEmitFlags(visited, typescript_1.default.EmitFlags.SingleLine);
return visited;
}
}
}
function visitJSDocOptionalType(node) {
return factory.createUnionTypeNode([
typescript_1.default.visitNode(node.type, visitJSDocType),
factory.createKeywordTypeNode(typescript_1.default.SyntaxKind.UndefinedKeyword),
]);
}
function visitJSDocNullableType(node) {
return factory.createUnionTypeNode([
typescript_1.default.visitNode(node.type, visitJSDocType),
factory.createLiteralTypeNode(factory.createToken(typescript_1.default.SyntaxKind.NullKeyword)),
]);
}
function visitJSDocVariadicType(node) {
return factory.createArrayTypeNode(typescript_1.default.visitNode(node.type, visitJSDocType));
}
function visitJSDocFunctionType(node) {
var _a;
return factory.createFunctionTypeNode(undefined, node.parameters.map(visitJSDocParameter), (_a = node.type) !== null && _a !== void 0 ? _a : anyType);
}
function visitJSDocTypeLiteral(node) {
const propertySignatures = [];
if (node.jsDocPropertyTags) {
node.jsDocPropertyTags.forEach((tag) => {
const property = visitJSDocPropertyLikeTag(tag);
if (property) {
propertySignatures.push(property);
}
});
}
return factory.createTypeLiteralNode(propertySignatures);
}
function visitJSDocPropertyLikeTag(node) {
let optionalType = false;
let type;
if (node.typeExpression) {
type = visitJSDocType(node.typeExpression.type);
optionalType = typescript_1.default.isJSDocOptionalType(node.typeExpression);
}
else {
type = anyType;
}
const questionToken = node.isBracketed || optionalType
? factory.createToken(typescript_1.default.SyntaxKind.QuestionToken)
: undefined;
if (typescript_1.default.isIdentifier(node.name)) {
return factory.createPropertySignature(undefined, node.name, questionToken, type);
}
// Assumption: the leaf field on the QualifiedName belongs directly to the parent object type.
return factory.createPropertySignature(undefined, node.name.right, questionToken, type);
}
function visitJSDocParameter(node) {
if (!node.type) {
return node;
}
const index = node.parent.parameters.indexOf(node);
const isRest = node.type.kind === typescript_1.default.SyntaxKind.JSDocVariadicType &&
index === node.parent.parameters.length - 1;
const name = node.name || (isRest ? 'rest' : `arg${index}`);
const dotdotdot = isRest
? factory.createToken(typescript_1.default.SyntaxKind.DotDotDotToken)
: node.dotDotDotToken;
return factory.createParameterDeclaration(node.decorators, node.modifiers, dotdotdot, name, node.questionToken, typescript_1.default.visitNode(node.type, visitJSDocType), node.initializer);
}
function visitJSDocTypeReference(node) {
let name = node.typeName;
let args = node.typeArguments;
if (typescript_1.default.isIdentifier(node.typeName)) {
if (isJSDocIndexSignature(node)) {
return visitJSDocIndexSignature(node);
}
let { text } = node.typeName;
let acceptsTypeParameters = true;
if (text in typeMap) {
const typeOptions = typeMap[text];
if (typeof typeOptions === 'string') {
text = typeOptions;
}
else {
if (typeOptions.tsName) {
text = typeOptions.tsName;
}
acceptsTypeParameters = typeOptions.acceptsTypeParameters !== false;
}
}
name = factory.createIdentifier(text);
if ((text === 'Array' || text === 'Promise') && !node.typeArguments) {
args = factory.createNodeArray([anyType]);
}
else if (acceptsTypeParameters) {
args = typescript_1.default.visitNodes(node.typeArguments, visitJSDocType);
}
if (!acceptsTypeParameters) {
args = undefined;
}
}
return factory.createTypeReferenceNode(name, args);
}
function visitJSDocIndexSignature(node) {
const typeArguments = node.typeArguments;
const index = factory.createParameterDeclaration(
/* decorators */ undefined,
/* modifiers */ undefined,
/* dotDotDotToken */ undefined, typeArguments[0].kind === typescript_1.default.SyntaxKind.NumberKeyword ? 'n' : 's',
/* questionToken */ undefined, factory.createTypeReferenceNode(typeArguments[0].kind === typescript_1.default.SyntaxKind.NumberKeyword ? 'number' : 'string', []),
/* initializer */ undefined);
const indexSignature = factory.createTypeLiteralNode([
factory.createIndexSignature(
/* decorators */ undefined,
/* modifiers */ undefined, [index], typeArguments[1]),
]);
typescript_1.default.setEmitFlags(indexSignature, typescript_1.default.EmitFlags.SingleLine);
return indexSignature;
}
};
const accessibilityMask = typescript_1.default.ModifierFlags.Private | typescript_1.default.ModifierFlags.Protected | typescript_1.default.ModifierFlags.Public;
function modifiersFromJSDoc(methodDeclaration, factory) {
let modifierFlags = typescript_1.default.getCombinedModifierFlags(methodDeclaration);
if ((modifierFlags & accessibilityMask) !== 0) {
// Don't overwrite existing accessibility modifier.
return methodDeclaration.modifiers;
}
if (typescript_1.default.getJSDocPrivateTag(methodDeclaration)) {
modifierFlags |= typescript_1.default.ModifierFlags.Private;
}
else if (typescript_1.default.getJSDocProtectedTag(methodDeclaration)) {
modifierFlags |= typescript_1.default.ModifierFlags.Protected;
}
else if (typescript_1.default.getJSDocPublicTag(methodDeclaration)) {
modifierFlags |= typescript_1.default.ModifierFlags.Public;
}
else {
return methodDeclaration.modifiers;
}
return factory.createModifiersFromModifierFlags(modifierFlags);
}
// Copied from: https://github.com/microsoft/TypeScript/blob/v4.0.2/src/compiler/utilities.ts#L1879
function isJSDocIndexSignature(node) {
return (typescript_1.default.isTypeReferenceNode(node) &&
typescript_1.default.isIdentifier(node.typeName) &&
node.typeName.escapedText === 'Object' &&
node.typeArguments &&
node.typeArguments.length === 2 &&
(node.typeArguments[0].kind === typescript_1.default.SyntaxKind.StringKeyword ||
node.typeArguments[0].kind === typescript_1.default.SyntaxKind.NumberKeyword));
}
//# sourceMappingURL=jsdoc.js.map