documentation
Version:
a documentation generator
181 lines (166 loc) • 4.75 kB
JavaScript
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;