| "use strict"; |
| |
| Object.defineProperty(exports, "__esModule", { |
| value: true |
| }); |
| exports.hasParams = exports.hasATag = exports.getTagsByType = exports.getTags = exports.getTagStructureForMode = exports.getTagDescription = exports.getRegexFromString = exports.getPreferredTagNameSimple = exports.getPreferredTagName = exports.getJsdocTagsDeep = exports.getInlineTags = exports.getIndent = exports.getFunctionParameterNames = exports.getContextObject = exports.getAllTags = exports.forEachPreferredTag = exports.flattenRoots = exports.filterTags = exports.exemptSpeciaMethods = exports.enforcedContexts = exports.dropPathSegmentQuotes = exports.comparePaths = void 0; |
| Object.defineProperty(exports, "hasReturnValue", { |
| enumerable: true, |
| get: function () { |
| return _hasReturnValue.hasReturnValue; |
| } |
| }); |
| exports.hasThrowValue = exports.hasTag = void 0; |
| Object.defineProperty(exports, "hasValueOrExecutorHasNonEmptyResolveValue", { |
| enumerable: true, |
| get: function () { |
| return _hasReturnValue.hasValueOrExecutorHasNonEmptyResolveValue; |
| } |
| }); |
| exports.tagMustHaveTypePosition = exports.tagMustHaveNamePosition = exports.tagMissingRequiredTypeOrNamepath = exports.tagMightHaveTypePosition = exports.tagMightHaveNamepath = exports.tagMightHaveNamePosition = exports.tagMightHaveNameOrNamepath = exports.tagMightHaveName = exports.tagMightHaveEitherTypeOrNamePosition = exports.strictNativeTypes = exports.setTagStructure = exports.rewireByParsedType = exports.pathDoesNotBeginWith = exports.parseClosureTemplateTag = exports.overrideTagStructure = exports.mayBeUndefinedTypeTag = exports.isValidTag = exports.isSetter = exports.isNamepathReferencingTag = exports.isNamepathOrUrlReferencingTag = exports.isNameOrNamepathDefiningTag = exports.isGetter = exports.isConstructor = exports.hasYieldValue = void 0; |
| var _getDefaultTagStructureForMode = _interopRequireDefault(require("./getDefaultTagStructureForMode.cjs")); |
| var _tagNames = require("./tagNames.cjs"); |
| var _WarnSettings = _interopRequireDefault(require("./WarnSettings.cjs")); |
| var _jsdoccomment = require("@es-joy/jsdoccomment"); |
| var _hasReturnValue = require("./utils/hasReturnValue.cjs"); |
| function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } |
| /** |
| * @typedef {number} Integer |
| */ |
| /** |
| * @typedef {import('./utils/hasReturnValue.js').ESTreeOrTypeScriptNode} ESTreeOrTypeScriptNode |
| */ |
| |
| /** |
| * @typedef {"jsdoc"|"typescript"|"closure"|"permissive"} ParserMode |
| */ |
| |
| /** |
| * @type {import('./getDefaultTagStructureForMode.js').TagStructure} |
| */ |
| let tagStructure; |
| |
| /** |
| * @param {ParserMode} mode |
| * @returns {void} |
| */ |
| const setTagStructure = mode => { |
| tagStructure = (0, _getDefaultTagStructureForMode.default)(mode); |
| }; |
| |
| /** |
| * @typedef {undefined|string|{ |
| * name: Integer, |
| * restElement: boolean |
| * }|{ |
| * isRestProperty: boolean|undefined, |
| * name: string, |
| * restElement: boolean |
| * }|{ |
| * name: string, |
| * restElement: boolean |
| * }} ParamCommon |
| */ |
| /** |
| * @typedef {ParamCommon|[string|undefined, (FlattendRootInfo & { |
| * annotationParamName?: string, |
| * })]|NestedParamInfo} ParamNameInfo |
| */ |
| |
| /** |
| * @typedef {{ |
| * hasPropertyRest: boolean, |
| * hasRestElement: boolean, |
| * names: string[], |
| * rests: boolean[], |
| * }} FlattendRootInfo |
| */ |
| /** |
| * @typedef {[string, (string[]|ParamInfo[])]} NestedParamInfo |
| */ |
| /** |
| * @typedef {ParamCommon| |
| * [string|undefined, (FlattendRootInfo & { |
| * annotationParamName?: string |
| * })]| |
| * NestedParamInfo} ParamInfo |
| */ |
| |
| /** |
| * Given a nested array of property names, reduce them to a single array, |
| * appending the name of the root element along the way if present. |
| * @callback FlattenRoots |
| * @param {ParamInfo[]} params |
| * @param {string} [root] |
| * @returns {FlattendRootInfo} |
| */ |
| |
| /** @type {FlattenRoots} */ |
| exports.setTagStructure = setTagStructure; |
| const flattenRoots = (params, root = '') => { |
| let hasRestElement = false; |
| let hasPropertyRest = false; |
| |
| /** |
| * @type {boolean[]} |
| */ |
| const rests = []; |
| const names = params.reduce( |
| /** |
| * @param {string[]} acc |
| * @param {ParamInfo} cur |
| * @returns {string[]} |
| */ |
| (acc, cur) => { |
| if (Array.isArray(cur)) { |
| let nms; |
| if (Array.isArray(cur[1])) { |
| nms = cur[1]; |
| } else { |
| if (cur[1].hasRestElement) { |
| hasRestElement = true; |
| } |
| if (cur[1].hasPropertyRest) { |
| hasPropertyRest = true; |
| } |
| nms = cur[1].names; |
| } |
| const flattened = flattenRoots(nms, root ? `${root}.${cur[0]}` : cur[0]); |
| if (flattened.hasRestElement) { |
| hasRestElement = true; |
| } |
| if (flattened.hasPropertyRest) { |
| hasPropertyRest = true; |
| } |
| const inner = /** @type {string[]} */[root ? `${root}.${cur[0]}` : cur[0], ...flattened.names].filter(Boolean); |
| rests.push(false, ...flattened.rests); |
| return acc.concat(inner); |
| } |
| if (typeof cur === 'object') { |
| if ('isRestProperty' in cur && cur.isRestProperty) { |
| hasPropertyRest = true; |
| rests.push(true); |
| } else { |
| rests.push(false); |
| } |
| if ('restElement' in cur && cur.restElement) { |
| hasRestElement = true; |
| } |
| acc.push(root ? `${root}.${String(cur.name)}` : String(cur.name)); |
| } else if (typeof cur !== 'undefined') { |
| rests.push(false); |
| acc.push(root ? `${root}.${cur}` : cur); |
| } |
| return acc; |
| }, []); |
| return { |
| hasPropertyRest, |
| hasRestElement, |
| names, |
| rests |
| }; |
| }; |
| |
| /** |
| * @param {import('@typescript-eslint/types').TSESTree.TSIndexSignature| |
| * import('@typescript-eslint/types').TSESTree.TSConstructSignatureDeclaration| |
| * import('@typescript-eslint/types').TSESTree.TSCallSignatureDeclaration| |
| * import('@typescript-eslint/types').TSESTree.TSPropertySignature} propSignature |
| * @returns {undefined|string|[string, string[]]} |
| */ |
| exports.flattenRoots = flattenRoots; |
| const getPropertiesFromPropertySignature = propSignature => { |
| if (propSignature.type === 'TSIndexSignature' || propSignature.type === 'TSConstructSignatureDeclaration' || propSignature.type === 'TSCallSignatureDeclaration') { |
| return undefined; |
| } |
| if (propSignature.typeAnnotation && propSignature.typeAnnotation.typeAnnotation.type === 'TSTypeLiteral') { |
| return [/** @type {import('@typescript-eslint/types').TSESTree.Identifier} */propSignature.key.name, propSignature.typeAnnotation.typeAnnotation.members.map(member => { |
| return /** @type {string} */getPropertiesFromPropertySignature(/** @type {import('@typescript-eslint/types').TSESTree.TSPropertySignature} */ |
| member); |
| })]; |
| } |
| return /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */propSignature.key.name; |
| }; |
| |
| /** |
| * @param {ESTreeOrTypeScriptNode|null} functionNode |
| * @param {boolean} [checkDefaultObjects] |
| * @param {boolean} [ignoreInterfacedParameters] |
| * @throws {Error} |
| * @returns {ParamNameInfo[]} |
| */ |
| const getFunctionParameterNames = (functionNode, checkDefaultObjects, ignoreInterfacedParameters) => { |
| /* eslint-disable complexity -- Temporary */ |
| /** |
| * @param {import('estree').Identifier|import('estree').AssignmentPattern| |
| * import('estree').ObjectPattern|import('estree').Property| |
| * import('estree').RestElement|import('estree').ArrayPattern| |
| * import('@typescript-eslint/types').TSESTree.TSParameterProperty| |
| * import('@typescript-eslint/types').TSESTree.Property| |
| * import('@typescript-eslint/types').TSESTree.RestElement| |
| * import('@typescript-eslint/types').TSESTree.Identifier| |
| * import('@typescript-eslint/types').TSESTree.ObjectPattern| |
| * import('@typescript-eslint/types').TSESTree.BindingName| |
| * import('@typescript-eslint/types').TSESTree.Parameter |
| * } param |
| * @param {boolean} [isProperty] |
| * @returns {ParamNameInfo|[string, ParamNameInfo[]]} |
| */ |
| const getParamName = (param, isProperty) => { |
| /* eslint-enable complexity -- Temporary */ |
| const hasLeftTypeAnnotation = 'left' in param && 'typeAnnotation' in param.left; |
| if ('typeAnnotation' in param || hasLeftTypeAnnotation) { |
| if (ignoreInterfacedParameters && 'typeAnnotation' in param && param.typeAnnotation) { |
| // No-op |
| return [undefined, { |
| hasPropertyRest: false, |
| hasRestElement: false, |
| names: [], |
| rests: [] |
| }]; |
| } |
| const typeAnnotation = hasLeftTypeAnnotation ? /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */param.left.typeAnnotation : /** @type {import('@typescript-eslint/types').TSESTree.Identifier|import('@typescript-eslint/types').TSESTree.ObjectPattern} */ |
| param.typeAnnotation; |
| if (typeAnnotation?.typeAnnotation?.type === 'TSTypeLiteral') { |
| const propertyNames = typeAnnotation.typeAnnotation.members.map(member => { |
| return getPropertiesFromPropertySignature(/** @type {import('@typescript-eslint/types').TSESTree.TSPropertySignature} */ |
| member); |
| }); |
| const flattened = { |
| ...flattenRoots(propertyNames), |
| annotationParamName: 'name' in param ? param.name : undefined |
| }; |
| const hasLeftName = 'left' in param && 'name' in param.left; |
| if ('name' in param || hasLeftName) { |
| return [hasLeftName ? /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */param.left.name : /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */param.name, flattened]; |
| } |
| return [undefined, flattened]; |
| } |
| } |
| if ('name' in param) { |
| return param.name; |
| } |
| if ('left' in param && 'name' in param.left) { |
| return param.left.name; |
| } |
| if (param.type === 'ObjectPattern' || 'left' in param && param.left.type === 'ObjectPattern') { |
| const properties = /** @type {import('@typescript-eslint/types').TSESTree.ObjectPattern} */param.properties || /** @type {import('estree').ObjectPattern} */ |
| (/** @type {import('@typescript-eslint/types').TSESTree.AssignmentPattern} */param.left)?.properties; |
| const roots = properties.map(prop => { |
| return getParamName(prop, true); |
| }); |
| return [undefined, flattenRoots(roots)]; |
| } |
| if (param.type === 'Property') { |
| switch (param.value.type) { |
| case 'ArrayPattern': |
| { |
| return [/** @type {import('estree').Identifier} */ |
| param.key.name, /** @type {import('estree').ArrayPattern} */param.value.elements.map((prop, idx) => { |
| return { |
| name: idx, |
| restElement: prop?.type === 'RestElement' |
| }; |
| })]; |
| } |
| case 'ObjectPattern': |
| { |
| return [/** @type {import('estree').Identifier} */param.key.name, /** @type {import('estree').ObjectPattern} */param.value.properties.map(prop => { |
| return /** @type {string|[string, string[]]} */getParamName(prop, isProperty); |
| })]; |
| } |
| case 'AssignmentPattern': |
| { |
| switch (param.value.left.type) { |
| case 'ArrayPattern': |
| return [/** @type {import('estree').Identifier} */ |
| param.key.name, /** @type {import('estree').ArrayPattern} */param.value.left.elements.map((prop, idx) => { |
| return { |
| name: idx, |
| restElement: prop?.type === 'RestElement' |
| }; |
| })]; |
| case 'Identifier': |
| // Default parameter |
| if (checkDefaultObjects && param.value.right.type === 'ObjectExpression') { |
| return [/** @type {import('estree').Identifier} */param.key.name, /** @type {import('estree').AssignmentPattern} */param.value.right.properties.map(prop => { |
| return /** @type {string} */getParamName(/** @type {import('estree').Property} */ |
| prop, isProperty); |
| })]; |
| } |
| break; |
| case 'ObjectPattern': |
| return [/** @type {import('estree').Identifier} */ |
| param.key.name, /** @type {import('estree').ObjectPattern} */param.value.left.properties.map(prop => { |
| return getParamName(prop, isProperty); |
| })]; |
| } |
| } |
| } |
| switch (param.key.type) { |
| case 'Identifier': |
| return param.key.name; |
| |
| // The key of an object could also be a string or number |
| case 'Literal': |
| /* c8 ignore next 2 -- `raw` may not be present in all parsers */ |
| return /** @type {string} */param.key.raw || param.key.value; |
| |
| // case 'MemberExpression': |
| default: |
| // Todo: We should really create a structure (and a corresponding |
| // option analogous to `checkRestProperty`) which allows for |
| // (and optionally requires) dynamic properties to have a single |
| // line of documentation |
| return undefined; |
| } |
| } |
| if (param.type === 'ArrayPattern' || /** @type {import('estree').AssignmentPattern} */param.left?.type === 'ArrayPattern') { |
| const elements = /** @type {import('estree').ArrayPattern} */param.elements || /** @type {import('estree').ArrayPattern} */(/** @type {import('estree').AssignmentPattern} */param.left)?.elements; |
| const roots = elements.map((prop, idx) => { |
| return { |
| name: `"${idx}"`, |
| restElement: prop?.type === 'RestElement' |
| }; |
| }); |
| return [undefined, flattenRoots(roots)]; |
| } |
| if (['ExperimentalRestProperty', 'RestElement'].includes(param.type)) { |
| return { |
| isRestProperty: isProperty, |
| name: /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */(/** @type {import('@typescript-eslint/types').TSESTree.RestElement} */param |
| // @ts-expect-error Ok |
| .argument).name ?? param?.argument?.elements?.map(({ |
| // @ts-expect-error Ok |
| name |
| }) => { |
| return name; |
| }), |
| restElement: true |
| }; |
| } |
| if (param.type === 'TSParameterProperty') { |
| return getParamName(/** @type {import('@typescript-eslint/types').TSESTree.Identifier} */ |
| /** @type {import('@typescript-eslint/types').TSESTree.TSParameterProperty} */param.parameter, true); |
| } |
| throw new Error(`Unsupported function signature format: \`${param.type}\`.`); |
| }; |
| if (!functionNode) { |
| return []; |
| } |
| return (/** @type {import('@typescript-eslint/types').TSESTree.FunctionDeclaration} */functionNode.params || /** @type {import('@typescript-eslint/types').TSESTree.MethodDefinition} */functionNode.value?.params || []).map(param => { |
| return getParamName(param); |
| }); |
| }; |
| |
| /** |
| * @param {ESTreeOrTypeScriptNode} functionNode |
| * @returns {Integer} |
| */ |
| exports.getFunctionParameterNames = getFunctionParameterNames; |
| const hasParams = functionNode => { |
| // Should also check `functionNode.value.params` if supporting `MethodDefinition` |
| return /** @type {import('@typescript-eslint/types').TSESTree.FunctionDeclaration} */functionNode.params.length; |
| }; |
| |
| /** |
| * Gets all names of the target type, including those that refer to a path, e.g. |
| * `foo` or `foo.bar`. |
| * @param {import('comment-parser').Block} jsdoc |
| * @param {string} targetTagName |
| * @returns {{ |
| * idx: Integer, |
| * name: string, |
| * type: string |
| * }[]} |
| */ |
| exports.hasParams = hasParams; |
| const getJsdocTagsDeep = (jsdoc, targetTagName) => { |
| const ret = []; |
| for (const [idx, { |
| name, |
| tag, |
| type |
| }] of jsdoc.tags.entries()) { |
| if (tag !== targetTagName) { |
| continue; |
| } |
| ret.push({ |
| idx, |
| name, |
| type |
| }); |
| } |
| return ret; |
| }; |
| exports.getJsdocTagsDeep = getJsdocTagsDeep; |
| const modeWarnSettings = (0, _WarnSettings.default)(); |
| |
| /** |
| * @param {ParserMode|undefined} mode |
| * @param {import('eslint').Rule.RuleContext} context |
| * @returns {import('./tagNames.js').AliasedTags} |
| */ |
| const getTagNamesForMode = (mode, context) => { |
| switch (mode) { |
| case 'closure': |
| case 'permissive': |
| return _tagNames.closureTags; |
| case 'jsdoc': |
| return _tagNames.jsdocTags; |
| case 'typescript': |
| return _tagNames.typeScriptTags; |
| default: |
| if (!modeWarnSettings.hasBeenWarned(context, 'mode')) { |
| context.report({ |
| loc: { |
| end: { |
| column: 1, |
| line: 1 |
| }, |
| start: { |
| column: 1, |
| line: 1 |
| } |
| }, |
| message: `Unrecognized value \`${mode}\` for \`settings.jsdoc.mode\`.` |
| }); |
| modeWarnSettings.markSettingAsWarned(context, 'mode'); |
| } |
| |
| // We'll avoid breaking too many other rules |
| return _tagNames.jsdocTags; |
| } |
| }; |
| |
| /** |
| * @param {import('comment-parser').Spec} tg |
| * @param {boolean} [returnArray] |
| * @returns {string[]|string} |
| */ |
| const getTagDescription = (tg, returnArray) => { |
| /** |
| * @type {string[]} |
| */ |
| const descriptions = []; |
| tg.source.some(({ |
| tokens: { |
| description, |
| end, |
| lineEnd, |
| name, |
| postDelimiter, |
| postTag, |
| tag, |
| type |
| } |
| }) => { |
| 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'); |
| }; |
| |
| /** |
| * @typedef {{ |
| * report: (descriptor: import('eslint').Rule.ReportDescriptor) => void |
| * }} Reporter |
| */ |
| |
| /** |
| * @param {string} name |
| * @param {ParserMode|undefined} mode |
| * @param {TagNamePreference} tagPreference |
| * @param {import('eslint').Rule.RuleContext} context |
| * @returns {string|false|{ |
| * message: string; |
| * replacement?: string|undefined; |
| * }} |
| */ |
| exports.getTagDescription = getTagDescription; |
| const getPreferredTagNameSimple = (name, mode, tagPreference = {}, |
| // @ts-expect-error Just a no-op |
| // eslint-disable-next-line unicorn/no-object-as-default-parameter -- Ok |
| context = { |
| report() { |
| // No-op |
| } |
| }) => { |
| const prefValues = Object.values(tagPreference); |
| if (prefValues.includes(name) || prefValues.some(prefVal => { |
| return prefVal && typeof prefVal === 'object' && prefVal.replacement === name; |
| })) { |
| return name; |
| } |
| |
| // Allow keys to have a 'tag ' prefix to avoid upstream bug in ESLint |
| // that disallows keys that conflict with Object.prototype, |
| // e.g. 'tag constructor' for 'constructor': |
| // https://github.com/eslint/eslint/issues/13289 |
| // https://github.com/gajus/eslint-plugin-jsdoc/issues/537 |
| const tagPreferenceFixed = Object.fromEntries(Object.entries(tagPreference).map(([key, value]) => { |
| return [key.replace(/^tag /v, ''), value]; |
| })); |
| if (Object.hasOwn(tagPreferenceFixed, name)) { |
| return tagPreferenceFixed[name]; |
| } |
| const tagNames = getTagNamesForMode(mode, context); |
| const preferredTagName = Object.entries(tagNames).find(([, aliases]) => { |
| return aliases.includes(name); |
| })?.[0]; |
| if (preferredTagName) { |
| return preferredTagName; |
| } |
| return name; |
| }; |
| |
| /** |
| * @param {import('eslint').Rule.RuleContext} context |
| * @param {ParserMode|undefined} mode |
| * @param {string} name |
| * @param {string[]} definedTags |
| * @returns {boolean} |
| */ |
| exports.getPreferredTagNameSimple = getPreferredTagNameSimple; |
| const isValidTag = (context, mode, name, definedTags) => { |
| const tagNames = getTagNamesForMode(mode, context); |
| const validTagNames = Object.keys(tagNames).concat(Object.values(tagNames).flat()); |
| const additionalTags = definedTags; |
| const allTags = validTagNames.concat(additionalTags); |
| return allTags.includes(name); |
| }; |
| |
| /** |
| * @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc |
| * @param {string} targetTagName |
| * @returns {boolean} |
| */ |
| exports.isValidTag = isValidTag; |
| const hasTag = (jsdoc, targetTagName) => { |
| const targetTagLower = targetTagName.toLowerCase(); |
| return jsdoc.tags.some(doc => { |
| return doc.tag.toLowerCase() === targetTagLower; |
| }); |
| }; |
| |
| /** |
| * @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc |
| * @param {(tag: import('@es-joy/jsdoccomment').JsdocTagWithInline) => boolean} filter |
| * @returns {import('@es-joy/jsdoccomment').JsdocTagWithInline[]} |
| */ |
| exports.hasTag = hasTag; |
| const filterTags = (jsdoc, filter) => { |
| return jsdoc.tags.filter(tag => { |
| return filter(tag); |
| }); |
| }; |
| |
| /** |
| * @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc |
| * @param {string} tagName |
| * @returns {import('comment-parser').Spec[]} |
| */ |
| exports.filterTags = filterTags; |
| const getTags = (jsdoc, tagName) => { |
| return filterTags(jsdoc, item => { |
| return item.tag === tagName; |
| }); |
| }; |
| |
| /** |
| * @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc |
| * @param {{ |
| * tagName: string, |
| * context?: import('eslint').Rule.RuleContext, |
| * mode?: ParserMode, |
| * report?: import('./iterateJsdoc.js').Report |
| * tagNamePreference?: TagNamePreference |
| * skipReportingBlockedTag?: boolean, |
| * allowObjectReturn?: boolean, |
| * defaultMessage?: string, |
| * }} cfg |
| * @returns {string|undefined|false|{ |
| * message: string; |
| * replacement?: string|undefined; |
| * }|{ |
| * blocked: true, |
| * tagName: string |
| * }} |
| */ |
| exports.getTags = getTags; |
| const getPreferredTagName = (jsdoc, { |
| allowObjectReturn = false, |
| context, |
| tagName, |
| defaultMessage = `Unexpected tag \`@${tagName}\``, |
| mode, |
| report = () => {}, |
| skipReportingBlockedTag = false, |
| tagNamePreference |
| }) => { |
| const ret = getPreferredTagNameSimple(tagName, mode, tagNamePreference, context); |
| const isObject = ret && typeof ret === 'object'; |
| if (hasTag(jsdoc, tagName) && (ret === false || isObject && !ret.replacement)) { |
| if (skipReportingBlockedTag) { |
| return { |
| blocked: true, |
| tagName |
| }; |
| } |
| const message = isObject && ret.message || defaultMessage; |
| report(message, null, getTags(jsdoc, tagName)[0]); |
| return false; |
| } |
| return isObject && !allowObjectReturn ? ret.replacement : ret; |
| }; |
| |
| /** |
| * @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc |
| * @param {string} tagName |
| * @param {( |
| * matchingJsdocTag: import('@es-joy/jsdoccomment').JsdocTagWithInline, |
| * targetTagName: string |
| * ) => void} arrayHandler |
| * @param {object} cfg |
| * @param {import('eslint').Rule.RuleContext} [cfg.context] |
| * @param {ParserMode} [cfg.mode] |
| * @param {import('./iterateJsdoc.js').Report} [cfg.report] |
| * @param {TagNamePreference} [cfg.tagNamePreference] |
| * @param {boolean} [cfg.skipReportingBlockedTag] |
| * @returns {void} |
| */ |
| exports.getPreferredTagName = getPreferredTagName; |
| const forEachPreferredTag = (jsdoc, tagName, arrayHandler, { |
| context, |
| mode, |
| report, |
| skipReportingBlockedTag = false, |
| tagNamePreference |
| } = {}) => { |
| const targetTagName = /** @type {string|false} */ |
| getPreferredTagName(jsdoc, { |
| context, |
| mode, |
| report, |
| skipReportingBlockedTag, |
| tagName, |
| tagNamePreference |
| }); |
| if (!targetTagName || skipReportingBlockedTag && targetTagName && typeof targetTagName === 'object') { |
| return; |
| } |
| const matchingJsdocTags = jsdoc.tags.filter(({ |
| tag |
| }) => { |
| return tag === targetTagName; |
| }); |
| for (const matchingJsdocTag of matchingJsdocTags) { |
| arrayHandler( |
| /** |
| * @type {import('@es-joy/jsdoccomment').JsdocTagWithInline} |
| */ |
| matchingJsdocTag, targetTagName); |
| } |
| }; |
| |
| /** |
| * Get all inline tags and inline tags in tags |
| * @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc |
| * @returns {(import('comment-parser').Spec| |
| * import('@es-joy/jsdoccomment').JsdocInlineTagNoType & { |
| * line?: number | undefined; column?: number | undefined; |
| * })[]} |
| */ |
| exports.forEachPreferredTag = forEachPreferredTag; |
| const getInlineTags = jsdoc => { |
| return [...jsdoc.inlineTags.map(inlineTag => { |
| // Tags don't have source or line numbers, so add before returning |
| let line = -1; |
| for (const { |
| tokens: { |
| description |
| } |
| } of jsdoc.source) { |
| line++; |
| if (description && description.includes(`{@${inlineTag.tag}`)) { |
| break; |
| } |
| } |
| inlineTag.line = line; |
| return inlineTag; |
| }), ...jsdoc.tags.flatMap(tag => { |
| for (const inlineTag of tag.inlineTags) { |
| /** @type {import('./iterateJsdoc.js').Integer} */ |
| let line = 0; |
| for (const { |
| number, |
| tokens: { |
| description |
| } |
| } of tag.source) { |
| if (description && description.includes(`{@${inlineTag.tag}`)) { |
| line = number; |
| break; |
| } |
| } |
| inlineTag.line = line; |
| } |
| return ( |
| /** |
| * @type {import('comment-parser').Spec & { |
| * inlineTags: import('@es-joy/jsdoccomment').JsdocInlineTagNoType[] |
| * }} |
| */ |
| tag.inlineTags |
| ); |
| })]; |
| }; |
| |
| /** |
| * Get all tags, inline tags and inline tags in tags |
| * @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc |
| * @returns {(import('comment-parser').Spec| |
| * import('@es-joy/jsdoccomment').JsdocInlineTagNoType & { |
| * line?: number | undefined; column?: number | undefined; |
| * })[]} |
| */ |
| exports.getInlineTags = getInlineTags; |
| const getAllTags = jsdoc => { |
| return [...jsdoc.tags, ...getInlineTags(jsdoc)]; |
| }; |
| |
| /** |
| * @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc |
| * @param {string[]} targetTagNames |
| * @returns {boolean} |
| */ |
| exports.getAllTags = getAllTags; |
| const hasATag = (jsdoc, targetTagNames) => { |
| return targetTagNames.some(targetTagName => { |
| return hasTag(jsdoc, targetTagName); |
| }); |
| }; |
| |
| /** |
| * Checks if the JSDoc comment has an undefined type. |
| * @param {import('comment-parser').Spec|null|undefined} tag |
| * the tag which should be checked. |
| * @param {ParserMode} mode |
| * @returns {boolean} |
| * true in case a defined type is undeclared; otherwise false. |
| */ |
| exports.hasATag = hasATag; |
| const mayBeUndefinedTypeTag = (tag, mode) => { |
| // The function should not continue in the event the type is not defined... |
| if (typeof tag === 'undefined' || tag === null) { |
| return true; |
| } |
| |
| // .. same applies if it declares an `{undefined}` or `{void}` type |
| const tagType = tag.type.trim(); |
| |
| // Exit early if matching |
| if (tagType === 'undefined' || tagType === 'void' || tagType === '*' || tagType === 'any') { |
| return true; |
| } |
| let parsedTypes; |
| try { |
| parsedTypes = (0, _jsdoccomment.tryParse)(tagType, mode === 'permissive' ? undefined : [mode]); |
| } catch { |
| // Ignore |
| } |
| if ( |
| // We do not traverse deeply as it could be, e.g., `Promise<void>` |
| parsedTypes && parsedTypes.type === 'JsdocTypeUnion' && parsedTypes.elements.some(elem => { |
| return elem.type === 'JsdocTypeUndefined' || elem.type === 'JsdocTypeName' && elem.value === 'void'; |
| })) { |
| return true; |
| } |
| |
| // In any other case, a type is present |
| return false; |
| }; |
| |
| /** |
| * @param {import('./getDefaultTagStructureForMode.js').TagStructure} map |
| * @param {string} tag |
| * @returns {Map<string, string|string[]|boolean|undefined>} |
| */ |
| exports.mayBeUndefinedTypeTag = mayBeUndefinedTypeTag; |
| const ensureMap = (map, tag) => { |
| if (!map.has(tag)) { |
| map.set(tag, new Map()); |
| } |
| return /** @type {Map<string, string | boolean>} */map.get(tag); |
| }; |
| |
| /** |
| * @param {import('./iterateJsdoc.js').StructuredTags} structuredTags |
| * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap |
| * @returns {void} |
| */ |
| const overrideTagStructure = (structuredTags, tagMap = tagStructure) => { |
| for (const [tag, { |
| name, |
| required = [], |
| type |
| }] of Object.entries(structuredTags)) { |
| const tagStruct = ensureMap(tagMap, tag); |
| tagStruct.set('namepathRole', name); |
| tagStruct.set('typeAllowed', type); |
| const requiredName = required.includes('name'); |
| if (requiredName && name === false) { |
| throw new Error('Cannot add "name" to `require` with the tag\'s `name` set to `false`'); |
| } |
| tagStruct.set('nameRequired', requiredName); |
| const requiredType = required.includes('type'); |
| if (requiredType && type === false) { |
| throw new Error('Cannot add "type" to `require` with the tag\'s `type` set to `false`'); |
| } |
| tagStruct.set('typeRequired', requiredType); |
| const typeOrNameRequired = required.includes('typeOrNameRequired'); |
| if (typeOrNameRequired && name === false) { |
| throw new Error('Cannot add "typeOrNameRequired" to `require` with the tag\'s `name` set to `false`'); |
| } |
| if (typeOrNameRequired && type === false) { |
| throw new Error('Cannot add "typeOrNameRequired" to `require` with the tag\'s `type` set to `false`'); |
| } |
| tagStruct.set('typeOrNameRequired', typeOrNameRequired); |
| } |
| }; |
| |
| /** |
| * @param {ParserMode} mode |
| * @param {import('./iterateJsdoc.js').StructuredTags} structuredTags |
| * @returns {import('./getDefaultTagStructureForMode.js').TagStructure} |
| */ |
| exports.overrideTagStructure = overrideTagStructure; |
| const getTagStructureForMode = (mode, structuredTags) => { |
| const tagStruct = (0, _getDefaultTagStructureForMode.default)(mode); |
| try { |
| overrideTagStructure(structuredTags, tagStruct); |
| /* c8 ignore next 3 */ |
| } catch { |
| // |
| } |
| return tagStruct; |
| }; |
| |
| /** |
| * @param {string} tag |
| * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap |
| * @returns {boolean} |
| */ |
| exports.getTagStructureForMode = getTagStructureForMode; |
| const isNameOrNamepathDefiningTag = (tag, tagMap = tagStructure) => { |
| const tagStruct = ensureMap(tagMap, tag); |
| return /** @type {(string|boolean|undefined)[]} */['name-defining', 'namepath-defining'].includes(/** @type {string|boolean|undefined} */ |
| tagStruct.get('namepathRole')); |
| }; |
| |
| /** |
| * @param {string} tag |
| * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap |
| * @returns {boolean} |
| */ |
| exports.isNameOrNamepathDefiningTag = isNameOrNamepathDefiningTag; |
| const isNamepathReferencingTag = (tag, tagMap = tagStructure) => { |
| const tagStruct = ensureMap(tagMap, tag); |
| return tagStruct.get('namepathRole') === 'namepath-referencing'; |
| }; |
| |
| /** |
| * @param {string} tag |
| * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap |
| * @returns {boolean} |
| */ |
| exports.isNamepathReferencingTag = isNamepathReferencingTag; |
| const isNamepathOrUrlReferencingTag = (tag, tagMap = tagStructure) => { |
| const tagStruct = ensureMap(tagMap, tag); |
| return tagStruct.get('namepathRole') === 'namepath-or-url-referencing'; |
| }; |
| |
| /** |
| * @param {string} tag |
| * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap |
| * @returns {boolean|undefined} |
| */ |
| exports.isNamepathOrUrlReferencingTag = isNamepathOrUrlReferencingTag; |
| const tagMustHaveTypePosition = (tag, tagMap = tagStructure) => { |
| const tagStruct = ensureMap(tagMap, tag); |
| return /** @type {boolean|undefined} */tagStruct.get('typeRequired'); |
| }; |
| |
| /** |
| * @param {string} tag |
| * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap |
| * @returns {boolean|string} |
| */ |
| exports.tagMustHaveTypePosition = tagMustHaveTypePosition; |
| const tagMightHaveTypePosition = (tag, tagMap = tagStructure) => { |
| if (tagMustHaveTypePosition(tag, tagMap)) { |
| return true; |
| } |
| const tagStruct = ensureMap(tagMap, tag); |
| const ret = /** @type {boolean|undefined} */tagStruct.get('typeAllowed'); |
| return ret === undefined ? true : ret; |
| }; |
| exports.tagMightHaveTypePosition = tagMightHaveTypePosition; |
| const namepathTypes = new Set(['name-defining', 'namepath-defining', 'namepath-referencing']); |
| |
| /** |
| * @param {string} tag |
| * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap |
| * @returns {boolean} |
| */ |
| const tagMightHaveNamePosition = (tag, tagMap = tagStructure) => { |
| const tagStruct = ensureMap(tagMap, tag); |
| const ret = tagStruct.get('namepathRole'); |
| return ret === undefined ? true : Boolean(ret); |
| }; |
| |
| /** |
| * @param {string} tag |
| * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap |
| * @returns {boolean} |
| */ |
| exports.tagMightHaveNamePosition = tagMightHaveNamePosition; |
| const tagMightHaveNameOrNamepath = (tag, tagMap = tagStructure) => { |
| const tagStruct = ensureMap(tagMap, tag); |
| const nampathRole = tagStruct.get('namepathRole'); |
| return nampathRole !== false && namepathTypes.has(/** @type {string} */nampathRole); |
| }; |
| |
| /** |
| * @param {string} tag |
| * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap |
| * @returns {boolean} |
| */ |
| exports.tagMightHaveNameOrNamepath = tagMightHaveNameOrNamepath; |
| const tagMightHaveNamepath = (tag, tagMap = tagStructure) => { |
| const tagStruct = ensureMap(tagMap, tag); |
| const nampathRole = tagStruct.get('namepathRole'); |
| return nampathRole !== false && ['namepath-defining', 'namepath-referencing'].includes(/** @type {string} */nampathRole); |
| }; |
| |
| /** |
| * @param {string} tag |
| * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap |
| * @returns {boolean} |
| */ |
| exports.tagMightHaveNamepath = tagMightHaveNamepath; |
| const tagMightHaveName = (tag, tagMap = tagStructure) => { |
| const tagStruct = ensureMap(tagMap, tag); |
| const nampathRole = tagStruct.get('namepathRole'); |
| return nampathRole !== false && nampathRole === 'name-defining'; |
| }; |
| |
| /** |
| * @param {string} tag |
| * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap |
| * @returns {boolean|undefined} |
| */ |
| exports.tagMightHaveName = tagMightHaveName; |
| const tagMustHaveNamePosition = (tag, tagMap = tagStructure) => { |
| const tagStruct = ensureMap(tagMap, tag); |
| return /** @type {boolean|undefined} */tagStruct.get('nameRequired'); |
| }; |
| |
| /** |
| * @param {string} tag |
| * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap |
| * @returns {boolean} |
| */ |
| exports.tagMustHaveNamePosition = tagMustHaveNamePosition; |
| const tagMightHaveEitherTypeOrNamePosition = (tag, tagMap) => { |
| return Boolean(tagMightHaveTypePosition(tag, tagMap)) || tagMightHaveNameOrNamepath(tag, tagMap); |
| }; |
| |
| /** |
| * @param {string} tag |
| * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap |
| * @returns {boolean|undefined} |
| */ |
| exports.tagMightHaveEitherTypeOrNamePosition = tagMightHaveEitherTypeOrNamePosition; |
| const tagMustHaveEitherTypeOrNamePosition = (tag, tagMap) => { |
| const tagStruct = ensureMap(tagMap, tag); |
| return /** @type {boolean} */tagStruct.get('typeOrNameRequired'); |
| }; |
| |
| /** |
| * @param {import('comment-parser').Spec} tag |
| * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap |
| * @returns {boolean|undefined} |
| */ |
| const tagMissingRequiredTypeOrNamepath = (tag, tagMap = tagStructure) => { |
| const mustHaveTypePosition = tagMustHaveTypePosition(tag.tag, tagMap); |
| const mightHaveTypePosition = tagMightHaveTypePosition(tag.tag, tagMap); |
| const hasTypePosition = mightHaveTypePosition && Boolean(tag.type); |
| const hasNameOrNamepathPosition = (tagMustHaveNamePosition(tag.tag, tagMap) || tagMightHaveNameOrNamepath(tag.tag, tagMap)) && Boolean(tag.name); |
| const mustHaveEither = tagMustHaveEitherTypeOrNamePosition(tag.tag, tagMap); |
| const hasEither = tagMightHaveEitherTypeOrNamePosition(tag.tag, tagMap) && (hasTypePosition || hasNameOrNamepathPosition); |
| return mustHaveEither && !hasEither && !mustHaveTypePosition; |
| }; |
| |
| /* eslint-disable complexity -- Temporary */ |
| /** |
| * @param {ESTreeOrTypeScriptNode|null|undefined} node |
| * @param {boolean} [checkYieldReturnValue] |
| * @returns {boolean} |
| */ |
| exports.tagMissingRequiredTypeOrNamepath = tagMissingRequiredTypeOrNamepath; |
| const hasNonFunctionYield = (node, checkYieldReturnValue) => { |
| /* eslint-enable complexity -- Temporary */ |
| if (!node) { |
| return false; |
| } |
| switch (node.type) { |
| case 'ArrayExpression': |
| case 'ArrayPattern': |
| return node.elements.some(element => { |
| return hasNonFunctionYield(element, checkYieldReturnValue); |
| }); |
| case 'AssignmentExpression': |
| case 'BinaryExpression': |
| case 'LogicalExpression': |
| { |
| return hasNonFunctionYield(node.left, checkYieldReturnValue) || hasNonFunctionYield(node.right, checkYieldReturnValue); |
| } |
| case 'AssignmentPattern': |
| return hasNonFunctionYield(node.right, checkYieldReturnValue); |
| case 'BlockStatement': |
| { |
| return node.body.some(bodyNode => { |
| return !['ArrowFunctionExpression', 'FunctionDeclaration', 'FunctionExpression'].includes(bodyNode.type) && hasNonFunctionYield(bodyNode, checkYieldReturnValue); |
| }); |
| } |
| |
| /* c8 ignore next 2 -- In Babel? */ |
| case 'CallExpression': |
| // @ts-expect-error In Babel? |
| case 'OptionalCallExpression': |
| return node.arguments.some(element => { |
| return hasNonFunctionYield(element, checkYieldReturnValue); |
| }); |
| case 'ChainExpression': |
| case 'ExpressionStatement': |
| { |
| return hasNonFunctionYield(node.expression, checkYieldReturnValue); |
| } |
| |
| /* c8 ignore next 2 -- In Babel? */ |
| // @ts-expect-error In Babel? |
| case 'ClassProperty': |
| |
| /* c8 ignore next 2 -- In Babel? */ |
| // @ts-expect-error In Babel? |
| case 'ObjectProperty': |
| /* c8 ignore next 2 -- In Babel? */ |
| case 'Property': |
| case 'PropertyDefinition': |
| return node.computed && hasNonFunctionYield(node.key, checkYieldReturnValue) || hasNonFunctionYield(node.value, checkYieldReturnValue); |
| case 'ConditionalExpression': |
| case 'IfStatement': |
| { |
| return hasNonFunctionYield(node.test, checkYieldReturnValue) || hasNonFunctionYield(node.consequent, checkYieldReturnValue) || hasNonFunctionYield(node.alternate, checkYieldReturnValue); |
| } |
| case 'DoWhileStatement': |
| case 'ForInStatement': |
| case 'ForOfStatement': |
| case 'ForStatement': |
| case 'LabeledStatement': |
| case 'WhileStatement': |
| case 'WithStatement': |
| { |
| return hasNonFunctionYield(node.body, checkYieldReturnValue); |
| } |
| |
| /* c8 ignore next 2 -- In Babel? */ |
| // @ts-expect-error In Babel? |
| case 'Import': |
| case 'ImportExpression': |
| return hasNonFunctionYield(node.source, checkYieldReturnValue); |
| |
| // ?. |
| /* c8 ignore next 2 -- In Babel? */ |
| case 'MemberExpression': |
| // @ts-expect-error In Babel? |
| case 'OptionalMemberExpression': |
| return hasNonFunctionYield(node.object, checkYieldReturnValue) || hasNonFunctionYield(node.property, checkYieldReturnValue); |
| case 'ObjectExpression': |
| case 'ObjectPattern': |
| return node.properties.some(property => { |
| return hasNonFunctionYield(property, checkYieldReturnValue); |
| }); |
| /* c8 ignore next 2 -- In Babel? */ |
| // @ts-expect-error In Babel? |
| case 'ObjectMethod': |
| /* c8 ignore next 6 -- In Babel? */ |
| // @ts-expect-error In Babel? |
| return node.computed && hasNonFunctionYield(node.key, checkYieldReturnValue) || |
| // @ts-expect-error In Babel? |
| node.arguments.some(nde => { |
| return hasNonFunctionYield(nde, checkYieldReturnValue); |
| }); |
| case 'ReturnStatement': |
| { |
| if (node.argument === null) { |
| return false; |
| } |
| return hasNonFunctionYield(node.argument, checkYieldReturnValue); |
| } |
| |
| // Comma |
| case 'SequenceExpression': |
| case 'TemplateLiteral': |
| return node.expressions.some(subExpression => { |
| return hasNonFunctionYield(subExpression, checkYieldReturnValue); |
| }); |
| case 'SpreadElement': |
| case 'UnaryExpression': |
| return hasNonFunctionYield(node.argument, checkYieldReturnValue); |
| case 'SwitchStatement': |
| { |
| return node.cases.some(someCase => { |
| return someCase.consequent.some(nde => { |
| return hasNonFunctionYield(nde, checkYieldReturnValue); |
| }); |
| }); |
| } |
| case 'TaggedTemplateExpression': |
| return hasNonFunctionYield(node.quasi, checkYieldReturnValue); |
| case 'TryStatement': |
| { |
| return hasNonFunctionYield(node.block, checkYieldReturnValue) || hasNonFunctionYield(node.handler && node.handler.body, checkYieldReturnValue) || hasNonFunctionYield(/** @type {import('@typescript-eslint/types').TSESTree.BlockStatement} */ |
| node.finalizer, checkYieldReturnValue); |
| } |
| case 'VariableDeclaration': |
| { |
| return node.declarations.some(nde => { |
| return hasNonFunctionYield(nde, checkYieldReturnValue); |
| }); |
| } |
| case 'VariableDeclarator': |
| { |
| return hasNonFunctionYield(node.id, checkYieldReturnValue) || hasNonFunctionYield(node.init, checkYieldReturnValue); |
| } |
| case 'YieldExpression': |
| { |
| if (checkYieldReturnValue) { |
| if (/** @type {import('eslint').Rule.Node} */node.parent?.type === 'VariableDeclarator') { |
| return true; |
| } |
| return false; |
| } |
| |
| // void return does not count. |
| if (node.argument === null) { |
| return false; |
| } |
| return true; |
| } |
| default: |
| { |
| return false; |
| } |
| } |
| }; |
| |
| /** |
| * Checks if a node has a return statement. Void return does not count. |
| * @param {ESTreeOrTypeScriptNode} node |
| * @param {boolean} [checkYieldReturnValue] |
| * @returns {boolean} |
| */ |
| const hasYieldValue = (node, checkYieldReturnValue) => { |
| return /** @type {import('@typescript-eslint/types').TSESTree.FunctionDeclaration} */node.generator && (/** @type {import('@typescript-eslint/types').TSESTree.FunctionDeclaration} */node.expression || hasNonFunctionYield(/** @type {import('@typescript-eslint/types').TSESTree.FunctionDeclaration} */ |
| node.body, checkYieldReturnValue)); |
| }; |
| |
| /** |
| * Checks if a node has a throws statement. |
| * @param {ESTreeOrTypeScriptNode|null|undefined} node |
| * @param {boolean} [innerFunction] |
| * @returns {boolean} |
| */ |
| exports.hasYieldValue = hasYieldValue; |
| const hasThrowValue = (node, innerFunction) => { |
| if (!node) { |
| return false; |
| } |
| |
| // There are cases where a function may execute its inner function which |
| // throws, but we're treating functions atomically rather than trying to |
| // follow them |
| switch (node.type) { |
| case 'ArrowFunctionExpression': |
| case 'FunctionDeclaration': |
| case 'FunctionExpression': |
| { |
| return !innerFunction && !node.async && hasThrowValue(node.body, true); |
| } |
| case 'BlockStatement': |
| { |
| return node.body.some(bodyNode => { |
| return bodyNode.type !== 'FunctionDeclaration' && hasThrowValue(bodyNode); |
| }); |
| } |
| case 'DoWhileStatement': |
| case 'ForInStatement': |
| case 'ForOfStatement': |
| case 'ForStatement': |
| case 'LabeledStatement': |
| case 'WhileStatement': |
| case 'WithStatement': |
| { |
| return hasThrowValue(node.body); |
| } |
| case 'IfStatement': |
| { |
| return hasThrowValue(node.consequent) || hasThrowValue(node.alternate); |
| } |
| case 'SwitchStatement': |
| { |
| return node.cases.some(someCase => { |
| return someCase.consequent.some(nde => { |
| return hasThrowValue(nde); |
| }); |
| }); |
| } |
| case 'ThrowStatement': |
| { |
| return true; |
| } |
| |
| // We only consider it to throw an error if the catch or finally blocks throw an error. |
| case 'TryStatement': |
| { |
| return hasThrowValue(node.handler && node.handler.body) || hasThrowValue(node.finalizer); |
| } |
| default: |
| { |
| return false; |
| } |
| } |
| }; |
| |
| /** |
| * @param {string} tag |
| */ |
| /* |
| const isInlineTag = (tag) => { |
| return /^(@link|@linkcode|@linkplain|@tutorial) /v.test(tag); |
| }; |
| */ |
| |
| /** |
| * Parses GCC Generic/Template types |
| * @see {@link https://github.com/google/closure-compiler/wiki/Generic-Types} |
| * @see {@link https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#template} |
| * @param {import('comment-parser').Spec} tag |
| * @returns {string[]} |
| */ |
| exports.hasThrowValue = hasThrowValue; |
| const parseClosureTemplateTag = tag => { |
| return tag.name.split(',').map(type => { |
| return type.trim().replace(/^\[?(?<name>.*?)=.*$/v, '$<name>'); |
| }); |
| }; |
| |
| /** |
| * @typedef {true|string[]} DefaultContexts |
| */ |
| |
| /** |
| * Checks user option for `contexts` array, defaulting to |
| * contexts designated by the rule. Returns an array of |
| * ESTree AST types, indicating allowable contexts. |
| * @param {import('eslint').Rule.RuleContext} context |
| * @param {DefaultContexts|undefined} defaultContexts |
| * @param {{ |
| * contexts?: import('./iterateJsdoc.js').Context[] |
| * }} settings |
| * @returns {(string|import('./iterateJsdoc.js').ContextObject)[]} |
| */ |
| exports.parseClosureTemplateTag = parseClosureTemplateTag; |
| const enforcedContexts = (context, defaultContexts, settings) => { |
| const contexts = context.options[0]?.contexts || settings.contexts || (defaultContexts === true ? ['ArrowFunctionExpression', 'FunctionDeclaration', 'FunctionExpression', 'TSDeclareFunction'] : defaultContexts); |
| return contexts; |
| }; |
| |
| /** |
| * @param {import('./iterateJsdoc.js').Context[]} contexts |
| * @param {import('./iterateJsdoc.js').CheckJsdoc} checkJsdoc |
| * @param {import('@es-joy/jsdoccomment').CommentHandler} [handler] |
| * @returns {import('eslint').Rule.RuleListener} |
| */ |
| exports.enforcedContexts = enforcedContexts; |
| const getContextObject = (contexts, checkJsdoc, handler) => { |
| /** @type {import('eslint').Rule.RuleListener} */ |
| const properties = {}; |
| for (const [idx, prop] of contexts.entries()) { |
| /** @type {string} */ |
| let property; |
| |
| /** @type {(node: import('eslint').Rule.Node) => void} */ |
| let value; |
| if (typeof prop === 'object') { |
| const selInfo = { |
| lastIndex: idx, |
| selector: prop.context |
| }; |
| if (prop.comment) { |
| property = /** @type {string} */prop.context; |
| value = checkJsdoc.bind(null, { |
| ...selInfo, |
| comment: prop.comment |
| }, |
| /** |
| * @type {(jsdoc: import('@es-joy/jsdoccomment').JsdocBlockWithInline) => boolean} |
| */ |
| /** @type {import('@es-joy/jsdoccomment').CommentHandler} */ |
| handler.bind(null, prop.comment)); |
| } else { |
| property = /** @type {string} */prop.context; |
| value = checkJsdoc.bind(null, selInfo, null); |
| } |
| } else { |
| const selInfo = { |
| lastIndex: idx, |
| selector: prop |
| }; |
| property = prop; |
| value = checkJsdoc.bind(null, selInfo, null); |
| } |
| const old = |
| /** |
| * @type {((node: import('eslint').Rule.Node) => void)} |
| */ |
| properties[property]; |
| properties[property] = old ? |
| /** |
| * @type {((node: import('eslint').Rule.Node) => void)} |
| */ |
| function (node) { |
| old(node); |
| value(node); |
| } : value; |
| } |
| return properties; |
| }; |
| exports.getContextObject = getContextObject; |
| const tagsWithNamesAndDescriptions = new Set(['arg', 'argument', 'param', 'prop', 'property', 'return', |
| // These two are parsed by our custom parser as though having a `name` |
| 'returns', 'template']); |
| |
| /** |
| * @typedef {{ |
| * [key: string]: false|string| |
| * {message: string, replacement?: string} |
| * }} TagNamePreference |
| */ |
| |
| /** |
| * @param {import('eslint').Rule.RuleContext} context |
| * @param {ParserMode|undefined} mode |
| * @param {import('comment-parser').Spec[]} tags |
| * @returns {{ |
| * tagsWithNames: import('comment-parser').Spec[], |
| * tagsWithoutNames: import('comment-parser').Spec[] |
| * }} |
| */ |
| const getTagsByType = (context, mode, tags) => { |
| /** |
| * @type {import('comment-parser').Spec[]} |
| */ |
| const tagsWithoutNames = []; |
| const tagsWithNames = tags.filter(tag => { |
| const { |
| tag: tagName |
| } = tag; |
| const tagWithName = tagsWithNamesAndDescriptions.has(tagName); |
| if (!tagWithName) { |
| tagsWithoutNames.push(tag); |
| } |
| return tagWithName; |
| }); |
| return { |
| tagsWithNames, |
| tagsWithoutNames |
| }; |
| }; |
| |
| /** |
| * @param {import('eslint').SourceCode|{ |
| * text: string |
| * }} sourceCode |
| * @returns {string} |
| */ |
| exports.getTagsByType = getTagsByType; |
| const getIndent = sourceCode => { |
| return (sourceCode.text.match(/^\n*([ \t]+)/v)?.[1] ?? '') + ' '; |
| }; |
| |
| /** |
| * @param {import('eslint').Rule.Node|null} node |
| * @returns {boolean} |
| */ |
| exports.getIndent = getIndent; |
| const isConstructor = node => { |
| return node?.type === 'MethodDefinition' && node.kind === 'constructor' || /** @type {import('@typescript-eslint/types').TSESTree.MethodDefinition} */node?.parent?.kind === 'constructor'; |
| }; |
| |
| /** |
| * @param {import('eslint').Rule.Node|null} node |
| * @returns {boolean} |
| */ |
| exports.isConstructor = isConstructor; |
| const isGetter = node => { |
| return node !== null && |
| /** |
| * @type {import('@typescript-eslint/types').TSESTree.MethodDefinition| |
| * import('@typescript-eslint/types').TSESTree.Property} |
| */ |
| node.parent?.kind === 'get'; |
| }; |
| |
| /** |
| * @param {import('eslint').Rule.Node|null} node |
| * @returns {boolean} |
| */ |
| exports.isGetter = isGetter; |
| const isSetter = node => { |
| return node !== null && |
| /** |
| * @type {import('@typescript-eslint/types').TSESTree.MethodDefinition| |
| * import('@typescript-eslint/types').TSESTree.Property} |
| */ |
| node.parent?.kind === 'set'; |
| }; |
| |
| /** |
| * @param {import('eslint').Rule.Node} node |
| * @returns {boolean} |
| */ |
| exports.isSetter = isSetter; |
| const hasAccessorPair = node => { |
| const { |
| key, |
| kind: sourceKind, |
| type |
| } = |
| /** |
| * @type {import('@typescript-eslint/types').TSESTree.MethodDefinition| |
| * import('@typescript-eslint/types').TSESTree.Property} |
| */ |
| node; |
| const sourceName = /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */key.name; |
| const oppositeKind = sourceKind === 'get' ? 'set' : 'get'; |
| const sibling = type === 'MethodDefinition' ? /** @type {import('@typescript-eslint/types').TSESTree.ClassBody} */node.parent.body : /** @type {import('@typescript-eslint/types').TSESTree.ObjectExpression} */node.parent.properties; |
| return sibling.some(child => { |
| const { |
| key: ky, |
| kind |
| } = |
| /** |
| * @type {import('@typescript-eslint/types').TSESTree.MethodDefinition| |
| * import('@typescript-eslint/types').TSESTree.Property} |
| */ |
| child; |
| const name = /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */ky.name; |
| return kind === oppositeKind && name === sourceName; |
| }); |
| }; |
| |
| /** |
| * @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc |
| * @param {import('eslint').Rule.Node|null} node |
| * @param {import('eslint').Rule.RuleContext} context |
| * @param {import('json-schema').JSONSchema4} schema |
| * @returns {boolean} |
| */ |
| const exemptSpeciaMethods = (jsdoc, node, context, schema) => { |
| /** |
| * @param {"checkGetters"|"checkSetters"|"checkConstructors"} prop |
| * @returns {boolean|"no-setter"|"no-getter"} |
| */ |
| const hasSchemaOption = prop => { |
| const schemaProperties = schema[0].properties; |
| return context.options[0]?.[prop] ?? (schemaProperties[prop] && schemaProperties[prop].default); |
| }; |
| const checkGetters = hasSchemaOption('checkGetters'); |
| const checkSetters = hasSchemaOption('checkSetters'); |
| return !hasSchemaOption('checkConstructors') && (isConstructor(node) || hasATag(jsdoc, ['class', 'constructor'])) || isGetter(node) && (!checkGetters || checkGetters === 'no-setter' && hasAccessorPair(/** @type {import('./iterateJsdoc.js').Node} */ |
| /** @type {import('./iterateJsdoc.js').Node} */node.parent)) || isSetter(node) && (!checkSetters || checkSetters === 'no-getter' && hasAccessorPair(/** @type {import('./iterateJsdoc.js').Node} */ |
| /** @type {import('./iterateJsdoc.js').Node} */node.parent)); |
| }; |
| |
| /** |
| * Since path segments may be unquoted (if matching a reserved word, |
| * identifier or numeric literal) or single or double quoted, in either |
| * the `@param` or in source, we need to strip the quotes to give a fair |
| * comparison. |
| * @param {string} str |
| * @returns {string} |
| */ |
| exports.exemptSpeciaMethods = exemptSpeciaMethods; |
| const dropPathSegmentQuotes = str => { |
| return str.replaceAll(/\.(['"])(.*)\1/gv, '.$2'); |
| }; |
| |
| /** |
| * @param {string} name |
| * @returns {(otherPathName: string) => boolean} |
| */ |
| exports.dropPathSegmentQuotes = dropPathSegmentQuotes; |
| const comparePaths = name => { |
| return otherPathName => { |
| return otherPathName === name || dropPathSegmentQuotes(otherPathName) === dropPathSegmentQuotes(name); |
| }; |
| }; |
| |
| /** |
| * @callback PathDoesNotBeginWith |
| * @param {string} name |
| * @param {string} otherPathName |
| * @returns {boolean} |
| */ |
| |
| /** @type {PathDoesNotBeginWith} */ |
| exports.comparePaths = comparePaths; |
| const pathDoesNotBeginWith = (name, otherPathName) => { |
| return !name.startsWith(otherPathName) && !dropPathSegmentQuotes(name).startsWith(dropPathSegmentQuotes(otherPathName)); |
| }; |
| |
| /** |
| * @param {string} regexString |
| * @param {string} [requiredFlags] |
| * @returns {RegExp} |
| */ |
| exports.pathDoesNotBeginWith = pathDoesNotBeginWith; |
| const getRegexFromString = (regexString, requiredFlags) => { |
| const match = regexString.match(/^\/(.*)\/([gimyvus]*)$/vs); |
| let flags = 'v'; |
| let regex = regexString; |
| if (match) { |
| [, regex, flags] = match; |
| if (!flags) { |
| flags = 'v'; |
| } |
| } |
| const uniqueFlags = [...new Set(flags + (requiredFlags || ''))]; |
| flags = uniqueFlags.join(''); |
| return new RegExp(regex, flags); |
| }; |
| exports.getRegexFromString = getRegexFromString; |
| const strictNativeTypes = exports.strictNativeTypes = ['undefined', 'null', 'boolean', 'number', 'bigint', 'string', 'symbol', 'object', 'Array', 'Function', 'Date', 'RegExp']; |
| |
| /** |
| * @param {import('@es-joy/jsdoccomment').JsdocBlockWithInline} jsdoc |
| * @param {import('@es-joy/jsdoccomment').JsdocTagWithInline} tag |
| * @param {import('jsdoc-type-pratt-parser').RootResult} parsedType |
| * @param {string} indent |
| * @param {string} typeBracketSpacing |
| */ |
| const rewireByParsedType = (jsdoc, tag, parsedType, indent, typeBracketSpacing = '') => { |
| const typeLines = (0, _jsdoccomment.stringify)(parsedType).split('\n'); |
| const firstTypeLine = typeLines.shift(); |
| const lastTypeLine = typeLines.pop(); |
| const beginNameOrDescIdx = tag.source.findIndex(({ |
| tokens |
| }) => { |
| return tokens.name || tokens.description; |
| }); |
| const nameAndDesc = beginNameOrDescIdx === -1 ? null : tag.source.slice(beginNameOrDescIdx); |
| const initialNumber = tag.source[0].number; |
| const src = [ |
| // Get inevitably present tag from first `tag.source` |
| { |
| number: initialNumber, |
| source: '', |
| tokens: { |
| ...tag.source[0].tokens, |
| ...(typeLines.length || lastTypeLine ? { |
| end: '', |
| name: '', |
| postName: '', |
| postType: '' |
| } : nameAndDesc ? { |
| name: nameAndDesc[0].tokens.name, |
| postType: ' ' |
| } : {}), |
| type: '{' + typeBracketSpacing + firstTypeLine + (!typeLines.length && lastTypeLine === undefined ? typeBracketSpacing + '}' : '') |
| } |
| }, |
| // Get any intervening type lines |
| ...(typeLines.length ? typeLines.map((typeLine, idx) => { |
| return { |
| number: initialNumber + idx + 1, |
| source: '', |
| tokens: { |
| // Grab any delimiter info from first item |
| ...tag.source[0].tokens, |
| delimiter: tag.source[0].tokens.delimiter === '/**' ? '*' : tag.source[0].tokens.delimiter, |
| end: '', |
| name: '', |
| postName: '', |
| postTag: '', |
| postType: '', |
| start: indent + ' ', |
| tag: '', |
| type: typeLine |
| } |
| }; |
| }) : [])]; |
| |
| // Merge any final type line and name and description |
| if ( |
| // Name and description may be already included if present with the tag |
| nameAndDesc && beginNameOrDescIdx > 0) { |
| if (typeLines.length || lastTypeLine !== undefined) { |
| src.push({ |
| number: src.length + 1, |
| source: '', |
| tokens: { |
| ...nameAndDesc[0].tokens, |
| type: lastTypeLine + typeBracketSpacing + '}' |
| } |
| }); |
| } |
| if ( |
| // Get any remaining description lines |
| nameAndDesc.length > 1) { |
| src.push(...nameAndDesc.slice(1).map(({ |
| source, |
| tokens |
| }, idx) => { |
| return { |
| number: src.length + idx + 2, |
| source, |
| tokens |
| }; |
| })); |
| } |
| } else if (nameAndDesc) { |
| if ((typeLines.length || lastTypeLine !== undefined) && lastTypeLine) { |
| src.push({ |
| number: src.length + 1, |
| source: '', |
| tokens: { |
| ...nameAndDesc[0].tokens, |
| delimiter: nameAndDesc[0].tokens.delimiter === '/**' ? '*' : nameAndDesc[0].tokens.delimiter, |
| postTag: '', |
| start: indent + ' ', |
| tag: '', |
| type: lastTypeLine + typeBracketSpacing + '}' |
| } |
| }); |
| } |
| if ( |
| // Get any remaining description lines |
| nameAndDesc.length > 1) { |
| src.push(...nameAndDesc.slice(1).map(({ |
| source, |
| tokens |
| }, idx) => { |
| return { |
| number: src.length + idx + 2, |
| source, |
| tokens |
| }; |
| })); |
| } |
| } else if (lastTypeLine) { |
| src.push({ |
| number: src.length + 1, |
| source: '', |
| tokens: { |
| ...tag.source[0].tokens, |
| delimiter: tag.source[0].tokens.delimiter === '/**' ? '*' : tag.source[0].tokens.delimiter, |
| postTag: '', |
| start: indent + ' ', |
| tag: '', |
| type: lastTypeLine + typeBracketSpacing + '}' |
| } |
| }); |
| } |
| tag.source = src; |
| |
| // Properly rewire `jsdoc.source` |
| const firstTagIdx = jsdoc.source.findIndex(({ |
| tokens: { |
| tag: tg |
| } |
| }) => { |
| return tg; |
| }); |
| const initialEndSource = jsdoc.source.find(({ |
| tokens: { |
| end |
| } |
| }) => { |
| return end; |
| }); |
| jsdoc.source = [...jsdoc.source.slice(0, firstTagIdx), ...jsdoc.tags.flatMap(({ |
| source |
| }) => { |
| return source; |
| })]; |
| if (initialEndSource && !jsdoc.source.at(-1)?.tokens?.end) { |
| jsdoc.source.push(initialEndSource); |
| } |
| }; |
| exports.rewireByParsedType = rewireByParsedType; |
| //# sourceMappingURL=jsdocUtils.cjs.map |