UNPKG

4.69 kBJavaScriptView Raw
1const generate = require('@babel/generator').default;
2
3const namedTypes = {
4 TSNumberKeyword: 'number',
5 TSBooleanKeyword: 'boolean',
6 TSStringKeyword: 'string',
7 TSSymbolKeyword: 'symbol',
8 TSThisType: 'this',
9 TSObjectKeyword: 'object',
10 TSNeverKeyword: 'never'
11};
12
13const oneToOne = {
14 TSAnyKeyword: 'AllLiteral',
15 TSUnknownKeyword: 'AllLiteral',
16 TSNullKeyword: 'NullLiteral',
17 TSUndefinedKeyword: 'UndefinedLiteral',
18 TSVoidKeyword: 'VoidLiteral'
19};
20
21function propertyToField(property) {
22 if (!property.typeAnnotation) return null;
23
24 let type = tsDoctrine(property.typeAnnotation.typeAnnotation);
25 if (property.optional) {
26 // Doctrine does not support optional fields but it does have something called optional types
27 // (which makes no sense, but let's play along).
28 type = {
29 type: 'OptionalType',
30 expression: type
31 };
32 }
33 return {
34 type: 'FieldType',
35 key: property.key ? property.key.name || property.key.value : '',
36 value: type
37 };
38}
39
40/**
41 * Babel parses TypeScript annotations in JavaScript into AST nodes. documentation.js uses
42 * Babel to parse TypeScript. This method restructures those Babel-generated
43 * objects into objects that fit the output of Doctrine, the module we use
44 * to parse JSDoc annotations. This lets us use TypeScript annotations _as_
45 * JSDoc annotations.
46 *
47 * @private
48 * @param {Object} type babel-parsed typescript type
49 * @returns {Object} doctrine compatible type
50 */
51function tsDoctrine(type) {
52 if (type.type in namedTypes) {
53 const doctrineType = {
54 type: 'NameExpression',
55 name: namedTypes[type.type]
56 };
57 return doctrineType;
58 }
59
60 // TODO: unhandled types
61 // TSIntersectionType, TSConditionalType, TSInferType, TSTypeOperator, TSIndexedAccessType
62 // TSMappedType, TSImportType, TSTypePredicate, TSTypeQuery, TSExpressionWithTypeArguments
63
64 if (type.type in oneToOne) {
65 return { type: oneToOne[type.type] };
66 }
67
68 switch (type.type) {
69 case 'TSOptionalType':
70 return {
71 type: 'NullableType',
72 expression: tsDoctrine(type.typeAnnotation)
73 };
74 case 'TSParenthesizedType':
75 return tsDoctrine(type.typeAnnotation);
76 case 'TSUnionType':
77 return {
78 type: 'UnionType',
79 elements: type.types.map(tsDoctrine)
80 };
81 // [number]
82 // [string, boolean, number]
83 case 'TSTupleType':
84 return {
85 type: 'ArrayType',
86 elements: type.elementTypes.map(tsDoctrine)
87 };
88 // number[]
89 case 'TSArrayType':
90 return {
91 type: 'TypeApplication',
92 expression: {
93 type: 'NameExpression',
94 name: 'Array'
95 },
96 applications: [tsDoctrine(type.elementType)]
97 };
98 // ...string
99 case 'TSRestType':
100 return {
101 type: 'RestType',
102 expression: tsDoctrine(type.typeAnnotation)
103 };
104 // (y: number) => bool
105 case 'TSFunctionType':
106 case 'TSConstructorType':
107 case 'TSMethodSignature':
108 return {
109 type: 'FunctionType',
110 params: type.parameters.map(param => {
111 if (param.type === 'RestElement') {
112 return {
113 type: 'RestType',
114 expression: {
115 type: 'ParameterType',
116 name: param.argument.name,
117 expression: tsDoctrine(param.typeAnnotation.typeAnnotation)
118 }
119 };
120 }
121
122 return {
123 type: 'ParameterType',
124 name: param.name,
125 expression: tsDoctrine(param.typeAnnotation.typeAnnotation)
126 };
127 }),
128 result: tsDoctrine(type.typeAnnotation.typeAnnotation)
129 };
130
131 case 'TSTypeReference':
132 if (type.typeParameters) {
133 return {
134 type: 'TypeApplication',
135 expression: {
136 type: 'NameExpression',
137 name: generate(type.typeName, {
138 compact: true
139 }).code
140 },
141 applications: type.typeParameters.params.map(tsDoctrine)
142 };
143 }
144
145 return {
146 type: 'NameExpression',
147 name: generate(type.typeName, {
148 compact: true
149 }).code
150 };
151
152 case 'TSTypeLiteral':
153 if (type.members) {
154 return {
155 type: 'RecordType',
156 fields: type.members.map(propertyToField).filter(x => x)
157 };
158 }
159
160 return {
161 type: 'NameExpression',
162 name: generate(type.id, {
163 compact: true
164 }).code
165 };
166 case 'TSLiteralType':
167 return {
168 type: `${type.literal.type}Type`,
169 value: type.literal.value
170 };
171 default:
172 return {
173 type: 'AllLiteral'
174 };
175 }
176}
177
178module.exports = tsDoctrine;