| "use strict"; |
| |
| Object.defineProperty(exports, "__esModule", { |
| value: true |
| }); |
| exports.buildRejectOrPreferRuleDefinition = void 0; |
| var _iterateJsdoc = _interopRequireDefault(require("./iterateJsdoc.cjs")); |
| var _jsdoccomment = require("@es-joy/jsdoccomment"); |
| function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } |
| /** |
| * Adjusts the parent type node `meta` for generic matches (or type node |
| * `type` for `JsdocTypeAny`) and sets the type node `value`. |
| * @param {string} type The actual type |
| * @param {string} preferred The preferred type |
| * @param {boolean} isGenericMatch |
| * @param {string} typeNodeName |
| * @param {import('jsdoc-type-pratt-parser').NonRootResult} node |
| * @param {import('jsdoc-type-pratt-parser').NonRootResult|undefined} parentNode |
| * @returns {void} |
| */ |
| const adjustNames = (type, preferred, isGenericMatch, typeNodeName, node, parentNode) => { |
| let ret = preferred; |
| if (isGenericMatch) { |
| const parentMeta = /** @type {import('jsdoc-type-pratt-parser').GenericResult} */parentNode.meta; |
| if (preferred === '[]') { |
| parentMeta.brackets = 'square'; |
| parentMeta.dot = false; |
| ret = 'Array'; |
| } else { |
| const dotBracketEnd = preferred.match(/\.(?:<>)?$/v); |
| if (dotBracketEnd) { |
| parentMeta.brackets = 'angle'; |
| parentMeta.dot = true; |
| ret = preferred.slice(0, -dotBracketEnd[0].length); |
| } else { |
| const bracketEnd = preferred.endsWith('<>'); |
| if (bracketEnd) { |
| parentMeta.brackets = 'angle'; |
| parentMeta.dot = false; |
| ret = preferred.slice(0, -2); |
| } else if (parentMeta?.brackets === 'square' && (typeNodeName === '[]' || typeNodeName === 'Array')) { |
| parentMeta.brackets = 'angle'; |
| parentMeta.dot = false; |
| } |
| } |
| } |
| } else if (type === 'JsdocTypeAny') { |
| node.type = 'JsdocTypeName'; |
| } |
| |
| /** @type {import('jsdoc-type-pratt-parser').NameResult} */ |
| node.value = ret.replace(/(?:\.|<>|\.<>|\[\])$/v, ''); |
| |
| // For bare pseudo-types like `<>` |
| if (!ret) { |
| /** @type {import('jsdoc-type-pratt-parser').NameResult} */node.value = typeNodeName; |
| } |
| }; |
| |
| /** |
| * @param {boolean} [upperCase] |
| * @returns {string} |
| */ |
| const getMessage = upperCase => { |
| return 'Use object shorthand or index signatures instead of ' + '`' + (upperCase ? 'O' : 'o') + 'bject`, e.g., `{[key: string]: string}`'; |
| }; |
| |
| /** |
| * @type {{ |
| * message: string, |
| * replacement: false |
| * }} |
| */ |
| const info = { |
| message: getMessage(), |
| replacement: false |
| }; |
| |
| /** |
| * @type {{ |
| * message: string, |
| * replacement: false |
| * }} |
| */ |
| const infoUC = { |
| message: getMessage(true), |
| replacement: false |
| }; |
| |
| /** |
| * @param {{ |
| * checkNativeTypes?: import('./rules/checkTypes.js').CheckNativeTypes|null |
| * overrideSettings?: import('./iterateJsdoc.js').Settings['preferredTypes']|null, |
| * description?: string, |
| * schema?: import('eslint').Rule.RuleMetaData['schema'], |
| * typeName?: string, |
| * url?: string, |
| * }} cfg |
| * @returns {import('@eslint/core').RuleDefinition< |
| * import('@eslint/core').RuleDefinitionTypeOptions |
| * >} |
| */ |
| const buildRejectOrPreferRuleDefinition = ({ |
| checkNativeTypes = null, |
| typeName, |
| description = typeName ?? 'Reports types deemed invalid (customizable and with defaults, for preventing and/or recommending replacements).', |
| overrideSettings = null, |
| schema = [], |
| url = 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/check-types.md#repos-sticky-header' |
| }) => { |
| return (0, _iterateJsdoc.default)(({ |
| context, |
| jsdocNode, |
| report, |
| settings, |
| sourceCode, |
| utils |
| }) => { |
| const jsdocTagsWithPossibleType = utils.filterTags(tag => { |
| return Boolean(utils.tagMightHaveTypePosition(tag.tag)); |
| }); |
| const |
| /** |
| * @type {{ |
| * preferredTypes: import('./iterateJsdoc.js').PreferredTypes, |
| * structuredTags: import('./iterateJsdoc.js').StructuredTags, |
| * mode: import('./jsdocUtils.js').ParserMode |
| * }} |
| */ |
| { |
| mode, |
| preferredTypes: preferredTypesOriginal, |
| structuredTags |
| } = overrideSettings ? { |
| mode: settings.mode, |
| preferredTypes: overrideSettings, |
| structuredTags: {} |
| } : settings; |
| const injectObjectPreferredTypes = !overrideSettings && !('Object' in preferredTypesOriginal || 'object' in preferredTypesOriginal || 'object.<>' in preferredTypesOriginal || 'Object.<>' in preferredTypesOriginal || 'object<>' in preferredTypesOriginal); |
| |
| /** @type {import('./iterateJsdoc.js').PreferredTypes} */ |
| const typeToInject = mode === 'typescript' ? { |
| Object: 'object', |
| 'object.<>': info, |
| 'Object.<>': infoUC, |
| 'object<>': info, |
| 'Object<>': infoUC |
| } : { |
| Object: 'object', |
| 'object.<>': 'Object<>', |
| 'Object.<>': 'Object<>', |
| 'object<>': 'Object<>' |
| }; |
| |
| /** @type {import('./iterateJsdoc.js').PreferredTypes} */ |
| const preferredTypes = { |
| ...(injectObjectPreferredTypes ? typeToInject : {}), |
| ...preferredTypesOriginal |
| }; |
| const |
| /** |
| * @type {{ |
| * noDefaults: boolean, |
| * unifyParentAndChildTypeChecks: boolean, |
| * exemptTagContexts: ({ |
| * tag: string, |
| * types: true|string[] |
| * })[] |
| * }} |
| */ |
| { |
| exemptTagContexts = [], |
| noDefaults, |
| unifyParentAndChildTypeChecks |
| } = context.options[0] || {}; |
| |
| /** |
| * Gets information about the preferred type: whether there is a matching |
| * preferred type, what the type is, and whether it is a match to a generic. |
| * @param {string} _type Not currently in use |
| * @param {string} typeNodeName |
| * @param {import('jsdoc-type-pratt-parser').NonRootResult|undefined} parentNode |
| * @param {string|undefined} property |
| * @returns {[hasMatchingPreferredType: boolean, typeName: string, isGenericMatch: boolean]} |
| */ |
| const getPreferredTypeInfo = (_type, typeNodeName, parentNode, property) => { |
| let hasMatchingPreferredType = false; |
| let isGenericMatch = false; |
| let typName = typeNodeName; |
| const isNameOfGeneric = parentNode !== undefined && parentNode.type === 'JsdocTypeGeneric' && property === 'left'; |
| const brackets = /** @type {import('jsdoc-type-pratt-parser').GenericResult} */parentNode?.meta?.brackets; |
| const dot = /** @type {import('jsdoc-type-pratt-parser').GenericResult} */parentNode?.meta?.dot; |
| if (brackets === 'angle') { |
| const checkPostFixes = dot ? ['.', '.<>'] : ['<>']; |
| isGenericMatch = checkPostFixes.some(checkPostFix => { |
| const preferredType = preferredTypes?.[typeNodeName + checkPostFix]; |
| |
| // Does `unifyParentAndChildTypeChecks` need to be checked here? |
| if ((unifyParentAndChildTypeChecks || isNameOfGeneric || (/* c8 ignore next 2 -- If checking `unifyParentAndChildTypeChecks` */ |
| typeof preferredType === 'object' && preferredType?.unifyParentAndChildTypeChecks)) && preferredType !== undefined) { |
| typName += checkPostFix; |
| return true; |
| } |
| return false; |
| }); |
| } |
| if (!isGenericMatch && property && /** @type {import('jsdoc-type-pratt-parser').NonRootResult} */parentNode.type === 'JsdocTypeGeneric') { |
| const checkPostFixes = dot ? ['.', '.<>'] : [brackets === 'angle' ? '<>' : '[]']; |
| isGenericMatch = checkPostFixes.some(checkPostFix => { |
| const preferredType = preferredTypes?.[checkPostFix]; |
| if ( |
| // Does `unifyParentAndChildTypeChecks` need to be checked here? |
| (unifyParentAndChildTypeChecks || isNameOfGeneric || (/* c8 ignore next 2 -- If checking `unifyParentAndChildTypeChecks` */ |
| typeof preferredType === 'object' && preferredType?.unifyParentAndChildTypeChecks)) && preferredType !== undefined) { |
| typName = checkPostFix; |
| return true; |
| } |
| return false; |
| }); |
| } |
| const prefType = preferredTypes?.[typeNodeName]; |
| const directNameMatch = prefType !== undefined && !Object.values(preferredTypes).includes(typeNodeName); |
| const specificUnify = typeof prefType === 'object' && prefType?.unifyParentAndChildTypeChecks; |
| const unifiedSyntaxParentMatch = property && directNameMatch && (unifyParentAndChildTypeChecks || specificUnify); |
| isGenericMatch = isGenericMatch || Boolean(unifiedSyntaxParentMatch); |
| hasMatchingPreferredType = isGenericMatch || directNameMatch && !property; |
| return [hasMatchingPreferredType, typName, isGenericMatch]; |
| }; |
| |
| /** |
| * Collect invalid type info. |
| * @param {string} type |
| * @param {string} value |
| * @param {string} tagName |
| * @param {string} nameInTag |
| * @param {number} idx |
| * @param {string|undefined} property |
| * @param {import('jsdoc-type-pratt-parser').NonRootResult} node |
| * @param {import('jsdoc-type-pratt-parser').NonRootResult|undefined} parentNode |
| * @param {(string|false|undefined)[][]} invalidTypes |
| * @returns {void} |
| */ |
| const getInvalidTypes = (type, value, tagName, nameInTag, idx, property, node, parentNode, invalidTypes) => { |
| let typeNodeName = type === 'JsdocTypeAny' ? '*' : value; |
| const [hasMatchingPreferredType, typName, isGenericMatch] = getPreferredTypeInfo(type, typeNodeName, parentNode, property); |
| let preferred; |
| let types; |
| if (hasMatchingPreferredType) { |
| const preferredSetting = preferredTypes[typName]; |
| typeNodeName = typName === '[]' ? typName : typeNodeName; |
| if (!preferredSetting) { |
| invalidTypes.push([typeNodeName]); |
| } else if (typeof preferredSetting === 'string') { |
| preferred = preferredSetting; |
| invalidTypes.push([typeNodeName, preferred]); |
| } else if (preferredSetting && typeof preferredSetting === 'object') { |
| const nextItem = preferredSetting.skipRootChecking && jsdocTagsWithPossibleType[idx + 1]; |
| if (!nextItem || !nextItem.name.startsWith(`${nameInTag}.`)) { |
| preferred = preferredSetting.replacement; |
| invalidTypes.push([typeNodeName, preferred, preferredSetting.message]); |
| } |
| } else { |
| utils.reportSettings('Invalid `settings.jsdoc.preferredTypes`. Values must be falsy, a string, or an object.'); |
| return; |
| } |
| } else if (Object.entries(structuredTags).some(([tag, { |
| type: typs |
| }]) => { |
| types = typs; |
| return tag === tagName && Array.isArray(types) && !types.includes(typeNodeName); |
| })) { |
| invalidTypes.push([typeNodeName, types]); |
| } else if (checkNativeTypes && !noDefaults && type === 'JsdocTypeName') { |
| preferred = checkNativeTypes(preferredTypes, typeNodeName, preferred, parentNode, invalidTypes); |
| } |
| |
| // For fixer |
| if (preferred) { |
| adjustNames(type, preferred, isGenericMatch, typeNodeName, node, parentNode); |
| } |
| }; |
| for (const [idx, jsdocTag] of jsdocTagsWithPossibleType.entries()) { |
| /** @type {(string|false|undefined)[][]} */ |
| const invalidTypes = []; |
| let typeAst; |
| try { |
| typeAst = mode === 'permissive' ? (0, _jsdoccomment.tryParse)(jsdocTag.type) : (0, _jsdoccomment.parse)(jsdocTag.type, mode); |
| } catch { |
| continue; |
| } |
| const { |
| name: nameInTag, |
| tag: tagName |
| } = jsdocTag; |
| (0, _jsdoccomment.traverse)(typeAst, (node, parentNode, property) => { |
| const { |
| type, |
| value |
| } = |
| /** |
| * @type {import('jsdoc-type-pratt-parser').NameResult} |
| */ |
| node; |
| if (!['JsdocTypeAny', 'JsdocTypeName'].includes(type)) { |
| return; |
| } |
| getInvalidTypes(type, value, tagName, nameInTag, idx, property, node, parentNode, invalidTypes); |
| }); |
| if (invalidTypes.length) { |
| const fixedType = (0, _jsdoccomment.stringify)(typeAst); |
| |
| /** |
| * @type {import('eslint').Rule.ReportFixer} |
| */ |
| const fix = fixer => { |
| return fixer.replaceText(jsdocNode, sourceCode.getText(jsdocNode).replace(`{${jsdocTag.type}}`, `{${fixedType}}`)); |
| }; |
| for (const [badType, preferredType = '', msg] of invalidTypes) { |
| const tagValue = jsdocTag.name ? ` "${jsdocTag.name}"` : ''; |
| if (exemptTagContexts.some(({ |
| tag, |
| types |
| }) => { |
| return tag === tagName && (types === true || types.includes(jsdocTag.type)); |
| })) { |
| continue; |
| } |
| report(msg || `Invalid JSDoc @${tagName}${tagValue} type "${badType}"` + (preferredType ? '; ' : '.') + (preferredType ? `prefer: ${JSON.stringify(preferredType)}.` : ''), preferredType ? fix : null, jsdocTag, msg ? { |
| tagName, |
| tagValue |
| } : undefined); |
| } |
| } |
| } |
| }, { |
| iterateAllJsdocs: true, |
| meta: { |
| docs: { |
| description, |
| url |
| }, |
| ...(!overrideSettings || Object.values(overrideSettings).some(os => { |
| return os && typeof os === 'object' ? /* c8 ignore next -- Ok */ |
| os.replacement : typeof os === 'string'; |
| }) ? { |
| fixable: 'code' |
| } : {}), |
| schema, |
| type: 'suggestion' |
| } |
| }); |
| }; |
| exports.buildRejectOrPreferRuleDefinition = buildRejectOrPreferRuleDefinition; |
| //# sourceMappingURL=buildRejectOrPreferRuleDefinition.cjs.map |