UNPKG

eslint-plugin-jsdoc

Version:
212 lines (205 loc) 6.17 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _iterateJsdoc = _interopRequireDefault(require("../iterateJsdoc.cjs")); var _jsdoccomment = require("@es-joy/jsdoccomment"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } var _default = exports.default = (0, _iterateJsdoc.default)(({ context, node, report, settings, utils }) => { if (utils.avoidDocs()) { return; } const { requireSeparateTemplates = false } = context.options[0] || {}; const { mode } = settings; const usedNames = new Set(); const tgName = /** @type {string} */utils.getPreferredTagName({ tagName: 'template' }); if (!tgName) { return; } const templateTags = utils.getTags(tgName); const templateNames = templateTags.flatMap(tag => { return utils.parseClosureTemplateTag(tag); }); if (requireSeparateTemplates) { for (const tag of templateTags) { const names = utils.parseClosureTemplateTag(tag); if (names.length > 1) { report(`Missing separate @${tgName} for ${names[1]}`, null, tag); } } } /** * @param {import('@typescript-eslint/types').TSESTree.FunctionDeclaration| * import('@typescript-eslint/types').TSESTree.ClassDeclaration| * import('@typescript-eslint/types').TSESTree.TSDeclareFunction| * import('@typescript-eslint/types').TSESTree.TSInterfaceDeclaration| * import('@typescript-eslint/types').TSESTree.TSTypeAliasDeclaration} aliasDeclaration */ const checkTypeParams = aliasDeclaration => { const { params /* c8 ignore next -- Guard */ } = aliasDeclaration.typeParameters ?? { /* c8 ignore next -- Guard */ params: [] }; for (const { name: { name } } of params) { usedNames.add(name); } for (const usedName of usedNames) { if (!templateNames.includes(usedName)) { report(`Missing @${tgName} ${usedName}`); } } }; const handleTypes = () => { const nde = /** @type {import('@typescript-eslint/types').TSESTree.Node} */ node; if (!nde) { return; } switch (nde.type) { case 'ClassDeclaration': case 'FunctionDeclaration': case 'TSDeclareFunction': case 'TSInterfaceDeclaration': case 'TSTypeAliasDeclaration': checkTypeParams(nde); break; case 'ExportDefaultDeclaration': switch (nde.declaration?.type) { case 'ClassDeclaration': case 'FunctionDeclaration': case 'TSInterfaceDeclaration': checkTypeParams(nde.declaration); break; } break; case 'ExportNamedDeclaration': switch (nde.declaration?.type) { case 'ClassDeclaration': case 'FunctionDeclaration': case 'TSDeclareFunction': case 'TSInterfaceDeclaration': case 'TSTypeAliasDeclaration': checkTypeParams(nde.declaration); break; } break; } }; const usedNameToTag = new Map(); /** * @param {import('comment-parser').Spec} potentialTag */ const checkForUsedTypes = potentialTag => { let parsedType; try { parsedType = mode === 'permissive' ? (0, _jsdoccomment.tryParse)(/** @type {string} */potentialTag.type) : (0, _jsdoccomment.parse)(/** @type {string} */potentialTag.type, mode); } catch { return; } (0, _jsdoccomment.traverse)(parsedType, nde => { const { type, value } = /** @type {import('jsdoc-type-pratt-parser').NameResult} */nde; if (type === 'JsdocTypeName' && /^[A-Z]$/v.test(value)) { usedNames.add(value); if (!usedNameToTag.has(value)) { usedNameToTag.set(value, potentialTag); } } }); }; /** * @param {string[]} tagNames */ const checkTagsAndTemplates = tagNames => { for (const tagName of tagNames) { const preferredTagName = /** @type {string} */utils.getPreferredTagName({ tagName }); const matchingTags = utils.getTags(preferredTagName); for (const matchingTag of matchingTags) { checkForUsedTypes(matchingTag); } } // Could check against whitelist/blacklist for (const usedName of usedNames) { if (!templateNames.includes(usedName)) { report(`Missing @${tgName} ${usedName}`, null, usedNameToTag.get(usedName)); } } }; const callbackTags = utils.getTags('callback'); const functionTags = utils.getTags('function'); if (callbackTags.length || functionTags.length) { checkTagsAndTemplates(['param', 'returns']); return; } const typedefTags = utils.getTags('typedef'); if (!typedefTags.length || typedefTags.length >= 2) { handleTypes(); return; } const potentialTypedef = typedefTags[0]; checkForUsedTypes(potentialTypedef); checkTagsAndTemplates(['property']); }, { iterateAllJsdocs: true, meta: { docs: { description: 'Requires `@template` tags be present when type parameters are used.', url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-template.md#repos-sticky-header' }, schema: [{ additionalProperties: false, properties: { exemptedBy: { description: `Array of tags (e.g., \`['type']\`) whose presence on the document block avoids the need for a \`@template\`. Defaults to an array with \`inheritdoc\`. If you set this array, it will overwrite the default, so be sure to add back \`inheritdoc\` if you wish its presence to cause exemption of the rule.`, items: { type: 'string' }, type: 'array' }, requireSeparateTemplates: { description: `Requires that each template have its own separate line, i.e., preventing templates of this format: \`\`\`js /** * @template T, U, V */ \`\`\` Defaults to \`false\`.`, type: 'boolean' } }, type: 'object' }], type: 'suggestion' } }); module.exports = exports.default; //# sourceMappingURL=requireTemplate.cjs.map