UNPKG

14.1 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.default = void 0;
7
8var _lodash = _interopRequireDefault(require("lodash"));
9
10var _tagNames = require("./tagNames");
11
12var _WarnSettings = _interopRequireDefault(require("./WarnSettings"));
13
14function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15
16const getFunctionParameterNames = functionNode => {
17 const getParamName = param => {
18 if (_lodash.default.has(param, 'name')) {
19 return param.name;
20 }
21
22 if (_lodash.default.has(param, 'left.name')) {
23 return param.left.name;
24 }
25
26 if (param.type === 'ObjectPattern' || _lodash.default.get(param, 'left.type') === 'ObjectPattern') {
27 return '<ObjectPattern>';
28 }
29
30 if (param.type === 'ArrayPattern' || _lodash.default.get(param, 'left.type') === 'ArrayPattern') {
31 return '<ArrayPattern>';
32 }
33
34 if (param.type === 'RestElement') {
35 return param.argument.name;
36 }
37
38 if (param.type === 'TSParameterProperty') {
39 return getParamName(param.parameter);
40 }
41
42 throw new Error('Unsupported function signature format.');
43 };
44
45 return functionNode.params.map(getParamName);
46};
47/**
48 * Gets all parameter names, including those that refer to a path, e.g. "@param foo; @param foo.bar".
49 */
50
51
52const getJsdocParameterNamesDeep = (jsdoc, targetTagName) => {
53 let jsdocParameterNames;
54 jsdocParameterNames = _lodash.default.filter(jsdoc.tags, {
55 tag: targetTagName
56 });
57 jsdocParameterNames = _lodash.default.map(jsdocParameterNames, 'name');
58 return jsdocParameterNames;
59};
60
61const getJsdocParameterNames = (jsdoc, targetTagName) => {
62 let jsdocParameterNames;
63 jsdocParameterNames = getJsdocParameterNamesDeep(jsdoc, targetTagName);
64 jsdocParameterNames = jsdocParameterNames.filter(name => {
65 return !name.includes('.');
66 });
67 return jsdocParameterNames;
68};
69
70const modeWarnSettings = (0, _WarnSettings.default)();
71
72const getTagNamesForMode = (mode, context) => {
73 switch (mode) {
74 case 'jsdoc':
75 return _tagNames.jsdocTags;
76
77 case 'typescript':
78 return _tagNames.typeScriptTags;
79
80 case 'closure':
81 return _tagNames.closureTags;
82
83 default:
84 if (!modeWarnSettings.hasBeenWarned(context, 'mode')) {
85 context.report({
86 loc: {
87 start: {
88 column: 1,
89 line: 1
90 }
91 },
92 message: `Unrecognized value \`${mode}\` for \`settings.jsdoc.mode\`.`
93 });
94 modeWarnSettings.markSettingAsWarned(context, 'mode');
95 } // We'll avoid breaking too many other rules
96
97
98 return _tagNames.jsdocTags;
99 }
100};
101
102const getPreferredTagName = (context, mode, name, tagPreference = {}) => {
103 const prefValues = _lodash.default.values(tagPreference);
104
105 if (prefValues.includes(name) || prefValues.some(prefVal => {
106 return prefVal && typeof prefVal === 'object' && prefVal.replacement === name;
107 })) {
108 return name;
109 }
110
111 if (_lodash.default.has(tagPreference, name)) {
112 return tagPreference[name];
113 }
114
115 const tagNames = getTagNamesForMode(mode, context);
116
117 const preferredTagName = _lodash.default.findKey(tagNames, aliases => {
118 return aliases.includes(name);
119 });
120
121 if (preferredTagName) {
122 return preferredTagName;
123 }
124
125 return name;
126};
127
128const isValidTag = (context, mode, name, definedTags) => {
129 const tagNames = getTagNamesForMode(mode, context);
130
131 const validTagNames = _lodash.default.keys(tagNames).concat(_lodash.default.flatten(_lodash.default.values(tagNames)));
132
133 const additionalTags = definedTags;
134 const allTags = validTagNames.concat(additionalTags);
135 return allTags.includes(name);
136};
137
138const hasTag = (jsdoc, targetTagName) => {
139 const targetTagLower = targetTagName.toLowerCase();
140 return _lodash.default.some(jsdoc.tags, doc => {
141 return doc.tag.toLowerCase() === targetTagLower;
142 });
143};
144
145const hasATag = (jsdoc, targetTagNames) => {
146 return targetTagNames.some(targetTagName => {
147 return hasTag(jsdoc, targetTagName);
148 });
149};
150/**
151 * Checks if the JSDoc comment declares a return value.
152 *
153 * @param {JsDocTag} tag
154 * the tag which should be checked.
155 * @returns {boolean}
156 * true in case a return value is declared; otherwise false.
157 */
158
159
160const hasDefinedTypeReturnTag = tag => {
161 // The function should not continue in the event @returns is not defined...
162 if (typeof tag === 'undefined' || tag === null) {
163 return false;
164 } // .. same applies if it declares `@returns {undefined}` or `@returns {void}`
165
166
167 const tagType = tag.type.trim();
168
169 if (tagType === 'undefined' || tagType === 'void') {
170 return false;
171 } // In any other case, something must be returned, and
172 // a return statement is expected
173
174
175 return true;
176};
177
178const tagsWithMandatoryTypePosition = [// These both show curly brackets in the doc signature and examples
179// "typeExpression"
180'implements', // "typeName"
181'type'];
182const tagsWithMandatoryTypePositionClosure = [...tagsWithMandatoryTypePosition, 'this', 'define']; // All of these have a signature with "type" except for
183// `augments`/`extends` ("namepath")
184// `param`/`arg`/`argument` (no signature)
185// `property`/`prop` (no signature)
186// `modifies` (undocumented)
187
188const tagsWithOptionalTypePosition = [// These have the example showing curly brackets but not in their doc signature, e.g.: https://jsdoc.app/tags-enum.html
189'enum', 'member', 'var', 'typedef', // These do not show curly brackets in either the signature or examples
190'augments', 'extends', 'class', 'constructor', 'constant', 'const', // These show the signature with curly brackets but not in the example
191'module', 'namespace', // These have no formal signature in the docs but show curly brackets
192// in the examples
193'param', 'arg', 'argument', 'property', 'prop', // These show curly brackets in the signature and in the examples
194'returns', 'return', 'throws', 'exception', 'yields', 'yield', // Has no documentation, but test example has curly brackets, and
195// "name" would be suggested rather than "namepath" based on example; not
196// sure if name is required
197'modifies'];
198const tagsWithOptionalTypePositionClosure = [...tagsWithOptionalTypePosition, 'export', // Shows the signature with curly brackets but not in the example
199// "typeExpression"
200'package', 'private', 'protected', // These do not show a signature nor show curly brackets in the example
201'public', 'static']; // None of these show as having curly brackets for their name/namepath
202
203const namepathDefiningTags = [// These appear to require a "name" in their signature, albeit these
204// are somewhat different from other "name"'s (including as described
205// at https://jsdoc.app/about-namepaths.html )
206'external', 'host', 'event', // These allow for "name"'s in their signature, but indicate as optional
207'class', 'constructor', 'constant', 'const', 'function', 'func', 'method', 'interface', 'member', 'var', 'mixin', 'namespace', // Todo: Should add `module` here (with optional "name" and no curly brackets);
208// this block impacts `no-undefined-types` and `valid-types` (search for
209// "isNamepathDefiningTag|tagMightHaveNamePosition|tagMightHaveEitherTypeOrNamePosition")
210// These seem to all require a "namepath" in their signatures (with no counter-examples)
211'name', 'typedef', 'callback']; // The following do not seem to allow curly brackets in their doc
212// signature or examples (besides `modifies`)
213
214const tagsWithOptionalNamePosition = [...namepathDefiningTags, // `borrows` has a different format, however, so needs special parsing;
215// seems to require both, and as "namepath"'s
216'borrows', // Signature seems to require a "name" (of an event) and no counter-examples
217'emits', 'fires', 'listens', // Signature seems to require a "namepath" (and no counter-examples)
218'alias', 'augments', 'extends', 'lends', 'this', // Signature seems to require a "namepath" (and no counter-examples),
219// though it allows an incomplete namepath ending with connecting symbol
220'memberof', 'memberof!', // Signature seems to require a "OtherObjectPath" with no counter-examples
221'mixes', // Signature allows for "namepath" or text
222'see']; // Todo: `@link` seems to require a namepath OR URL and might be checked as such.
223// The doc signature of `event` seems to require a "name"
224
225const tagsWithMandatoryNamePosition = [// "name" (and a special syntax for the `external` name)
226'external', 'host', // "namepath"
227'callback', 'name', 'typedef'];
228const tagsWithMandatoryTypeOrNamePosition = [// "namepath"
229'alias', 'augments', 'extends', 'borrows', 'lends', 'memberof', 'memberof!', 'name', 'this', 'typedef', // "name"
230'external', 'host', // "OtherObjectPath"
231'mixes'];
232
233const isNamepathDefiningTag = tagName => {
234 return namepathDefiningTags.includes(tagName);
235};
236
237const tagMightHaveTypePosition = (mode, tag) => {
238 if (mode === 'closure') {
239 return tagsWithMandatoryTypePositionClosure.includes(tag) || tagsWithOptionalTypePositionClosure.includes(tag);
240 }
241
242 return tagsWithMandatoryTypePosition.includes(tag) || tagsWithOptionalTypePosition.includes(tag);
243};
244
245const tagMustHaveTypePosition = (mode, tag) => {
246 if (mode === 'closure') {
247 return tagsWithMandatoryTypePositionClosure.includes(tag);
248 }
249
250 return tagsWithMandatoryTypePosition.includes(tag);
251};
252
253const tagMightHaveNamePosition = tag => {
254 return tagsWithOptionalNamePosition.includes(tag);
255};
256
257const tagMustHaveNamePosition = tag => {
258 return tagsWithMandatoryNamePosition.includes(tag);
259};
260
261const tagMightHaveEitherTypeOrNamePosition = (mode, tag) => {
262 return tagMightHaveTypePosition(mode, tag) || tagMightHaveNamePosition(tag);
263};
264
265const tagMustHaveEitherTypeOrNamePosition = tag => {
266 return tagsWithMandatoryTypeOrNamePosition.includes(tag);
267};
268/**
269 * Checks if a node has a return statement. Void return does not count.
270 *
271 * @param {object} node
272 * @returns {boolean}
273 */
274// eslint-disable-next-line complexity
275
276
277const hasReturnValue = node => {
278 if (!node) {
279 return false;
280 }
281
282 switch (node.type) {
283 case 'FunctionExpression':
284 case 'FunctionDeclaration':
285 case 'ArrowFunctionExpression':
286 {
287 return node.expression || hasReturnValue(node.body);
288 }
289
290 case 'BlockStatement':
291 {
292 return node.body.some(bodyNode => {
293 return bodyNode.type !== 'FunctionDeclaration' && hasReturnValue(bodyNode);
294 });
295 }
296
297 case 'WhileStatement':
298 case 'DoWhileStatement':
299 case 'ForStatement':
300 case 'ForInStatement':
301 case 'ForOfStatement':
302 case 'WithStatement':
303 {
304 return hasReturnValue(node.body);
305 }
306
307 case 'IfStatement':
308 {
309 return hasReturnValue(node.consequent) || hasReturnValue(node.alternate);
310 }
311
312 case 'TryStatement':
313 {
314 return hasReturnValue(node.block) || hasReturnValue(node.handler && node.handler.body) || hasReturnValue(node.finalizer);
315 }
316
317 case 'SwitchStatement':
318 {
319 return node.cases.some(someCase => {
320 return someCase.consequent.some(hasReturnValue);
321 });
322 }
323
324 case 'ReturnStatement':
325 {
326 // void return does not count.
327 if (node.argument === null) {
328 return false;
329 }
330
331 return true;
332 }
333
334 default:
335 {
336 return false;
337 }
338 }
339};
340/** @param {string} tag */
341
342/*
343const isInlineTag = (tag) => {
344 return /^(@link|@linkcode|@linkplain|@tutorial) /u.test(tag);
345};
346*/
347
348/**
349 * Parses GCC Generic/Template types
350 *
351 * @see {https://github.com/google/closure-compiler/wiki/Generic-Types}
352 * @param {JsDocTag} tag
353 * @returns {Array<string>}
354 */
355
356
357const parseClosureTemplateTag = tag => {
358 return (tag.name + ' ' + tag.description).split(',').map(type => {
359 return type.trim();
360 });
361};
362/**
363 * Checks user option for `contexts` array, defaulting to
364 * contexts designated by the rule. Returns an array of
365 * ESTree AST types, indicating allowable contexts.
366 *
367 * @param {*} context
368 * @param {true|string[]} defaultContexts
369 * @returns {string[]}
370 */
371
372
373const enforcedContexts = (context, defaultContexts) => {
374 const _ref = context.options[0] || {},
375 _ref$contexts = _ref.contexts,
376 contexts = _ref$contexts === void 0 ? defaultContexts === true ? ['ArrowFunctionExpression', 'FunctionDeclaration', 'FunctionExpression'] : defaultContexts : _ref$contexts;
377
378 return contexts;
379};
380/**
381 * @param {string[]} contexts
382 * @param {Function} checkJsdoc
383 */
384
385
386const getContextObject = (contexts, checkJsdoc) => {
387 return contexts.reduce((obj, prop) => {
388 obj[prop] = checkJsdoc;
389 return obj;
390 }, {});
391};
392
393const filterTags = (tags = [], filter) => {
394 return tags.filter(filter);
395};
396
397const tagsWithNamesAndDescriptions = ['param', 'arg', 'argument', 'property', 'prop', // These two are parsed by our custom parser as though having a `name`
398'returns', 'return'];
399
400const getTagsByType = (context, mode, tags, tagPreference) => {
401 const descName = getPreferredTagName(context, mode, 'description', tagPreference);
402 const tagsWithoutNames = [];
403 const tagsWithNames = filterTags(tags, tag => {
404 const tagName = tag.tag;
405 const tagWithName = tagsWithNamesAndDescriptions.includes(tagName);
406
407 if (!tagWithName && tagName !== descName) {
408 tagsWithoutNames.push(tag);
409 }
410
411 return tagWithName;
412 });
413 return {
414 tagsWithNames,
415 tagsWithoutNames
416 };
417};
418
419const getIndent = sourceCode => {
420 let indent = sourceCode.text.match(/^\n*([ \t]+)/u);
421 /* istanbul ignore next */
422
423 indent = indent ? indent[1] + indent[1].charAt() : ' ';
424 return indent;
425};
426
427var _default = {
428 enforcedContexts,
429 filterTags,
430 getContextObject,
431 getFunctionParameterNames,
432 getIndent,
433 getJsdocParameterNames,
434 getJsdocParameterNamesDeep,
435 getPreferredTagName,
436 getTagsByType,
437 hasATag,
438 hasDefinedTypeReturnTag,
439 hasReturnValue,
440 hasTag,
441 isNamepathDefiningTag,
442 isValidTag,
443 parseClosureTemplateTag,
444 tagMightHaveEitherTypeOrNamePosition,
445 tagMightHaveNamePosition,
446 tagMightHaveTypePosition,
447 tagMustHaveEitherTypeOrNamePosition,
448 tagMustHaveNamePosition,
449 tagMustHaveTypePosition
450};
451exports.default = _default;
452module.exports = exports.default;
453//# sourceMappingURL=jsdocUtils.js.map
\No newline at end of file