UNPKG

17 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.default = iterateJsdoc;
7exports.parseComment = exports.getSettings = void 0;
8
9var _commentParser = _interopRequireWildcard(require("comment-parser"));
10
11var _lodash = _interopRequireDefault(require("lodash"));
12
13var _jsdocUtils = _interopRequireDefault(require("./jsdocUtils"));
14
15var _getJSDocComment = _interopRequireWildcard(require("./eslint/getJSDocComment"));
16
17function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
18
19function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }
20
21function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
22
23// eslint-disable-next-line import/no-named-default
24const skipSeeLink = parser => {
25 return (str, data) => {
26 if (data.tag === 'see' && str.match(/\{@link.+?\}/u)) {
27 return null;
28 }
29
30 return parser(str, data);
31 };
32};
33/**
34 *
35 * @param {object} commentNode
36 * @param {string} indent Whitespace
37 * @param {boolean} [trim=true]
38 * @returns {object}
39 */
40
41
42const parseComment = (commentNode, indent, trim = true) => {
43 // Preserve JSDoc block start/end indentation.
44 return (0, _commentParser.default)(`${indent}/*${commentNode.value}${indent}*/`, {
45 // @see https://github.com/yavorskiy/comment-parser/issues/21
46 parsers: [_commentParser.default.PARSERS.parse_tag, skipSeeLink(_commentParser.default.PARSERS.parse_type), skipSeeLink((str, data) => {
47 if (['example', 'return', 'returns', 'throws', 'exception', 'access', 'version', 'since', 'license', 'author'].includes(data.tag)) {
48 return null;
49 }
50
51 return _commentParser.default.PARSERS.parse_name(str, data);
52 }), // parse_description
53 (str, data) => {
54 // Only expected throw in previous step is if bad name (i.e.,
55 // missing end bracket on optional name), but `@example`
56 // skips name parsing
57
58 /* istanbul ignore next */
59 if (data.errors && data.errors.length) {
60 return null;
61 } // Tweak original regex to capture only single optional space
62
63
64 const result = str.match(/^ ?((.|\s)+)?/u); // Always has at least whitespace due to `indent` we've added
65
66 /* istanbul ignore next */
67
68 if (result) {
69 return {
70 data: {
71 description: result[1] === undefined ? '' : result[1]
72 },
73 source: result[0]
74 };
75 } // Always has at least whitespace due to `indent` we've added
76
77 /* istanbul ignore next */
78
79
80 return null;
81 }],
82 trim
83 })[0] || {};
84};
85
86exports.parseComment = parseComment;
87
88const getUtils = (node, jsdoc, jsdocNode, {
89 tagNamePreference,
90 overrideReplacesDocs,
91 implementsReplacesDocs,
92 augmentsExtendsReplacesDocs,
93 maxLines,
94 minLines,
95 mode
96}, report, context) => {
97 const ancestors = context.getAncestors();
98 const sourceCode = context.getSourceCode();
99 const utils = {};
100
101 utils.stringify = tagBlock => {
102 const indent = _jsdocUtils.default.getIndent(sourceCode);
103
104 return (0, _commentParser.stringify)([tagBlock], {
105 indent
106 }).slice(indent.length - 1);
107 };
108
109 utils.reportJSDoc = (msg, tag, handler) => {
110 report(msg, handler ? fixer => {
111 handler();
112 const replacement = utils.stringify(jsdoc);
113 return fixer.replaceText(jsdocNode, replacement);
114 } : null, tag);
115 };
116
117 utils.getFunctionParameterNames = () => {
118 return _jsdocUtils.default.getFunctionParameterNames(node);
119 };
120
121 utils.isConstructor = () => {
122 return node.parent && node.parent.kind === 'constructor';
123 };
124
125 utils.isSetter = () => {
126 return node.parent.kind === 'set';
127 };
128
129 utils.getJsdocParameterNamesDeep = () => {
130 const param = utils.getPreferredTagName({
131 tagName: 'param'
132 });
133
134 if (!param) {
135 return false;
136 }
137
138 return _jsdocUtils.default.getJsdocParameterNamesDeep(jsdoc, param);
139 };
140
141 utils.getJsdocParameterNames = () => {
142 const param = utils.getPreferredTagName({
143 tagName: 'param'
144 });
145
146 if (!param) {
147 return false;
148 }
149
150 return _jsdocUtils.default.getJsdocParameterNames(jsdoc, param);
151 };
152
153 utils.getPreferredTagName = ({
154 tagName,
155 skipReportingBlockedTag = false,
156 allowObjectReturn = false,
157 defaultMessage = `Unexpected tag \`@${tagName}\``
158 }) => {
159 const ret = _jsdocUtils.default.getPreferredTagName(context, mode, tagName, tagNamePreference);
160
161 const isObject = ret && typeof ret === 'object';
162
163 if (utils.hasTag(tagName) && (ret === false || isObject && !ret.replacement)) {
164 if (skipReportingBlockedTag) {
165 return {
166 blocked: true,
167 tagName
168 };
169 }
170
171 const message = isObject && ret.message || defaultMessage;
172 report(message, null, utils.getTags(tagName)[0]);
173 return false;
174 }
175
176 return isObject && !allowObjectReturn ? ret.replacement : ret;
177 };
178
179 utils.isValidTag = (name, definedTags) => {
180 return _jsdocUtils.default.isValidTag(context, mode, name, definedTags);
181 };
182
183 utils.hasATag = name => {
184 return _jsdocUtils.default.hasATag(jsdoc, name);
185 };
186
187 utils.hasTag = name => {
188 return _jsdocUtils.default.hasTag(jsdoc, name);
189 };
190
191 utils.avoidDocs = () => {
192 if (overrideReplacesDocs !== false && (utils.hasTag('override') || utils.classHasTag('override')) || implementsReplacesDocs !== false && (utils.hasTag('implements') || utils.classHasTag('implements')) || // inheritdoc implies that all documentation is inherited; see https://jsdoc.app/tags-inheritdoc.html
193 utils.hasTag('inheritdoc') || augmentsExtendsReplacesDocs && (utils.hasATag(['augments', 'extends']) || utils.classHasTag('augments') || utils.classHasTag('extends'))) {
194 return true;
195 }
196
197 const exemptedBy = _lodash.default.get(context, 'options[0].exemptedBy');
198
199 if (exemptedBy && exemptedBy.length && utils.getPresentTags(exemptedBy).length) {
200 return true;
201 }
202
203 return false;
204 };
205
206 utils.tagMustHaveEitherTypeOrNamePosition = tagName => {
207 return _jsdocUtils.default.tagMustHaveEitherTypeOrNamePosition(tagName);
208 };
209
210 utils.tagMightHaveEitherTypeOrNamePosition = tagName => {
211 return _jsdocUtils.default.tagMightHaveEitherTypeOrNamePosition(mode, tagName);
212 };
213
214 utils.tagMustHaveNamePosition = tagName => {
215 return _jsdocUtils.default.tagMustHaveNamePosition(tagName);
216 };
217
218 utils.tagMightHaveNamePosition = tagName => {
219 return _jsdocUtils.default.tagMightHaveNamePosition(tagName);
220 };
221
222 utils.tagMustHaveTypePosition = tagName => {
223 return _jsdocUtils.default.tagMustHaveTypePosition(mode, tagName);
224 };
225
226 utils.tagMightHaveTypePosition = tagName => {
227 return _jsdocUtils.default.tagMightHaveTypePosition(mode, tagName);
228 };
229
230 utils.isNamepathDefiningTag = tagName => {
231 return _jsdocUtils.default.isNamepathDefiningTag(tagName);
232 };
233
234 utils.hasDefinedTypeReturnTag = tag => {
235 return _jsdocUtils.default.hasDefinedTypeReturnTag(tag);
236 };
237
238 utils.hasReturnValue = () => {
239 return _jsdocUtils.default.hasReturnValue(node);
240 };
241
242 utils.isAsync = () => {
243 return node.async;
244 };
245
246 utils.getTags = tagName => {
247 return utils.filterTags(item => {
248 return item.tag === tagName;
249 });
250 };
251
252 utils.getPresentTags = tagList => {
253 return utils.filterTags(tag => {
254 return tagList.includes(tag.tag);
255 });
256 };
257
258 utils.filterTags = filter => {
259 return _jsdocUtils.default.filterTags(jsdoc.tags, filter);
260 };
261
262 utils.getTagsByType = tags => {
263 return _jsdocUtils.default.getTagsByType(context, mode, tags, tagNamePreference);
264 };
265
266 utils.hasOptionTag = tagName => {
267 const tags = _lodash.default.get(context, 'options[0].tags');
268
269 return Boolean(tags && tags.includes(tagName));
270 };
271
272 utils.getClassNode = () => {
273 return [...ancestors, node].reverse().find(parent => {
274 return parent && ['ClassDeclaration', 'ClassExpression'].includes(parent.type);
275 }) || null;
276 };
277
278 utils.getClassJsdoc = () => {
279 const classNode = utils.getClassNode();
280
281 if (!classNode) {
282 return null;
283 }
284
285 const classJsdocNode = (0, _getJSDocComment.default)(sourceCode, classNode, {
286 maxLines,
287 minLines
288 });
289
290 if (classJsdocNode) {
291 const indent = ' '.repeat(classJsdocNode.loc.start.column);
292 return parseComment(classJsdocNode, indent);
293 }
294
295 return null;
296 };
297
298 utils.classHasTag = tagName => {
299 const classJsdoc = utils.getClassJsdoc();
300 return Boolean(classJsdoc) && _jsdocUtils.default.hasTag(classJsdoc, tagName);
301 };
302
303 utils.forEachPreferredTag = (tagName, arrayHandler, skipReportingBlockedTag = false) => {
304 const targetTagName = utils.getPreferredTagName({
305 skipReportingBlockedTag,
306 tagName
307 });
308
309 if (!targetTagName || skipReportingBlockedTag && targetTagName && typeof targetTagName === 'object') {
310 return;
311 }
312
313 const matchingJsdocTags = _lodash.default.filter(jsdoc.tags || [], {
314 tag: targetTagName
315 });
316
317 matchingJsdocTags.forEach(matchingJsdocTag => {
318 arrayHandler(matchingJsdocTag, targetTagName);
319 });
320 };
321
322 utils.reportSettings = message => {
323 context.report({
324 loc: {
325 start: {
326 column: 1,
327 line: 1
328 }
329 },
330 message
331 });
332 };
333
334 return utils;
335};
336
337const getSettings = context => {
338 const settings = {}; // All rules
339
340 settings.ignorePrivate = Boolean(_lodash.default.get(context, 'settings.jsdoc.ignorePrivate'));
341 settings.minLines = Number(_lodash.default.get(context, 'settings.jsdoc.minLines', 0));
342 settings.maxLines = Number(_lodash.default.get(context, 'settings.jsdoc.maxLines', 1)); // `check-tag-names` and many returns/param rules
343
344 settings.tagNamePreference = _lodash.default.get(context, 'settings.jsdoc.tagNamePreference') || {}; // `check-types` and `no-undefined-types`
345
346 settings.preferredTypes = _lodash.default.get(context, 'settings.jsdoc.preferredTypes') || {}; // `require-param`, `require-description`, `require-example`, `require-returns`
347
348 settings.overrideReplacesDocs = _lodash.default.get(context, 'settings.jsdoc.overrideReplacesDocs');
349 settings.implementsReplacesDocs = _lodash.default.get(context, 'settings.jsdoc.implementsReplacesDocs');
350 settings.augmentsExtendsReplacesDocs = _lodash.default.get(context, 'settings.jsdoc.augmentsExtendsReplacesDocs'); // Many rules, e.g., `check-tag-names`
351
352 settings.mode = _lodash.default.get(context, 'settings.jsdoc.mode') || 'jsdoc';
353 return settings;
354};
355/**
356 * Create the report function
357 *
358 * @param {object} context
359 * @param {object} commentNode
360 */
361
362
363exports.getSettings = getSettings;
364
365const makeReport = (context, commentNode) => {
366 const report = (message, fix = null, jsdocLoc = null, data = null) => {
367 let loc;
368
369 if (jsdocLoc) {
370 const lineNumber = commentNode.loc.start.line + jsdocLoc.line;
371 loc = {
372 end: {
373 line: lineNumber
374 },
375 start: {
376 line: lineNumber
377 }
378 };
379
380 if (jsdocLoc.column) {
381 const colNumber = commentNode.loc.start.column + jsdocLoc.column;
382 loc.end.column = colNumber;
383 loc.start.column = colNumber;
384 }
385 }
386
387 context.report({
388 data,
389 fix,
390 loc,
391 message,
392 node: commentNode
393 });
394 };
395
396 return report;
397};
398/**
399 * @typedef {ReturnType<typeof getUtils>} Utils
400 * @typedef {ReturnType<typeof getSettings>} Settings
401 * @typedef {(
402 * arg: {
403 * context: object,
404 * sourceCode: object,
405 * indent: string,
406 * jsdoc: object,
407 * jsdocNode: object,
408 * node: object | null,
409 * report: ReturnType<typeof makeReport>,
410 * settings: Settings,
411 * utils: Utils,
412 * }
413 * ) => any } JsdocVisitor
414 */
415
416/**
417 * Create an eslint rule that iterates over all JSDocs, regardless of whether
418 * they are attached to a function-like node.
419 *
420 * @param {JsdocVisitor} iterator
421 * @param {{meta: any}} ruleConfig
422 */
423
424
425const iterateAllJsdocs = (iterator, ruleConfig) => {
426 const trackedJsdocs = [];
427
428 const callIterator = (context, node, jsdocNodes) => {
429 const sourceCode = context.getSourceCode();
430 const settings = getSettings(context);
431 const lines = sourceCode.lines;
432 jsdocNodes.forEach(jsdocNode => {
433 if (!/^\/\*\*\s/.test(sourceCode.getText(jsdocNode))) {
434 return;
435 }
436
437 const sourceLine = lines[jsdocNode.loc.start.line - 1];
438 const indent = sourceLine.charAt(0).repeat(jsdocNode.loc.start.column);
439 const jsdoc = parseComment(jsdocNode, indent, !ruleConfig.noTrim);
440 const report = makeReport(context, jsdocNode);
441 iterator({
442 context,
443 indent,
444 jsdoc,
445 jsdocNode,
446 node,
447 report,
448 settings,
449 sourceCode,
450 utils: getUtils(node, jsdoc, jsdocNode, settings, report, context)
451 });
452 });
453 };
454
455 return {
456 create(context) {
457 const sourceCode = context.getSourceCode();
458 const settings = getSettings(context);
459 return {
460 '*:not(Program)'(node) {
461 const reducedNode = (0, _getJSDocComment.getReducedASTNode)(node, sourceCode);
462
463 if (node !== reducedNode) {
464 return;
465 }
466
467 const comment = (0, _getJSDocComment.default)(sourceCode, node, settings);
468
469 if (!comment || trackedJsdocs.includes(comment)) {
470 return;
471 }
472
473 trackedJsdocs.push(comment);
474 callIterator(context, node, [comment]);
475 },
476
477 'Program:exit'() {
478 const allJsdocs = sourceCode.getAllComments();
479 const untrackedJSdoc = allJsdocs.filter(node => {
480 return !trackedJsdocs.includes(node);
481 });
482 callIterator(context, null, untrackedJSdoc);
483 }
484
485 };
486 },
487
488 meta: ruleConfig.meta
489 };
490};
491
492/**
493 * @param {JsdocVisitor} iterator
494 * @param {{
495 * meta: any,
496 * contextDefaults?: true | string[],
497 * iterateAllJsdocs?: true,
498 * }} ruleConfig
499 */
500function iterateJsdoc(iterator, ruleConfig) {
501 const metaType = _lodash.default.get(ruleConfig, 'meta.type');
502
503 if (!metaType || !['problem', 'suggestion', 'layout'].includes(metaType)) {
504 throw new TypeError('Rule must include `meta.type` option (with value "problem", "suggestion", or "layout")');
505 }
506
507 if (typeof iterator !== 'function') {
508 throw new TypeError('The iterator argument must be a function.');
509 }
510
511 if (ruleConfig.iterateAllJsdocs) {
512 return iterateAllJsdocs(iterator, {
513 meta: ruleConfig.meta,
514 noTrim: ruleConfig.noTrim
515 });
516 }
517
518 return {
519 /**
520 * The entrypoint for the JSDoc rule.
521 *
522 * @param {*} context
523 * a reference to the context which hold all important information
524 * like settings and the sourcecode to check.
525 * @returns {object}
526 * a list with parser callback function.
527 */
528 create(context) {
529 const sourceCode = context.getSourceCode();
530 const settings = getSettings(context);
531
532 const checkJsdoc = node => {
533 const jsdocNode = (0, _getJSDocComment.default)(sourceCode, node, settings);
534
535 if (!jsdocNode) {
536 return;
537 }
538
539 const sourceLine = sourceCode.lines[jsdocNode.loc.start.line - 1];
540 const indent = sourceLine.charAt(0).repeat(jsdocNode.loc.start.column);
541 const jsdoc = parseComment(jsdocNode, indent);
542 const report = makeReport(context, jsdocNode);
543 const utils = getUtils(node, jsdoc, jsdocNode, settings, report, context);
544
545 if (settings.ignorePrivate && utils.hasTag('private')) {
546 return;
547 }
548
549 iterator({
550 context,
551 indent,
552 jsdoc,
553 jsdocNode,
554 node,
555 report,
556 settings,
557 sourceCode,
558 utils
559 });
560 };
561
562 if (ruleConfig.contextDefaults) {
563 const contexts = _jsdocUtils.default.enforcedContexts(context, ruleConfig.contextDefaults);
564
565 return _jsdocUtils.default.getContextObject(contexts, checkJsdoc);
566 }
567
568 return {
569 ArrowFunctionExpression: checkJsdoc,
570 FunctionDeclaration: checkJsdoc,
571 FunctionExpression: checkJsdoc
572 };
573 },
574
575 meta: ruleConfig.meta
576 };
577}
578//# sourceMappingURL=iterateJsdoc.js.map
\No newline at end of file