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}; // Todo: These type and namepath tag listings currently look
177// at tags with `{...}` as being a type, but jsdoc may
178// allow some namepaths only within brackets as well
179
180
181const tagsWithMandatoryType = [// These both show curly brackets in the doc signature and examples
182// "typeExpression"
183'implements', // "typeName"
184'type']; // All of these have a signature with "type" except for
185// `augments`/`extends` ("namepath")
186// `param`/`arg`/`argument` (no signature)
187// `property`/`prop` (no signature)
188// `modifies` (undocumented)
189
190const tagsWithOptionalType = [// These have the example showing curly brackets but not in their doc signature, e.g.: https://jsdoc.app/tags-enum.html
191'enum', 'member', 'var', 'typedef', // These do not show curly brackets in either the signature or examples
192'augments', 'extends', 'class', 'constructor', 'constant', 'const', // These show the signature with curly brackets but not in the example
193'module', 'namespace', // These have no formal signature in the docs but show curly brackets
194// in the examples
195'param', 'arg', 'argument', 'property', 'prop', // These show curly brackets in the signature and in the examples
196'returns', 'return', 'throws', 'exception', 'yields', 'yield', // Has no documentation, but test example has curly brackets, and
197// "name" would be suggested rather than "namepath" based on example; not
198// sure if name is required
199'modifies'];
200const tagsWithOptionalTypeClosure = [...tagsWithOptionalType, // Shows the signature with curly brackets but not in the example
201// "typeExpression"
202'package', 'private', 'protected', // These do not show a signature nor show curly brackets in the example
203'public', 'static']; // None of these show as having curly brackets for their name/namepath
204
205const namepathDefiningTags = [// These appear to require a "name" in their signature, albeit these
206// are somewhat different from other "name"'s (including as described
207// at https://jsdoc.app/about-namepaths.html )
208'external', 'host', 'event', // These allow for "name"'s in their signature, but indicate as optional
209'class', 'constructor', 'constant', 'const', 'function', 'func', 'method', 'interface', 'member', 'var', 'mixin', 'namespace', // Todo: Should add `module` here (with optional "name" and no curly brackets);
210// this block impacts `no-undefined-types` and `valid-types` (search for "isNamepathDefiningTag|tagMightHaveNamepath|tagMightHaveEitherTypeOrNamepath")
211// These seem to all require a "namepath" in their signatures (with no counter-examples)
212'name', 'typedef', 'callback']; // The following do not seem to allow curly brackets in their doc
213// signature or examples (besides `modifies`)
214
215const tagsWithOptionalNamepath = [...namepathDefiningTags, // `borrows` has a different format, however, so needs special parsing;
216// seems to require both, and as "namepath"'s
217'borrows', // Signature seems to require a "name" (of an event) and no counter-examples
218'emits', 'fires', 'listens', // Signature seems to require a "namepath" (and no counter-examples)
219'alias', 'augments', 'extends', 'lends', 'this', // Signature seems to require a "namepath" (and no counter-examples),
220// though it allows an incomplete namepath ending with connecting symbol
221'memberof', 'memberof!', // Signature seems to require a "OtherObjectPath" with no counter-examples
222'mixes', // Signature allows for "namepath" or text
223'see']; // Todo: `@link` seems to require a namepath OR URL and might be checked as such.
224// The doc signature of `event` seems to require a "name"
225
226const tagsWithMandatoryNamepath = [// "name" (and a special syntax for the `external` name)
227'external', 'host', // "namepath"
228'callback', 'name', 'typedef'];
229const tagsWithMandatoryTypeOrNamepath = [// "namepath"
230'alias', 'augments', 'extends', 'borrows', 'lends', 'memberof', 'memberof!', 'name', 'this', 'typedef', // "name"
231'external', 'host', // "OtherObjectPath"
232'mixes'];
233
234const isNamepathDefiningTag = tagName => {
235 return namepathDefiningTags.includes(tagName);
236};
237
238const tagMightHaveAType = (mode, tag) => {
239 return tagsWithMandatoryType.includes(tag) || (mode === 'closure' ? tagsWithOptionalTypeClosure.includes(tag) : tagsWithOptionalType.includes(tag));
240};
241
242const tagMustHaveType = tag => {
243 return tagsWithMandatoryType.includes(tag);
244};
245
246const tagMightHaveNamepath = tag => {
247 return tagsWithOptionalNamepath.includes(tag);
248};
249
250const tagMustHaveNamepath = tag => {
251 return tagsWithMandatoryNamepath.includes(tag);
252};
253
254const tagMightHaveEitherTypeOrNamepath = (mode, tag) => {
255 return tagMightHaveAType(mode, tag) || tagMightHaveNamepath(tag);
256};
257
258const tagMustHaveEitherTypeOrNamepath = tag => {
259 return tagsWithMandatoryTypeOrNamepath.includes(tag);
260};
261/**
262 * Checks if a node has a return statement. Void return does not count.
263 *
264 * @param {object} node
265 * @returns {boolean}
266 */
267// eslint-disable-next-line complexity
268
269
270const hasReturnValue = node => {
271 if (!node) {
272 return false;
273 }
274
275 switch (node.type) {
276 case 'FunctionExpression':
277 case 'FunctionDeclaration':
278 case 'ArrowFunctionExpression':
279 {
280 return node.expression || hasReturnValue(node.body);
281 }
282
283 case 'BlockStatement':
284 {
285 return node.body.some(bodyNode => {
286 return bodyNode.type !== 'FunctionDeclaration' && hasReturnValue(bodyNode);
287 });
288 }
289
290 case 'WhileStatement':
291 case 'DoWhileStatement':
292 case 'ForStatement':
293 case 'ForInStatement':
294 case 'ForOfStatement':
295 case 'WithStatement':
296 {
297 return hasReturnValue(node.body);
298 }
299
300 case 'IfStatement':
301 {
302 return hasReturnValue(node.consequent) || hasReturnValue(node.alternate);
303 }
304
305 case 'TryStatement':
306 {
307 return hasReturnValue(node.block) || hasReturnValue(node.handler && node.handler.body) || hasReturnValue(node.finalizer);
308 }
309
310 case 'SwitchStatement':
311 {
312 return node.cases.some(someCase => {
313 return someCase.consequent.some(hasReturnValue);
314 });
315 }
316
317 case 'ReturnStatement':
318 {
319 // void return does not count.
320 if (node.argument === null) {
321 return false;
322 }
323
324 return true;
325 }
326
327 default:
328 {
329 return false;
330 }
331 }
332};
333/** @param {string} tag */
334
335/*
336const isInlineTag = (tag) => {
337 return /^(@link|@linkcode|@linkplain|@tutorial) /u.test(tag);
338};
339*/
340
341/**
342 * Parses GCC Generic/Template types
343 *
344 * @see {https://github.com/google/closure-compiler/wiki/Generic-Types}
345 * @param {JsDocTag} tag
346 * @returns {Array<string>}
347 */
348
349
350const parseClosureTemplateTag = tag => {
351 return (tag.name + ' ' + tag.description).split(',').map(type => {
352 return type.trim();
353 });
354};
355/**
356 * Checks user option for `contexts` array, defaulting to
357 * contexts designated by the rule. Returns an array of
358 * ESTree AST types, indicating allowable contexts.
359 *
360 * @param {*} context
361 * @param {true|string[]} defaultContexts
362 * @returns {string[]}
363 */
364
365
366const enforcedContexts = (context, defaultContexts) => {
367 const _ref = context.options[0] || {},
368 _ref$contexts = _ref.contexts,
369 contexts = _ref$contexts === void 0 ? defaultContexts === true ? ['ArrowFunctionExpression', 'FunctionDeclaration', 'FunctionExpression'] : defaultContexts : _ref$contexts;
370
371 return contexts;
372};
373/**
374 * @param {string[]} contexts
375 * @param {Function} checkJsdoc
376 */
377
378
379const getContextObject = (contexts, checkJsdoc) => {
380 return contexts.reduce((obj, prop) => {
381 obj[prop] = checkJsdoc;
382 return obj;
383 }, {});
384};
385
386const filterTags = (tags = [], filter) => {
387 return tags.filter(filter);
388};
389
390const tagsWithNamesAndDescriptions = ['param', 'arg', 'argument', 'property', 'prop', // These two are parsed by our custom parser as though having a `name`
391'returns', 'return'];
392
393const getTagsByType = (context, mode, tags, tagPreference) => {
394 const descName = getPreferredTagName(context, mode, 'description', tagPreference);
395 const tagsWithoutNames = [];
396 const tagsWithNames = filterTags(tags, tag => {
397 const tagName = tag.tag;
398 const tagWithName = tagsWithNamesAndDescriptions.includes(tagName);
399
400 if (!tagWithName && tagName !== descName) {
401 tagsWithoutNames.push(tag);
402 }
403
404 return tagWithName;
405 });
406 return {
407 tagsWithNames,
408 tagsWithoutNames
409 };
410};
411
412const getAncestor = (sourceCode, nde, depth, idx = 0) => {
413 if (idx === depth) {
414 return nde;
415 }
416
417 const prevToken = sourceCode.getTokenBefore(nde);
418
419 if (prevToken) {
420 return getAncestor(sourceCode, prevToken, depth, idx + 1);
421 }
422
423 return null;
424};
425
426const getIndent = sourceCode => {
427 let indent = sourceCode.text.match(/^\n*([ \t]+)/u);
428 /* istanbul ignore next */
429
430 indent = indent ? indent[1] + indent[1].charAt() : ' ';
431 return indent;
432};
433
434var _default = {
435 enforcedContexts,
436 filterTags,
437 getAncestor,
438 getContextObject,
439 getFunctionParameterNames,
440 getIndent,
441 getJsdocParameterNames,
442 getJsdocParameterNamesDeep,
443 getPreferredTagName,
444 getTagsByType,
445 hasATag,
446 hasDefinedTypeReturnTag,
447 hasReturnValue,
448 hasTag,
449 isNamepathDefiningTag,
450 isValidTag,
451 parseClosureTemplateTag,
452 tagMightHaveAType,
453 tagMightHaveEitherTypeOrNamepath,
454 tagMightHaveNamepath,
455 tagMustHaveEitherTypeOrNamepath,
456 tagMustHaveNamepath,
457 tagMustHaveType
458};
459exports.default = _default;
460module.exports = exports.default;
461//# sourceMappingURL=jsdocUtils.js.map
\No newline at end of file