UNPKG

documentation

Version:
181 lines (166 loc) 4.75 kB
import generator from '@babel/generator'; const generate = generator.default || generator; const namedTypes = { TSBigIntKeyword: 'bigint', TSNumberKeyword: 'number', TSBooleanKeyword: 'boolean', TSStringKeyword: 'string', TSSymbolKeyword: 'symbol', TSThisType: 'this', TSObjectKeyword: 'object', TSNeverKeyword: 'never' }; const oneToOne = { TSAnyKeyword: 'AllLiteral', TSUnknownKeyword: 'AllLiteral', TSNullKeyword: 'NullLiteral', TSUndefinedKeyword: 'UndefinedLiteral', TSVoidKeyword: 'VoidLiteral' }; function propertyToField(property) { if (!property.typeAnnotation) return null; let type = tsDoctrine(property.typeAnnotation.typeAnnotation); if (property.optional) { // Doctrine does not support optional fields but it does have something called optional types // (which makes no sense, but let's play along). type = { type: 'OptionalType', expression: type }; } return { type: 'FieldType', key: property.key ? property.key.name || property.key.value : '', value: type }; } /** * Babel parses TypeScript annotations in JavaScript into AST nodes. documentation.js uses * Babel to parse TypeScript. This method restructures those Babel-generated * objects into objects that fit the output of Doctrine, the module we use * to parse JSDoc annotations. This lets us use TypeScript annotations _as_ * JSDoc annotations. * * @private * @param {Object} type babel-parsed typescript type * @returns {Object} doctrine compatible type */ function tsDoctrine(type) { if (type.type in namedTypes) { const doctrineType = { type: 'NameExpression', name: namedTypes[type.type] }; return doctrineType; } // TODO: unhandled types // TSIntersectionType, TSConditionalType, TSInferType, TSTypeOperator, TSIndexedAccessType // TSMappedType, TSImportType, TSTypePredicate, TSTypeQuery, TSExpressionWithTypeArguments if (type.type in oneToOne) { return { type: oneToOne[type.type] }; } switch (type.type) { case 'TSOptionalType': return { type: 'NullableType', expression: tsDoctrine(type.typeAnnotation) }; case 'TSParenthesizedType': return tsDoctrine(type.typeAnnotation); case 'TSUnionType': return { type: 'UnionType', elements: type.types.map(tsDoctrine) }; // [number] // [string, boolean, number] case 'TSTupleType': return { type: 'ArrayType', elements: type.elementTypes.map(tsDoctrine) }; // number[] case 'TSArrayType': return { type: 'TypeApplication', expression: { type: 'NameExpression', name: 'Array' }, applications: [tsDoctrine(type.elementType)] }; // ...string case 'TSRestType': return { type: 'RestType', expression: tsDoctrine(type.typeAnnotation) }; // (y: number) => bool case 'TSFunctionType': case 'TSConstructorType': case 'TSMethodSignature': return { type: 'FunctionType', params: type.parameters.map(param => { if (param.type === 'RestElement') { return { type: 'RestType', expression: { type: 'ParameterType', name: param.argument.name, expression: tsDoctrine(param.typeAnnotation.typeAnnotation) } }; } return { type: 'ParameterType', name: param.name, expression: tsDoctrine(param.typeAnnotation.typeAnnotation) }; }), result: tsDoctrine(type.typeAnnotation.typeAnnotation) }; case 'TSTypeReference': if (type.typeParameters) { return { type: 'TypeApplication', expression: { type: 'NameExpression', name: generate(type.typeName, { compact: true }).code }, applications: type.typeParameters.params.map(tsDoctrine) }; } return { type: 'NameExpression', name: generate(type.typeName, { compact: true }).code }; case 'TSTypeLiteral': if (type.members) { return { type: 'RecordType', fields: type.members.map(propertyToField).filter(x => x) }; } return { type: 'NameExpression', name: generate(type.id, { compact: true }).code }; case 'TSLiteralType': return { type: `${type.literal.type}Type`, value: type.literal.value }; default: return { type: 'AllLiteral' }; } } export default tsDoctrine;