UNPKG

@xtrek/ts-migrate-plugins

Version:

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

320 lines 14.7 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-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