UNPKG

16.4 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 = _interopRequireDefault(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 }), trim ? _commentParser.default.PARSERS.parse_description : // 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.tagMustHaveEitherTypeOrNamepath = tagName => {
207 return _jsdocUtils.default.tagMustHaveEitherTypeOrNamepath(tagName);
208 };
209
210 utils.tagMightHaveEitherTypeOrNamepath = tagName => {
211 return _jsdocUtils.default.tagMightHaveEitherTypeOrNamepath(mode, tagName);
212 };
213
214 utils.tagMustHaveNamepath = tagName => {
215 return _jsdocUtils.default.tagMustHaveNamepath(tagName);
216 };
217
218 utils.tagMightHaveNamepath = tagName => {
219 return _jsdocUtils.default.tagMightHaveNamepath(tagName);
220 };
221
222 utils.tagMustHaveType = tagName => {
223 return _jsdocUtils.default.tagMustHaveType(tagName);
224 };
225
226 utils.tagMightHaveAType = tagName => {
227 return _jsdocUtils.default.tagMightHaveAType(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 // Ancestors missing in `Program` comment iteration
274 const greatGrandParent = ancestors.length ? ancestors.slice(-3)[0] : _jsdocUtils.default.getAncestor(sourceCode, jsdocNode, 3);
275 const greatGrandParentValue = greatGrandParent && sourceCode.getFirstToken(greatGrandParent).value;
276
277 if (greatGrandParentValue === 'class') {
278 return greatGrandParent;
279 }
280
281 return false;
282 };
283
284 utils.getClassJsdoc = () => {
285 const classNode = utils.getClassNode();
286 const classJsdocNode = (0, _getJSDocComment.default)(sourceCode, classNode, {
287 maxLines,
288 minLines
289 });
290
291 if (classJsdocNode) {
292 const indent = ' '.repeat(classJsdocNode.loc.start.column);
293 return parseComment(classJsdocNode, indent);
294 }
295
296 return null;
297 };
298
299 utils.classHasTag = tagName => {
300 const classJsdoc = utils.getClassJsdoc();
301 return classJsdoc && _jsdocUtils.default.hasTag(classJsdoc, tagName);
302 };
303
304 utils.forEachPreferredTag = (tagName, arrayHandler, skipReportingBlockedTag = false) => {
305 const targetTagName = utils.getPreferredTagName({
306 skipReportingBlockedTag,
307 tagName
308 });
309
310 if (!targetTagName || skipReportingBlockedTag && targetTagName && typeof targetTagName === 'object') {
311 return;
312 }
313
314 const matchingJsdocTags = _lodash.default.filter(jsdoc.tags || [], {
315 tag: targetTagName
316 });
317
318 matchingJsdocTags.forEach(matchingJsdocTag => {
319 arrayHandler(matchingJsdocTag, targetTagName);
320 });
321 };
322
323 utils.reportSettings = message => {
324 context.report({
325 loc: {
326 start: {
327 column: 1,
328 line: 1
329 }
330 },
331 message
332 });
333 };
334
335 return utils;
336};
337
338const getSettings = context => {
339 const settings = {}; // All rules
340
341 settings.ignorePrivate = Boolean(_lodash.default.get(context, 'settings.jsdoc.ignorePrivate'));
342 settings.minLines = Number(_lodash.default.get(context, 'settings.jsdoc.minLines', 0));
343 settings.maxLines = Number(_lodash.default.get(context, 'settings.jsdoc.maxLines', 1)); // `check-tag-names` and many returns/param rules
344
345 settings.tagNamePreference = _lodash.default.get(context, 'settings.jsdoc.tagNamePreference') || {}; // `check-types` and `no-undefined-types`
346
347 settings.preferredTypes = _lodash.default.get(context, 'settings.jsdoc.preferredTypes') || {}; // `require-param`, `require-description`, `require-example`, `require-returns`
348
349 settings.overrideReplacesDocs = _lodash.default.get(context, 'settings.jsdoc.overrideReplacesDocs');
350 settings.implementsReplacesDocs = _lodash.default.get(context, 'settings.jsdoc.implementsReplacesDocs');
351 settings.augmentsExtendsReplacesDocs = _lodash.default.get(context, 'settings.jsdoc.augmentsExtendsReplacesDocs'); // Many rules, e.g., `check-tag-names`
352
353 settings.mode = _lodash.default.get(context, 'settings.jsdoc.mode') || 'jsdoc';
354 return settings;
355};
356/**
357 * Create the report function
358 *
359 * @param {object} context
360 * @param {object} commentNode
361 */
362
363
364exports.getSettings = getSettings;
365
366const makeReport = (context, commentNode) => {
367 const report = (message, fix = null, jsdocLoc = null, data = null) => {
368 let loc;
369
370 if (jsdocLoc) {
371 const lineNumber = commentNode.loc.start.line + jsdocLoc.line;
372 loc = {
373 end: {
374 line: lineNumber
375 },
376 start: {
377 line: lineNumber
378 }
379 };
380
381 if (jsdocLoc.column) {
382 const colNumber = commentNode.loc.start.column + jsdocLoc.column;
383 loc.end.column = colNumber;
384 loc.start.column = colNumber;
385 }
386 }
387
388 context.report({
389 data,
390 fix,
391 loc,
392 message,
393 node: commentNode
394 });
395 };
396
397 return report;
398};
399/**
400 * @typedef {ReturnType<typeof getUtils>} Utils
401 * @typedef {ReturnType<typeof getSettings>} Settings
402 * @typedef {(
403 * arg: {
404 * context: object,
405 * sourceCode: object,
406 * indent: string,
407 * jsdoc: object,
408 * jsdocNode: object,
409 * node: object | null,
410 * report: ReturnType<typeof makeReport>,
411 * settings: Settings,
412 * utils: Utils,
413 * }
414 * ) => any } JsdocVisitor
415 */
416
417/**
418 * Create an eslint rule that iterates over all JSDocs, regardless of whether
419 * they are attached to a function-like node.
420 *
421 * @param {JsdocVisitor} iterator
422 * @param {{meta: any}} ruleConfig
423 */
424
425
426const iterateAllJsdocs = (iterator, ruleConfig) => {
427 return {
428 create(context) {
429 return {
430 'Program'() {
431 const sourceCode = context.getSourceCode();
432 const comments = sourceCode.getAllComments();
433 comments.forEach(comment => {
434 if (!/^\/\*\*\s/.test(sourceCode.getText(comment))) {
435 return;
436 }
437
438 const sourceLine = sourceCode.lines[comment.loc.start.line - 1];
439 const indent = sourceLine.charAt(0).repeat(comment.loc.start.column);
440 const jsdoc = parseComment(comment, indent, !ruleConfig.noTrim);
441 const settings = getSettings(context);
442 const report = makeReport(context, comment);
443 const jsdocNode = comment;
444 iterator({
445 context,
446 indent,
447 jsdoc,
448 jsdocNode,
449 node: null,
450 report,
451 settings,
452 sourceCode,
453 utils: getUtils(null, jsdoc, jsdocNode, settings, report, context)
454 });
455 });
456 }
457
458 };
459 },
460
461 meta: ruleConfig.meta
462 };
463};
464
465/**
466 * @param {JsdocVisitor} iterator
467 * @param {{
468 * meta: any,
469 * contextDefaults?: true | string[],
470 * iterateAllJsdocs?: true,
471 * }} ruleConfig
472 */
473function iterateJsdoc(iterator, ruleConfig) {
474 const metaType = _lodash.default.get(ruleConfig, 'meta.type');
475
476 if (!metaType || !['problem', 'suggestion', 'layout'].includes(metaType)) {
477 throw new TypeError('Rule must include `meta.type` option (with value "problem", "suggestion", or "layout")');
478 }
479
480 if (typeof iterator !== 'function') {
481 throw new TypeError('The iterator argument must be a function.');
482 }
483
484 if (ruleConfig.iterateAllJsdocs) {
485 return iterateAllJsdocs(iterator, {
486 meta: ruleConfig.meta,
487 noTrim: ruleConfig.noTrim
488 });
489 }
490
491 return {
492 /**
493 * The entrypoint for the JSDoc rule.
494 *
495 * @param {*} context
496 * a reference to the context which hold all important information
497 * like settings and the sourcecode to check.
498 * @returns {object}
499 * a list with parser callback function.
500 */
501 create(context) {
502 const sourceCode = context.getSourceCode();
503 const settings = getSettings(context);
504
505 const checkJsdoc = node => {
506 const jsdocNode = (0, _getJSDocComment.default)(sourceCode, node, settings);
507
508 if (!jsdocNode) {
509 return;
510 }
511
512 const sourceLine = sourceCode.lines[jsdocNode.loc.start.line - 1];
513 const indent = sourceLine.charAt(0).repeat(jsdocNode.loc.start.column);
514 const jsdoc = parseComment(jsdocNode, indent);
515 const report = makeReport(context, jsdocNode);
516 const utils = getUtils(node, jsdoc, jsdocNode, settings, report, context);
517
518 if (settings.ignorePrivate && utils.hasTag('private')) {
519 return;
520 }
521
522 iterator({
523 context,
524 indent,
525 jsdoc,
526 jsdocNode,
527 node,
528 report,
529 settings,
530 sourceCode,
531 utils
532 });
533 };
534
535 if (ruleConfig.contextDefaults) {
536 const contexts = _jsdocUtils.default.enforcedContexts(context, ruleConfig.contextDefaults);
537
538 return _jsdocUtils.default.getContextObject(contexts, checkJsdoc);
539 }
540
541 return {
542 ArrowFunctionExpression: checkJsdoc,
543 FunctionDeclaration: checkJsdoc,
544 FunctionExpression: checkJsdoc
545 };
546 },
547
548 meta: ruleConfig.meta
549 };
550}
551//# sourceMappingURL=iterateJsdoc.js.map
\No newline at end of file