UNPKG

4.56 kBJavaScriptView Raw
1import doctrine from 'doctrine';
2
3function containsJsDoc(value) {
4 return value != null && value.includes('@');
5}
6
7function parse(content, tags) {
8 let ast;
9
10 try {
11 ast = doctrine.parse(content, {
12 tags,
13 sloppy: true
14 });
15 } catch (e) {
16 // eslint-disable-next-line no-console
17 console.error(e);
18 throw new Error('Cannot parse JSDoc tags.');
19 }
20
21 return ast;
22}
23
24const DEFAULT_OPTIONS = {
25 tags: ['param', 'arg', 'argument', 'returns', 'ignore']
26};
27export const parseJsDoc = (value, options = DEFAULT_OPTIONS) => {
28 if (!containsJsDoc(value)) {
29 return {
30 includesJsDoc: false,
31 ignore: false
32 };
33 }
34
35 const jsDocAst = parse(value, options.tags);
36 const extractedTags = extractJsDocTags(jsDocAst);
37
38 if (extractedTags.ignore) {
39 // There is no point in doing other stuff since this prop will not be rendered.
40 return {
41 includesJsDoc: true,
42 ignore: true
43 };
44 }
45
46 return {
47 includesJsDoc: true,
48 ignore: false,
49 // Always use the parsed description to ensure JSDoc is removed from the description.
50 description: jsDocAst.description,
51 extractedTags
52 };
53};
54
55function extractJsDocTags(ast) {
56 const extractedTags = {
57 params: null,
58 returns: null,
59 ignore: false
60 };
61
62 for (let i = 0; i < ast.tags.length; i += 1) {
63 const tag = ast.tags[i];
64
65 if (tag.title === 'ignore') {
66 extractedTags.ignore = true; // Once we reach an @ignore tag, there is no point in parsing the other tags since we will not render the prop.
67
68 break;
69 } else {
70 switch (tag.title) {
71 // arg & argument are aliases for param.
72 case 'param':
73 case 'arg':
74 case 'argument':
75 {
76 const paramTag = extractParam(tag);
77
78 if (paramTag != null) {
79 if (extractedTags.params == null) {
80 extractedTags.params = [];
81 }
82
83 extractedTags.params.push(paramTag);
84 }
85
86 break;
87 }
88
89 case 'returns':
90 {
91 const returnsTag = extractReturns(tag);
92
93 if (returnsTag != null) {
94 extractedTags.returns = returnsTag;
95 }
96
97 break;
98 }
99
100 default:
101 break;
102 }
103 }
104 }
105
106 return extractedTags;
107}
108
109function extractParam(tag) {
110 const paramName = tag.name; // When the @param doesn't have a name but have a type and a description, "null-null" is returned.
111
112 if (paramName != null && paramName !== 'null-null') {
113 return {
114 name: tag.name,
115 type: tag.type,
116 description: tag.description,
117 getPrettyName: () => {
118 if (paramName.includes('null')) {
119 // There is a few cases in which the returned param name contains "null".
120 // - @param {SyntheticEvent} event- Original SyntheticEvent
121 // - @param {SyntheticEvent} event.\n@returns {string}
122 return paramName.replace('-null', '').replace('.null', '');
123 }
124
125 return tag.name;
126 },
127 getTypeName: () => {
128 return tag.type != null ? extractTypeName(tag.type) : null;
129 }
130 };
131 }
132
133 return null;
134}
135
136function extractReturns(tag) {
137 if (tag.type != null) {
138 return {
139 type: tag.type,
140 description: tag.description,
141 getTypeName: () => {
142 return extractTypeName(tag.type);
143 }
144 };
145 }
146
147 return null;
148}
149
150function extractTypeName(type) {
151 if (type.type === 'NameExpression') {
152 return type.name;
153 }
154
155 if (type.type === 'RecordType') {
156 const recordFields = type.fields.map(field => {
157 if (field.value != null) {
158 const valueTypeName = extractTypeName(field.value);
159 return `${field.key}: ${valueTypeName}`;
160 }
161
162 return field.key;
163 });
164 return `({${recordFields.join(', ')}})`;
165 }
166
167 if (type.type === 'UnionType') {
168 const unionElements = type.elements.map(extractTypeName);
169 return `(${unionElements.join('|')})`;
170 } // Only support untyped array: []. Might add more support later if required.
171
172
173 if (type.type === 'ArrayType') {
174 return '[]';
175 }
176
177 if (type.type === 'TypeApplication') {
178 if (type.expression != null) {
179 if (type.expression.name === 'Array') {
180 const arrayType = extractTypeName(type.applications[0]);
181 return `${arrayType}[]`;
182 }
183 }
184 }
185
186 if (type.type === 'NullableType' || type.type === 'NonNullableType' || type.type === 'OptionalType') {
187 return extractTypeName(type.expression);
188 }
189
190 if (type.type === 'AllLiteral') {
191 return 'any';
192 }
193
194 return null;
195}
\No newline at end of file