| "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.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.tagMightHaveEitherTypeOrNamePosition = exports.setTagStructure = exports.pathDoesNotBeginWith = exports.parseClosureTemplateTag = exports.overrideTagStructure = exports.mayBeUndefinedTypeTag = exports.isValidTag = exports.isSetter = exports.isNamepathReferencingTag = exports.isNamepathOrUrlReferencingTag = exports.isNamepathDefiningTag = exports.isGetter = exports.isConstructor = exports.hasYieldValue = void 0; |
| var _getDefaultTagStructureForMode = _interopRequireDefault(require("./getDefaultTagStructureForMode.cjs")); |
| var _tagNames = require("./tagNames.cjs"); |
| var _hasReturnValue = require("./utils/hasReturnValue.cjs"); |
| var _WarnSettings = _interopRequireDefault(require("./WarnSettings.cjs")); |
| var _jsdoccomment = require("@es-joy/jsdoccomment"); |
| 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] |
| * @throws {Error} |
| * @returns {ParamNameInfo[]} |
| */ |
| const getFunctionParameterNames = (functionNode, checkDefaultObjects) => { |
| var _functionNode$value; |
| /* 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) => { |
| var _param$left2; |
| /* eslint-enable complexity -- Temporary */ |
| const hasLeftTypeAnnotation = 'left' in param && 'typeAnnotation' in param.left; |
| if ('typeAnnotation' in param || hasLeftTypeAnnotation) { |
| var _typeAnnotation$typeA; |
| 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 === null || typeAnnotation === void 0 || (_typeAnnotation$typeA = typeAnnotation.typeAnnotation) === null || _typeAnnotation$typeA === void 0 ? void 0 : _typeAnnotation$typeA.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') { |
| var _param$left; |
| const properties = /** @type {import('@typescript-eslint/types').TSESTree.ObjectPattern} */param.properties || ( /** @type {import('estree').ObjectPattern} */(_param$left = /** @type {import('@typescript-eslint/types').TSESTree.AssignmentPattern} */param.left) === null || _param$left === void 0 ? void 0 : _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 === null || prop === void 0 ? void 0 : 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 '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); |
| })]; |
| 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 === null || prop === void 0 ? void 0 : prop.type) === 'RestElement' |
| }; |
| })]; |
| } |
| } |
| } |
| 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$left2 = param.left) === null || _param$left2 === void 0 ? void 0 : _param$left2.type) === 'ArrayPattern') { |
| var _param$left3; |
| const elements = /** @type {import('estree').ArrayPattern} */param.elements || ( /** @type {import('estree').ArrayPattern} */(_param$left3 = /** @type {import('estree').AssignmentPattern} */param.left) === null || _param$left3 === void 0 ? void 0 : _param$left3.elements); |
| const roots = elements.map((prop, idx) => { |
| return { |
| name: `"${idx}"`, |
| restElement: (prop === null || prop === void 0 ? void 0 : prop.type) === 'RestElement' |
| }; |
| }); |
| return [undefined, flattenRoots(roots)]; |
| } |
| if (['RestElement', 'ExperimentalRestProperty'].includes(param.type)) { |
| var _param$argument; |
| 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 === null || param === void 0 || (_param$argument = param.argument) === null || _param$argument === void 0 || (_param$argument = _param$argument.elements) === null || _param$argument === void 0 ? void 0 : _param$argument.map(({ |
| 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 = functionNode.value) === null || _functionNode$value === void 0 ? void 0 : _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 {Reporter} context |
| * @returns {import('./tagNames.js').AliasedTags} |
| */ |
| const getTagNamesForMode = (mode, context) => { |
| switch (mode) { |
| case 'jsdoc': |
| return _tagNames.jsdocTags; |
| case 'typescript': |
| return _tagNames.typeScriptTags; |
| case 'closure': |
| case 'permissive': |
| return _tagNames.closureTags; |
| 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: { |
| 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'); |
| }; |
| |
| /** |
| * @typedef {{ |
| * report: (descriptor: import('eslint').Rule.ReportDescriptor) => void |
| * }} Reporter |
| */ |
| |
| /** |
| * @param {string} name |
| * @param {ParserMode|undefined} mode |
| * @param {TagNamePreference} tagPreference |
| * @param {Reporter} context |
| * @returns {string|false|{ |
| * message: string; |
| * replacement?: string|undefined; |
| * }} |
| */ |
| exports.getTagDescription = getTagDescription; |
| const getPreferredTagNameSimple = (name, mode, tagPreference = {}, context = { |
| report() { |
| // No-op |
| } |
| }) => { |
| var _Object$entries$find; |
| 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 /u, ''), value]; |
| })); |
| if (Object.prototype.hasOwnProperty.call(tagPreferenceFixed, name)) { |
| return tagPreferenceFixed[name]; |
| } |
| const tagNames = getTagNamesForMode(mode, context); |
| const preferredTagName = (_Object$entries$find = Object.entries(tagNames).find(([, aliases]) => { |
| return aliases.includes(name); |
| })) === null || _Object$entries$find === void 0 ? void 0 : _Object$entries$find[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, { |
| tagName, |
| context, |
| mode, |
| tagNamePreference, |
| report = () => {}, |
| skipReportingBlockedTag = false, |
| allowObjectReturn = false, |
| defaultMessage = `Unexpected tag \`@${tagName}\`` |
| }) => { |
| 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, |
| tagNamePreference, |
| skipReportingBlockedTag = false |
| } = {}) => { |
| const targetTagName = /** @type {string|false} */ |
| getPreferredTagName(jsdoc, { |
| skipReportingBlockedTag, |
| tagName, |
| context, |
| mode, |
| report, |
| 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 tags, inline tags and inline tags in tags |
| * @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc |
| * @returns {(import('comment-parser').Spec| |
| * import('@es-joy/jsdoccomment').JsdocInlineTagNoType)[]} |
| */ |
| exports.forEachPreferredTag = forEachPreferredTag; |
| const getAllTags = jsdoc => { |
| return [...jsdoc.tags, ...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 => { |
| let tagBegins = -1; |
| for (const { |
| tokens: { |
| tag: tg |
| } |
| } of jsdoc.source) { |
| tagBegins++; |
| if (tg) { |
| break; |
| } |
| } |
| 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 = tagBegins + line - 1; |
| } |
| return ( |
| /** |
| * @type {import('comment-parser').Spec & { |
| * inlineTags: import('@es-joy/jsdoccomment').JsdocInlineTagNoType[] |
| * }} |
| */ |
| tag.inlineTags |
| ); |
| })]; |
| }; |
| |
| /** |
| * @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.find(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, |
| type, |
| required = [] |
| }] 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 isNamepathDefiningTag = (tag, tagMap = tagStructure) => { |
| const tagStruct = ensureMap(tagMap, tag); |
| return tagStruct.get('namepathRole') === 'namepath-defining'; |
| }; |
| |
| /** |
| * @param {string} tag |
| * @param {import('./getDefaultTagStructureForMode.js').TagStructure} tagMap |
| * @returns {boolean} |
| */ |
| exports.isNamepathDefiningTag = isNamepathDefiningTag; |
| 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(['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 tagMightHaveNamepath = (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|undefined} |
| */ |
| exports.tagMightHaveNamepath = tagMightHaveNamepath; |
| 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)) || tagMightHaveNamepath(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) || tagMightHaveNamepath(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 'BlockStatement': |
| { |
| return node.body.some(bodyNode => { |
| return !['ArrowFunctionExpression', 'FunctionDeclaration', 'FunctionExpression'].includes(bodyNode.type) && hasNonFunctionYield(bodyNode, checkYieldReturnValue); |
| }); |
| } |
| |
| /* c8 ignore next 2 -- In Babel? */ |
| // @ts-expect-error In Babel? |
| case 'OptionalCallExpression': |
| case 'CallExpression': |
| return node.arguments.some(element => { |
| return hasNonFunctionYield(element, checkYieldReturnValue); |
| }); |
| case 'ChainExpression': |
| case 'ExpressionStatement': |
| { |
| return hasNonFunctionYield(node.expression, checkYieldReturnValue); |
| } |
| case 'LabeledStatement': |
| case 'WhileStatement': |
| case 'DoWhileStatement': |
| case 'ForStatement': |
| case 'ForInStatement': |
| case 'ForOfStatement': |
| case 'WithStatement': |
| { |
| return hasNonFunctionYield(node.body, checkYieldReturnValue); |
| } |
| case 'ConditionalExpression': |
| case 'IfStatement': |
| { |
| return hasNonFunctionYield(node.test, checkYieldReturnValue) || hasNonFunctionYield(node.consequent, checkYieldReturnValue) || hasNonFunctionYield(node.alternate, 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 'SwitchStatement': |
| { |
| return node.cases.some(someCase => { |
| return someCase.consequent.some(nde => { |
| return hasNonFunctionYield(nde, checkYieldReturnValue); |
| }); |
| }); |
| } |
| case 'ArrayPattern': |
| case 'ArrayExpression': |
| return node.elements.some(element => { |
| return hasNonFunctionYield(element, checkYieldReturnValue); |
| }); |
| case 'AssignmentPattern': |
| return hasNonFunctionYield(node.right, checkYieldReturnValue); |
| case 'VariableDeclaration': |
| { |
| return node.declarations.some(nde => { |
| return hasNonFunctionYield(nde, checkYieldReturnValue); |
| }); |
| } |
| case 'VariableDeclarator': |
| { |
| return hasNonFunctionYield(node.id, checkYieldReturnValue) || hasNonFunctionYield(node.init, checkYieldReturnValue); |
| } |
| case 'AssignmentExpression': |
| case 'BinaryExpression': |
| case 'LogicalExpression': |
| { |
| return hasNonFunctionYield(node.left, checkYieldReturnValue) || hasNonFunctionYield(node.right, checkYieldReturnValue); |
| } |
| |
| // Comma |
| case 'SequenceExpression': |
| case 'TemplateLiteral': |
| return node.expressions.some(subExpression => { |
| return hasNonFunctionYield(subExpression, checkYieldReturnValue); |
| }); |
| case 'ObjectPattern': |
| case 'ObjectExpression': |
| return node.properties.some(property => { |
| return hasNonFunctionYield(property, checkYieldReturnValue); |
| }); |
| |
| /* c8 ignore next -- In Babel? */ |
| case 'PropertyDefinition': |
| /* eslint-disable no-fallthrough */ |
| /* c8 ignore next 2 -- In Babel? */ |
| // @ts-expect-error In Babel? |
| case 'ObjectProperty': |
| /* c8 ignore next 2 -- In Babel? */ |
| // @ts-expect-error In Babel? |
| case 'ClassProperty': |
| case 'Property': |
| /* eslint-enable no-fallthrough */ |
| return node.computed && hasNonFunctionYield(node.key, checkYieldReturnValue) || hasNonFunctionYield(node.value, 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 'SpreadElement': |
| case 'UnaryExpression': |
| return hasNonFunctionYield(node.argument, checkYieldReturnValue); |
| case 'TaggedTemplateExpression': |
| return hasNonFunctionYield(node.quasi, checkYieldReturnValue); |
| |
| // ?. |
| /* c8 ignore next 2 -- In Babel? */ |
| // @ts-expect-error In Babel? |
| case 'OptionalMemberExpression': |
| case 'MemberExpression': |
| return hasNonFunctionYield(node.object, checkYieldReturnValue) || hasNonFunctionYield(node.property, checkYieldReturnValue); |
| |
| /* c8 ignore next 2 -- In Babel? */ |
| // @ts-expect-error In Babel? |
| case 'Import': |
| case 'ImportExpression': |
| return hasNonFunctionYield(node.source, checkYieldReturnValue); |
| case 'ReturnStatement': |
| { |
| if (node.argument === null) { |
| return false; |
| } |
| return hasNonFunctionYield(node.argument, 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} |
| */ |
| // eslint-disable-next-line complexity |
| 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 'FunctionExpression': |
| case 'FunctionDeclaration': |
| case 'ArrowFunctionExpression': |
| { |
| return !innerFunction && !node.async && hasThrowValue(node.body, true); |
| } |
| case 'BlockStatement': |
| { |
| return node.body.some(bodyNode => { |
| return bodyNode.type !== 'FunctionDeclaration' && hasThrowValue(bodyNode); |
| }); |
| } |
| case 'LabeledStatement': |
| case 'WhileStatement': |
| case 'DoWhileStatement': |
| case 'ForStatement': |
| case 'ForInStatement': |
| case 'ForOfStatement': |
| case 'WithStatement': |
| { |
| return hasThrowValue(node.body); |
| } |
| case 'IfStatement': |
| { |
| return hasThrowValue(node.consequent) || hasThrowValue(node.alternate); |
| } |
| |
| // 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); |
| } |
| case 'SwitchStatement': |
| { |
| return node.cases.some(someCase => { |
| return someCase.consequent.some(nde => { |
| return hasThrowValue(nde); |
| }); |
| }); |
| } |
| case 'ThrowStatement': |
| { |
| return true; |
| } |
| default: |
| { |
| return false; |
| } |
| } |
| }; |
| |
| /** |
| * @param {string} tag |
| */ |
| /* |
| const isInlineTag = (tag) => { |
| return /^(@link|@linkcode|@linkplain|@tutorial) /u.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>.*?)=.*\]$/u, '$<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) => { |
| var _context$options$; |
| const contexts = ((_context$options$ = context.options[0]) === null || _context$options$ === void 0 ? void 0 : _context$options$.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(['param', 'arg', 'argument', 'property', 'prop', 'template', |
| // These two are parsed by our custom parser as though having a `name` |
| 'returns', 'return']); |
| |
| /** |
| * @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 => { |
| var _sourceCode$text$matc; |
| return (((_sourceCode$text$matc = sourceCode.text.match(/^\n*([ \t]+)/u)) === null || _sourceCode$text$matc === void 0 ? void 0 : _sourceCode$text$matc[1]) ?? '') + ' '; |
| }; |
| |
| /** |
| * @param {import('eslint').Rule.Node|null} node |
| * @returns {boolean} |
| */ |
| exports.getIndent = getIndent; |
| const isConstructor = node => { |
| var _node$parent; |
| return (node === null || node === void 0 ? void 0 : node.type) === 'MethodDefinition' && node.kind === 'constructor' || /** @type {import('@typescript-eslint/types').TSESTree.MethodDefinition} */(node === null || node === void 0 || (_node$parent = node.parent) === null || _node$parent === void 0 ? void 0 : _node$parent.kind) === 'constructor'; |
| }; |
| |
| /** |
| * @param {import('eslint').Rule.Node|null} node |
| * @returns {boolean} |
| */ |
| exports.isConstructor = isConstructor; |
| const isGetter = node => { |
| var _node$parent2; |
| return node !== null && |
| /** |
| * @type {import('@typescript-eslint/types').TSESTree.MethodDefinition| |
| * import('@typescript-eslint/types').TSESTree.Property} |
| */ |
| ((_node$parent2 = node.parent) === null || _node$parent2 === void 0 ? void 0 : _node$parent2.kind) === 'get'; |
| }; |
| |
| /** |
| * @param {import('eslint').Rule.Node|null} node |
| * @returns {boolean} |
| */ |
| exports.isGetter = isGetter; |
| const isSetter = node => { |
| var _node$parent3; |
| return node !== null && |
| /** |
| * @type {import('@typescript-eslint/types').TSESTree.MethodDefinition| |
| * import('@typescript-eslint/types').TSESTree.Property} |
| */ |
| ((_node$parent3 = node.parent) === null || _node$parent3 === void 0 ? void 0 : _node$parent3.kind) === 'set'; |
| }; |
| |
| /** |
| * @param {import('eslint').Rule.Node} node |
| * @returns {boolean} |
| */ |
| exports.isSetter = isSetter; |
| const hasAccessorPair = node => { |
| const { |
| type, |
| kind: sourceKind, |
| key |
| } = |
| /** |
| * @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 { |
| kind, |
| key: ky |
| } = |
| /** |
| * @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 => { |
| var _context$options$2; |
| const schemaProperties = schema[0].properties; |
| return ((_context$options$2 = context.options[0]) === null || _context$options$2 === void 0 ? void 0 : _context$options$2[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} */node.parent)) || isSetter(node) && (!checkSetters || checkSetters === 'no-getter' && hasAccessorPair( /** @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/gu, '.$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(/^\/(.*)\/([gimyus]*)$/us); |
| let flags = 'u'; |
| let regex = regexString; |
| if (match) { |
| [, regex, flags] = match; |
| if (!flags) { |
| flags = 'u'; |
| } |
| } |
| const uniqueFlags = [...new Set(flags + (requiredFlags || ''))]; |
| flags = uniqueFlags.join(''); |
| return new RegExp(regex, flags); |
| }; |
| exports.getRegexFromString = getRegexFromString; |
| //# sourceMappingURL=jsdocUtils.cjs.map |