blob: 91788e31b3a115762e8eccb279c55b97d04b8ab5 [file]
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = iterateJsdoc;
exports.getSettings = void 0;
Object.defineProperty(exports, "parseComment", {
enumerable: true,
get: function () {
return _jsdoccomment.parseComment;
}
});
var _jsdoccomment = require("@es-joy/jsdoccomment");
var _commentParser = require("comment-parser");
var _jsdocUtils = _interopRequireDefault(require("./jsdocUtils"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const {
rewireSpecs,
seedTokens
} = _commentParser.util;
// todo: Change these `any` types once importing types properly.
/**
* Should use ESLint rule's typing.
*
* @typedef {any} EslintRuleMeta
*/
/**
* A plain object for tracking state as needed by rules across iterations.
*
* @typedef {any} StateObject
*/
/**
* The Node AST as supplied by the parser.
*
* @typedef {any} Node
*/
/*
const {
align as commentAlign,
flow: commentFlow,
indent: commentIndent,
} = transforms;
*/
const globalState = new Map();
const getBasicUtils = (context, {
tagNamePreference,
mode
}) => {
const utils = {};
utils.reportSettings = message => {
context.report({
loc: {
start: {
column: 1,
line: 1
}
},
message
});
};
utils.parseClosureTemplateTag = tag => {
return _jsdocUtils.default.parseClosureTemplateTag(tag);
};
utils.pathDoesNotBeginWith = _jsdocUtils.default.pathDoesNotBeginWith;
utils.getPreferredTagNameObject = ({
tagName
}) => {
const ret = _jsdocUtils.default.getPreferredTagName(context, mode, tagName, tagNamePreference);
const isObject = ret && typeof ret === 'object';
if (ret === false || isObject && !ret.replacement) {
return {
blocked: true,
tagName
};
}
return ret;
};
return utils;
};
const getUtils = (node, jsdoc, jsdocNode, settings, report, context, iteratingAll, ruleConfig, indent) => {
const ancestors = context.getAncestors();
const sourceCode = context.getSourceCode();
const utils = getBasicUtils(context, settings);
const {
tagNamePreference,
overrideReplacesDocs,
ignoreReplacesDocs,
implementsReplacesDocs,
augmentsExtendsReplacesDocs,
maxLines,
minLines,
mode
} = settings;
utils.isIteratingFunction = () => {
return !iteratingAll || ['MethodDefinition', 'ArrowFunctionExpression', 'FunctionDeclaration', 'FunctionExpression'].includes(node && node.type);
};
utils.isVirtualFunction = () => {
return iteratingAll && utils.hasATag(['callback', 'function', 'func', 'method']);
};
utils.stringify = (tagBlock, specRewire) => {
return (0, _commentParser.stringify)(specRewire ? rewireSpecs(tagBlock) : tagBlock);
};
utils.reportJSDoc = (msg, tag, handler, specRewire, data) => {
report(msg, handler ? fixer => {
handler();
const replacement = utils.stringify(jsdoc, specRewire);
return fixer.replaceText(jsdocNode, replacement);
} : null, tag, data);
};
utils.getRegexFromString = (str, requiredFlags) => {
return _jsdocUtils.default.getRegexFromString(str, requiredFlags);
};
utils.getTagDescription = (tg, returnArray) => {
const descriptions = [];
tg.source.some(({
tokens: {
end,
lineEnd,
postDelimiter,
tag,
postTag,
name,
type,
description
}
}) => {
const desc = (tag && postTag || !tag && !name && !type && postDelimiter || ''
// Remove space
).slice(1) + (description || '') + (lineEnd || '');
if (end) {
if (desc) {
descriptions.push(desc);
}
return true;
}
descriptions.push(desc);
return false;
});
return returnArray ? descriptions : descriptions.join('\n');
};
utils.setTagDescription = (tg, matcher, setter) => {
let finalIdx = 0;
tg.source.some(({
tokens: {
description
}
}, idx) => {
if (description && matcher.test(description)) {
tg.source[idx].tokens.description = setter(description);
finalIdx = idx;
return true;
}
return false;
});
return finalIdx;
};
utils.getDescription = () => {
const descriptions = [];
let lastDescriptionLine = 0;
jsdoc.source.some(({
tokens: {
description,
tag,
end
}
}, idx) => {
if (idx && (tag || end)) {
lastDescriptionLine = idx - 1;
return true;
}
if (idx || description) {
descriptions.push(description || (descriptions.length ? '' : '\n'));
}
return false;
});
return {
description: descriptions.join('\n'),
descriptions,
lastDescriptionLine
};
};
utils.setDescriptionLines = (matcher, setter) => {
let finalIdx = 0;
jsdoc.source.some(({
tokens: {
description,
tag,
end
}
}, idx) => {
// istanbul ignore if -- Already checked
if (idx && (tag || end)) {
return true;
}
if (description && matcher.test(description)) {
jsdoc.source[idx].tokens.description = setter(description);
finalIdx = idx;
return true;
}
return false;
});
return finalIdx;
};
utils.changeTag = (tag, ...tokens) => {
for (const [idx, src] of tag.source.entries()) {
src.tokens = {
...src.tokens,
...tokens[idx]
};
}
};
utils.setTag = (tag, tokens) => {
tag.source = [{
// Or tag.source[0].number?
number: tag.line,
tokens: seedTokens({
delimiter: '*',
postDelimiter: ' ',
start: indent + ' ',
tag: '@' + tag.tag,
...tokens
})
}];
};
utils.removeTag = idx => {
return utils.removeTagItem(idx);
};
utils.removeTagItem = (tagIndex, tagSourceOffset = 0) => {
const {
source: tagSource
} = jsdoc.tags[tagIndex];
let lastIndex;
const firstNumber = jsdoc.source[0].number;
tagSource.some(({
number
}, tagIdx) => {
const sourceIndex = jsdoc.source.findIndex(({
number: srcNumber,
tokens: {
end
}
}) => {
return number === srcNumber && !end;
});
// istanbul ignore else
if (sourceIndex > -1) {
let spliceCount = 1;
tagSource.slice(tagIdx + 1).some(({
tokens: {
tag,
end
}
}) => {
if (!tag && !end) {
spliceCount++;
return false;
}
return true;
});
jsdoc.source.splice(sourceIndex + tagSourceOffset, spliceCount - tagSourceOffset);
tagSource.splice(tagIdx + tagSourceOffset, spliceCount - tagSourceOffset);
lastIndex = sourceIndex;
return true;
}
// istanbul ignore next
return false;
});
for (const [idx, src] of jsdoc.source.slice(lastIndex).entries()) {
src.number = firstNumber + lastIndex + idx;
}
// Todo: Once rewiring of tags may be fixed in comment-parser to reflect missing tags,
// this step should be added here (so that, e.g., if accessing `jsdoc.tags`,
// such as to add a new tag, the correct information will be available)
};
utils.addTag = (targetTagName, number = ((() => {
var _jsdoc$tags, _jsdoc$tags$source$;
return (_jsdoc$tags = jsdoc.tags[jsdoc.tags.length - 1]) === null || _jsdoc$tags === void 0 ? void 0 : (_jsdoc$tags$source$ = _jsdoc$tags.source[0]) === null || _jsdoc$tags$source$ === void 0 ? void 0 : _jsdoc$tags$source$.number;
})() ?? jsdoc.source.findIndex(({
tokens: {
tag
}
}) => {
return tag;
}) - 1) + 1, tokens = {}) => {
jsdoc.source.splice(number, 0, {
number,
source: '',
tokens: seedTokens({
delimiter: '*',
postDelimiter: ' ',
start: indent + ' ',
tag: `@${targetTagName}`,
...tokens
})
});
for (const src of jsdoc.source.slice(number + 1)) {
src.number++;
}
};
utils.getFirstLine = () => {
let firstLine;
for (const {
number,
tokens: {
tag
}
} of jsdoc.source) {
if (tag) {
firstLine = number;
break;
}
}
return firstLine;
};
utils.seedTokens = seedTokens;
utils.emptyTokens = tokens => {
for (const prop of ['start', 'postDelimiter', 'tag', 'type', 'postType', 'postTag', 'name', 'postName', 'description', 'end', 'lineEnd']) {
tokens[prop] = '';
}
};
utils.addLine = (sourceIndex, tokens) => {
var _jsdoc$source;
const number = (((_jsdoc$source = jsdoc.source[sourceIndex - 1]) === null || _jsdoc$source === void 0 ? void 0 : _jsdoc$source.number) || 0) + 1;
jsdoc.source.splice(sourceIndex, 0, {
number,
source: '',
tokens: seedTokens(tokens)
});
for (const src of jsdoc.source.slice(number + 1)) {
src.number++;
}
// If necessary, we can rewire the tags (misnamed method)
// rewireSource(jsdoc);
};
utils.addLines = (tagIndex, tagSourceOffset, numLines) => {
const {
source: tagSource
} = jsdoc.tags[tagIndex];
let lastIndex;
const firstNumber = jsdoc.source[0].number;
tagSource.some(({
number
}) => {
const makeLine = () => {
return {
number,
source: '',
tokens: seedTokens({
delimiter: '*',
start: indent + ' '
})
};
};
const makeLines = () => {
return Array.from({
length: numLines
}, makeLine);
};
const sourceIndex = jsdoc.source.findIndex(({
number: srcNumber,
tokens: {
end
}
}) => {
return number === srcNumber && !end;
});
// istanbul ignore else
if (sourceIndex > -1) {
const lines = makeLines();
jsdoc.source.splice(sourceIndex + tagSourceOffset, 0, ...lines);
// tagSource.splice(tagIdx + 1, 0, ...makeLines());
lastIndex = sourceIndex;
return true;
}
// istanbul ignore next
return false;
});
for (const [idx, src] of jsdoc.source.slice(lastIndex).entries()) {
src.number = firstNumber + lastIndex + idx;
}
};
utils.makeMultiline = () => {
const {
source: [{
tokens
}]
} = jsdoc;
const {
postDelimiter,
description,
lineEnd,
tag,
name,
type
} = tokens;
let {
tokens: {
postName,
postTag,
postType
}
} = jsdoc.source[0];
// Strip trailing leftovers from single line ending
if (!description) {
if (postName) {
postName = '';
} else if (postType) {
postType = '';
// eslint-disable-next-line no-inline-comments
} else /* istanbul ignore else -- `comment-parser` prevents empty blocks currently per https://github.com/syavorsky/comment-parser/issues/128 */if (postTag) {
postTag = '';
}
}
utils.emptyTokens(tokens);
utils.addLine(1, {
delimiter: '*',
// If a description were present, it may have whitespace attached
// due to being at the end of the single line
description: description.trimEnd(),
name,
postDelimiter,
postName,
postTag,
postType,
start: indent + ' ',
tag,
type
});
utils.addLine(2, {
end: '*/',
lineEnd,
start: indent + ' '
});
};
utils.flattenRoots = params => {
return _jsdocUtils.default.flattenRoots(params);
};
utils.getFunctionParameterNames = useDefaultObjectProperties => {
return _jsdocUtils.default.getFunctionParameterNames(node, useDefaultObjectProperties);
};
utils.hasParams = () => {
return _jsdocUtils.default.hasParams(node);
};
utils.isGenerator = () => {
return node && (node.generator || node.type === 'MethodDefinition' && node.value.generator || ['ExportNamedDeclaration', 'ExportDefaultDeclaration'].includes(node.type) && node.declaration.generator);
};
utils.isConstructor = () => {
return _jsdocUtils.default.isConstructor(node);
};
utils.getJsdocTagsDeep = tagName => {
const name = utils.getPreferredTagName({
tagName
});
if (!name) {
return false;
}
return _jsdocUtils.default.getJsdocTagsDeep(jsdoc, name);
};
utils.getPreferredTagName = ({
tagName,
skipReportingBlockedTag = false,
allowObjectReturn = false,
defaultMessage = `Unexpected tag \`@${tagName}\``
}) => {
const ret = _jsdocUtils.default.getPreferredTagName(context, mode, tagName, tagNamePreference);
const isObject = ret && typeof ret === 'object';
if (utils.hasTag(tagName) && (ret === false || isObject && !ret.replacement)) {
if (skipReportingBlockedTag) {
return {
blocked: true,
tagName
};
}
const message = isObject && ret.message || defaultMessage;
report(message, null, utils.getTags(tagName)[0]);
return false;
}
return isObject && !allowObjectReturn ? ret.replacement : ret;
};
utils.isValidTag = (name, definedTags) => {
return _jsdocUtils.default.isValidTag(context, mode, name, definedTags);
};
utils.hasATag = names => {
return _jsdocUtils.default.hasATag(jsdoc, names);
};
utils.hasTag = name => {
return _jsdocUtils.default.hasTag(jsdoc, name);
};
utils.comparePaths = name => {
return _jsdocUtils.default.comparePaths(name);
};
utils.dropPathSegmentQuotes = name => {
return _jsdocUtils.default.dropPathSegmentQuotes(name);
};
utils.avoidDocs = () => {
var _context$options$;
if (ignoreReplacesDocs !== false && (utils.hasTag('ignore') || utils.classHasTag('ignore')) || overrideReplacesDocs !== false && (utils.hasTag('override') || utils.classHasTag('override')) || implementsReplacesDocs !== false && (utils.hasTag('implements') || utils.classHasTag('implements')) || augmentsExtendsReplacesDocs && (utils.hasATag(['augments', 'extends']) || utils.classHasTag('augments') || utils.classHasTag('extends'))) {
return true;
}
if (_jsdocUtils.default.exemptSpeciaMethods(jsdoc, node, context, ruleConfig.meta.schema)) {
return true;
}
const exemptedBy = ((_context$options$ = context.options[0]) === null || _context$options$ === void 0 ? void 0 : _context$options$.exemptedBy) ?? ['inheritDoc', ...(mode === 'closure' ? [] : ['inheritdoc'])];
if (exemptedBy.length && utils.getPresentTags(exemptedBy).length) {
return true;
}
return false;
};
for (const method of ['tagMightHaveNamePosition', 'tagMightHaveTypePosition']) {
utils[method] = (tagName, otherModeMaps) => {
const result = _jsdocUtils.default[method](tagName);
if (result) {
return true;
}
if (!otherModeMaps) {
return false;
}
const otherResult = otherModeMaps.some(otherModeMap => {
return _jsdocUtils.default[method](tagName, otherModeMap);
});
return otherResult ? {
otherMode: true
} : false;
};
}
for (const method of ['tagMustHaveNamePosition', 'tagMustHaveTypePosition', 'tagMissingRequiredTypeOrNamepath']) {
utils[method] = (tagName, otherModeMaps) => {
const result = _jsdocUtils.default[method](tagName);
if (!result) {
return false;
}
// if (!otherModeMaps) { return true; }
const otherResult = otherModeMaps.every(otherModeMap => {
return _jsdocUtils.default[method](tagName, otherModeMap);
});
return otherResult ? true : {
otherMode: false
};
};
}
for (const method of ['isNamepathDefiningTag', 'tagMightHaveNamepath']) {
utils[method] = tagName => {
return _jsdocUtils.default[method](tagName);
};
}
utils.getTagStructureForMode = mde => {
return _jsdocUtils.default.getTagStructureForMode(mde, settings.structuredTags);
};
utils.hasDefinedTypeTag = tag => {
return _jsdocUtils.default.hasDefinedTypeTag(tag, settings.mode);
};
utils.hasValueOrExecutorHasNonEmptyResolveValue = (anyPromiseAsReturn, allBranches) => {
return _jsdocUtils.default.hasValueOrExecutorHasNonEmptyResolveValue(node, anyPromiseAsReturn, allBranches);
};
utils.hasYieldValue = () => {
if (['ExportNamedDeclaration', 'ExportDefaultDeclaration'].includes(node.type)) {
return _jsdocUtils.default.hasYieldValue(node.declaration);
}
return _jsdocUtils.default.hasYieldValue(node);
};
utils.hasYieldReturnValue = () => {
return _jsdocUtils.default.hasYieldValue(node, true);
};
utils.hasThrowValue = () => {
return _jsdocUtils.default.hasThrowValue(node);
};
utils.isAsync = () => {
return node.async;
};
utils.getTags = tagName => {
return utils.filterTags(item => {
return item.tag === tagName;
});
};
utils.getPresentTags = tagList => {
return utils.filterTags(tag => {
return tagList.includes(tag.tag);
});
};
utils.filterTags = filter => {
return _jsdocUtils.default.filterTags(jsdoc.tags, filter);
};
utils.getTagsByType = tags => {
return _jsdocUtils.default.getTagsByType(context, mode, tags, tagNamePreference);
};
utils.hasOptionTag = tagName => {
const {
tags
} = context.options[0] ?? {};
return Boolean(tags && tags.includes(tagName));
};
utils.getClassNode = () => {
return [...ancestors, node].reverse().find(parent => {
return parent && ['ClassDeclaration', 'ClassExpression'].includes(parent.type);
}) || null;
};
utils.getClassJsdoc = () => {
const classNode = utils.getClassNode();
if (!classNode) {
return null;
}
const classJsdocNode = (0, _jsdoccomment.getJSDocComment)(sourceCode, classNode, {
maxLines,
minLines
});
if (classJsdocNode) {
return (0, _jsdoccomment.parseComment)(classJsdocNode, '');
}
return null;
};
utils.classHasTag = tagName => {
const classJsdoc = utils.getClassJsdoc();
return Boolean(classJsdoc) && _jsdocUtils.default.hasTag(classJsdoc, tagName);
};
utils.forEachPreferredTag = (tagName, arrayHandler, skipReportingBlockedTag = false) => {
const targetTagName = utils.getPreferredTagName({
skipReportingBlockedTag,
tagName
});
if (!targetTagName || skipReportingBlockedTag && targetTagName && typeof targetTagName === 'object') {
return;
}
const matchingJsdocTags = jsdoc.tags.filter(({
tag
}) => {
return tag === targetTagName;
});
for (const matchingJsdocTag of matchingJsdocTags) {
arrayHandler(matchingJsdocTag, targetTagName);
}
};
return utils;
};
const getSettings = context => {
var _context$settings$jsd, _context$settings$jsd2, _context$settings$jsd3, _context$settings$jsd4, _context$settings$jsd5, _context$settings$jsd6, _context$settings$jsd7, _context$settings$jsd8, _context$settings$jsd9, _context$settings$jsd10, _context$settings$jsd11, _context$settings$jsd12, _context$settings$jsd13;
/* eslint-disable canonical/sort-keys */
const settings = {
// All rules
ignorePrivate: Boolean((_context$settings$jsd = context.settings.jsdoc) === null || _context$settings$jsd === void 0 ? void 0 : _context$settings$jsd.ignorePrivate),
ignoreInternal: Boolean((_context$settings$jsd2 = context.settings.jsdoc) === null || _context$settings$jsd2 === void 0 ? void 0 : _context$settings$jsd2.ignoreInternal),
maxLines: Number(((_context$settings$jsd3 = context.settings.jsdoc) === null || _context$settings$jsd3 === void 0 ? void 0 : _context$settings$jsd3.maxLines) ?? 1),
minLines: Number(((_context$settings$jsd4 = context.settings.jsdoc) === null || _context$settings$jsd4 === void 0 ? void 0 : _context$settings$jsd4.minLines) ?? 0),
// `check-tag-names` and many returns/param rules
tagNamePreference: ((_context$settings$jsd5 = context.settings.jsdoc) === null || _context$settings$jsd5 === void 0 ? void 0 : _context$settings$jsd5.tagNamePreference) ?? {},
// `check-types` and `no-undefined-types`
preferredTypes: ((_context$settings$jsd6 = context.settings.jsdoc) === null || _context$settings$jsd6 === void 0 ? void 0 : _context$settings$jsd6.preferredTypes) ?? {},
// `check-types`, `no-undefined-types`, `valid-types`
structuredTags: ((_context$settings$jsd7 = context.settings.jsdoc) === null || _context$settings$jsd7 === void 0 ? void 0 : _context$settings$jsd7.structuredTags) ?? {},
// `require-param`, `require-description`, `require-example`,
// `require-returns`, `require-throw`, `require-yields`
overrideReplacesDocs: (_context$settings$jsd8 = context.settings.jsdoc) === null || _context$settings$jsd8 === void 0 ? void 0 : _context$settings$jsd8.overrideReplacesDocs,
ignoreReplacesDocs: (_context$settings$jsd9 = context.settings.jsdoc) === null || _context$settings$jsd9 === void 0 ? void 0 : _context$settings$jsd9.ignoreReplacesDocs,
implementsReplacesDocs: (_context$settings$jsd10 = context.settings.jsdoc) === null || _context$settings$jsd10 === void 0 ? void 0 : _context$settings$jsd10.implementsReplacesDocs,
augmentsExtendsReplacesDocs: (_context$settings$jsd11 = context.settings.jsdoc) === null || _context$settings$jsd11 === void 0 ? void 0 : _context$settings$jsd11.augmentsExtendsReplacesDocs,
// `require-param-type`, `require-param-description`
exemptDestructuredRootsFromChecks: (_context$settings$jsd12 = context.settings.jsdoc) === null || _context$settings$jsd12 === void 0 ? void 0 : _context$settings$jsd12.exemptDestructuredRootsFromChecks,
// Many rules, e.g., `check-tag-names`
mode: ((_context$settings$jsd13 = context.settings.jsdoc) === null || _context$settings$jsd13 === void 0 ? void 0 : _context$settings$jsd13.mode) ?? (context.parserPath.includes('@typescript-eslint') ? 'typescript' : 'jsdoc')
};
/* eslint-enable canonical/sort-keys */
_jsdocUtils.default.setTagStructure(settings.mode);
try {
_jsdocUtils.default.overrideTagStructure(settings.structuredTags);
} catch (error) {
context.report({
loc: {
start: {
column: 1,
line: 1
}
},
message: error.message
});
return false;
}
return settings;
};
/**
* Create the report function
*
* @param {object} context
* @param {object} commentNode
*/
exports.getSettings = getSettings;
const makeReport = (context, commentNode) => {
const report = (message, fix = null, jsdocLoc = null, data = null) => {
let loc;
if (jsdocLoc) {
if (!('line' in jsdocLoc)) {
jsdocLoc.line = jsdocLoc.source[0].number;
}
const lineNumber = commentNode.loc.start.line + jsdocLoc.line;
loc = {
end: {
column: 0,
line: lineNumber
},
start: {
column: 0,
line: lineNumber
}
};
// Todo: Remove ignore once `check-examples` can be restored for ESLint 8+
// istanbul ignore if
if (jsdocLoc.column) {
const colNumber = commentNode.loc.start.column + jsdocLoc.column;
loc.end.column = colNumber;
loc.start.column = colNumber;
}
}
context.report({
data,
fix,
loc,
message,
node: commentNode
});
};
return report;
};
/* eslint-disable jsdoc/no-undefined-types -- canonical still using an older version where not defined */
/**
* @typedef {ReturnType<typeof getUtils>} Utils
* @typedef {ReturnType<typeof getSettings>} Settings
* @typedef {(
* arg: {
* context: object,
* sourceCode: object,
* indent: string,
* jsdoc: object,
* jsdocNode: object,
* node: Node | null,
* report: ReturnType<typeof makeReport>,
* settings: Settings,
* utils: Utils,
* }
* ) => any } JsdocVisitor
*/
/* eslint-enable jsdoc/no-undefined-types -- canonical still using an older version where not defined */
const iterate = (info, indent, jsdoc, ruleConfig, context, lines, jsdocNode, node, settings, sourceCode, iterator, state, iteratingAll) => {
const report = makeReport(context, jsdocNode);
const utils = getUtils(node, jsdoc, jsdocNode, settings, report, context, iteratingAll, ruleConfig, indent);
if (!ruleConfig.checkInternal && settings.ignoreInternal && utils.hasTag('internal')) {
return;
}
if (!ruleConfig.checkPrivate && settings.ignorePrivate && (utils.hasTag('private') || jsdoc.tags.filter(({
tag
}) => {
return tag === 'access';
}).some(({
description
}) => {
return description === 'private';
}))) {
return;
}
iterator({
context,
globalState,
indent,
info,
iteratingAll,
jsdoc,
jsdocNode,
node,
report,
settings,
sourceCode,
state,
utils
});
};
const getIndentAndJSDoc = function (lines, jsdocNode) {
const sourceLine = lines[jsdocNode.loc.start.line - 1];
const indnt = sourceLine.charAt(0).repeat(jsdocNode.loc.start.column);
const jsdc = (0, _jsdoccomment.parseComment)(jsdocNode, '');
return [indnt, jsdc];
};
/**
*
* @typedef {{node: Node, state: StateObject}} NonCommentArgs
*/
/**
* Our internal dynamic set of utilities.
*
* @todo Document
* @typedef {any} Utils
*/
/**
* @typedef {object} RuleConfig
* @property {EslintRuleMeta} meta ESLint rule meta
* @property {import('./jsdocUtils').DefaultContexts} [contextDefaults] Any default contexts
* @property {true} [contextSelected] Whether to force a `contexts` check
* @property {true} [iterateAllJsdocs] Whether to iterate all JSDoc blocks by default
* regardless of context
* @property {(context, state: StateObject, utils: Utils) => void} [exit] Handler to be executed
* upon exiting iteration of program AST
* @property {(NonCommentArgs) => void} [nonComment] Handler to be executed if rule wishes
* to be supplied nodes without comments
*/
/**
* Create an eslint rule that iterates over all JSDocs, regardless of whether
* they are attached to a function-like node.
*
* @param {JsdocVisitor} iterator
* @param {RuleConfig} ruleConfig The rule's configuration
* @param contexts The `contexts` containing relevant `comment` info.
* @param {boolean} additiveCommentContexts If true, will have a separate
* iteration for each matching comment context. Otherwise, will iterate
* once if there is a single matching comment context.
*/
const iterateAllJsdocs = (iterator, ruleConfig, contexts, additiveCommentContexts) => {
const trackedJsdocs = new Set();
let handler;
let settings;
const callIterator = (context, node, jsdocNodes, state, lastCall) => {
const sourceCode = context.getSourceCode();
const {
lines
} = sourceCode;
const utils = getBasicUtils(context, settings);
for (const jsdocNode of jsdocNodes) {
if (!/^\/\*\*\s/u.test(sourceCode.getText(jsdocNode))) {
continue;
}
const [indent, jsdoc] = getIndentAndJSDoc(lines, jsdocNode);
if (additiveCommentContexts) {
for (const [idx, {
comment
}] of contexts.entries()) {
if (comment && handler(comment, jsdoc) === false) {
continue;
}
iterate({
comment,
lastIndex: idx,
selector: node === null || node === void 0 ? void 0 : node.type
}, indent, jsdoc, ruleConfig, context, lines, jsdocNode, node, settings, sourceCode, iterator, state, true);
}
continue;
}
let lastComment;
let lastIndex;
// eslint-disable-next-line no-loop-func
if (contexts && contexts.every(({
comment
}, idx) => {
lastComment = comment;
lastIndex = idx;
return comment && handler(comment, jsdoc) === false;
})) {
continue;
}
iterate(lastComment ? {
comment: lastComment,
lastIndex,
selector: node === null || node === void 0 ? void 0 : node.type
} : {
lastIndex,
selector: node === null || node === void 0 ? void 0 : node.type
}, indent, jsdoc, ruleConfig, context, lines, jsdocNode, node, settings, sourceCode, iterator, state, true);
}
if (lastCall && ruleConfig.exit) {
ruleConfig.exit({
context,
state,
utils
});
}
};
return {
create(context) {
const sourceCode = context.getSourceCode();
settings = getSettings(context);
if (!settings) {
return {};
}
if (contexts) {
handler = (0, _jsdoccomment.commentHandler)(settings);
}
const state = {};
return {
'*:not(Program)'(node) {
const commentNode = (0, _jsdoccomment.getJSDocComment)(sourceCode, node, settings);
if (!ruleConfig.noTracking && trackedJsdocs.has(commentNode)) {
return;
}
if (!commentNode) {
if (ruleConfig.nonComment) {
ruleConfig.nonComment({
node,
state
});
}
return;
}
trackedJsdocs.add(commentNode);
callIterator(context, node, [commentNode], state);
},
'Program:exit'() {
const allComments = sourceCode.getAllComments();
const untrackedJSdoc = allComments.filter(node => {
return !trackedJsdocs.has(node);
});
callIterator(context, null, untrackedJSdoc, state, true);
}
};
},
meta: ruleConfig.meta
};
};
/**
* Create an eslint rule that iterates over all JSDocs, regardless of whether
* they are attached to a function-like node.
*
* @param {JsdocVisitor} iterator
* @param {RuleConfig} ruleConfig
*/
const checkFile = (iterator, ruleConfig) => {
return {
create(context) {
const sourceCode = context.getSourceCode();
const settings = getSettings(context);
if (!settings) {
return {};
}
return {
'Program:exit'() {
const allComments = sourceCode.getAllComments();
const {
lines
} = sourceCode;
const utils = getBasicUtils(context, settings);
iterator({
allComments,
context,
lines,
makeReport,
settings,
sourceCode,
utils
});
}
};
},
meta: ruleConfig.meta
};
};
/**
* @param {JsdocVisitor} iterator
* @param {RuleConfig} ruleConfig
*/
function iterateJsdoc(iterator, ruleConfig) {
var _ruleConfig$meta;
const metaType = ruleConfig === null || ruleConfig === void 0 ? void 0 : (_ruleConfig$meta = ruleConfig.meta) === null || _ruleConfig$meta === void 0 ? void 0 : _ruleConfig$meta.type;
if (!metaType || !['problem', 'suggestion', 'layout'].includes(metaType)) {
throw new TypeError('Rule must include `meta.type` option (with value "problem", "suggestion", or "layout")');
}
if (typeof iterator !== 'function') {
throw new TypeError('The iterator argument must be a function.');
}
if (ruleConfig.checkFile) {
return checkFile(iterator, ruleConfig);
}
if (ruleConfig.iterateAllJsdocs) {
return iterateAllJsdocs(iterator, ruleConfig);
}
return {
/**
* The entrypoint for the JSDoc rule.
*
* @param {*} context
* a reference to the context which hold all important information
* like settings and the sourcecode to check.
* @returns {object}
* a list with parser callback function.
*/
create(context) {
const settings = getSettings(context);
if (!settings) {
return {};
}
let contexts;
if (ruleConfig.contextDefaults || ruleConfig.contextSelected || ruleConfig.matchContext) {
var _context$options$2, _contexts, _contexts2;
contexts = ruleConfig.matchContext && (_context$options$2 = context.options[0]) !== null && _context$options$2 !== void 0 && _context$options$2.match ? context.options[0].match : _jsdocUtils.default.enforcedContexts(context, ruleConfig.contextDefaults);
if (contexts) {
contexts = contexts.map(obj => {
if (typeof obj === 'object' && !obj.context) {
return {
...obj,
context: 'any'
};
}
return obj;
});
}
const hasPlainAny = (_contexts = contexts) === null || _contexts === void 0 ? void 0 : _contexts.includes('any');
const hasObjectAny = !hasPlainAny && ((_contexts2 = contexts) === null || _contexts2 === void 0 ? void 0 : _contexts2.find(ctxt => {
return (ctxt === null || ctxt === void 0 ? void 0 : ctxt.context) === 'any';
}));
if (hasPlainAny || hasObjectAny) {
return iterateAllJsdocs(iterator, ruleConfig, hasObjectAny ? contexts : null, ruleConfig.matchContext).create(context);
}
}
const sourceCode = context.getSourceCode();
const {
lines
} = sourceCode;
const state = {};
const checkJsdoc = (info, handler, node) => {
const jsdocNode = (0, _jsdoccomment.getJSDocComment)(sourceCode, node, settings);
if (!jsdocNode) {
return;
}
const [indent, jsdoc] = getIndentAndJSDoc(lines, jsdocNode);
if (
// Note, `handler` should already be bound in its first argument
// with these only to be called after the value of
// `comment`
handler && handler(jsdoc) === false) {
return;
}
iterate(info, indent, jsdoc, ruleConfig, context, lines, jsdocNode, node, settings, sourceCode, iterator, state);
};
let contextObject = {};
if (contexts && (ruleConfig.contextDefaults || ruleConfig.contextSelected || ruleConfig.matchContext)) {
contextObject = _jsdocUtils.default.getContextObject(contexts, checkJsdoc, (0, _jsdoccomment.commentHandler)(settings));
} else {
for (const prop of ['ArrowFunctionExpression', 'FunctionDeclaration', 'FunctionExpression', 'TSDeclareFunction']) {
contextObject[prop] = checkJsdoc.bind(null, {
selector: prop
}, null);
}
}
if (ruleConfig.exit) {
contextObject['Program:exit'] = () => {
ruleConfig.exit({
context,
state
});
};
}
return contextObject;
},
meta: ruleConfig.meta
};
}
//# sourceMappingURL=iterateJsdoc.js.map