UNPKG

eslint-plugin-jsdoc

Version:
1,054 lines (935 loc) • 30.3 kB
import iterateJsdoc, { parseComment, } from '../iterateJsdoc.js'; import { getDocumentNamepathDefiningTags, getJSDocCommentBlocks, } from '../jsdocUtils.js'; import { getJSDocComment, parse as parseType, traverse, tryParse as tryParseType, } from '@es-joy/jsdoccomment'; import { parseImportsExports, } from 'parse-imports-exports'; const extraTypes = [ 'null', 'undefined', 'void', 'string', 'boolean', 'object', 'function', 'symbol', 'number', 'bigint', 'NaN', 'Infinity', 'any', '*', 'never', 'unknown', 'const', 'this', 'true', 'false', 'Array', 'Object', 'RegExp', 'Date', 'Function', 'Intl', ]; const globalTypes = [ 'globalThis', 'global', 'window', 'self', ]; const iterableIterator = [ 'Iterable', 'Iterator', 'IteratorObject', ]; const typescriptGlobals = [ ...iterableIterator, // https://www.typescriptlang.org/docs/handbook/utility-types.html 'Awaited', 'Partial', 'Required', 'Readonly', 'Record', 'Pick', 'Omit', 'Exclude', 'Extract', 'NonNullable', 'Parameters', 'ConstructorParameters', 'ReturnType', 'InstanceType', 'ThisParameterType', 'OmitThisParameter', 'ThisType', 'Uppercase', 'Lowercase', 'Capitalize', 'Uncapitalize', // Type-only globals of TypeScript's standard library, grouped by the // `typescript/lib` file which first declares them // lib.es5.d.ts 'ArrayBufferConstructor', 'ArrayBufferLike', 'ArrayBufferTypes', 'ArrayBufferView', 'ArrayConstructor', 'ArrayLike', 'BooleanConstructor', 'CallableFunction', 'ConcatArray', 'DataViewConstructor', 'DateConstructor', 'ErrorConstructor', 'EvalErrorConstructor', 'Float32ArrayConstructor', 'Float64ArrayConstructor', 'FunctionConstructor', 'IArguments', 'ImportAssertions', 'ImportAttributes', 'ImportCallOptions', 'ImportMeta', 'Int16ArrayConstructor', 'Int32ArrayConstructor', 'Int8ArrayConstructor', 'NewableFunction', 'NoInfer', 'NumberConstructor', 'ObjectConstructor', 'PromiseConstructorLike', 'PromiseLike', 'PropertyDescriptor', 'PropertyDescriptorMap', 'PropertyKey', 'RangeErrorConstructor', 'ReadonlyArray', 'ReferenceErrorConstructor', 'RegExpConstructor', 'RegExpExecArray', 'RegExpMatchArray', 'StringConstructor', 'SyntaxErrorConstructor', 'TemplateStringsArray', 'TypeErrorConstructor', 'TypedPropertyDescriptor', 'URIErrorConstructor', 'Uint16ArrayConstructor', 'Uint32ArrayConstructor', 'Uint8ArrayConstructor', 'Uint8ClampedArrayConstructor', 'WeakKey', 'WeakKeyTypes', // lib.es2015.collection.d.ts 'MapConstructor', 'ReadonlyMap', 'ReadonlySet', 'SetConstructor', 'WeakMapConstructor', 'WeakSetConstructor', // lib.es2015.generator.d.ts 'Generator', 'GeneratorFunction', 'GeneratorFunctionConstructor', // lib.es2015.iterable.d.ts 'ArrayIterator', 'BuiltinIteratorReturn', 'IterableIterator', 'IteratorResult', 'IteratorReturnResult', 'IteratorYieldResult', 'MapIterator', 'PromiseConstructor', 'SetIterator', 'StringIterator', 'SymbolConstructor', // lib.es2015.proxy.d.ts 'ProxyConstructor', 'ProxyHandler', // lib.es2017.sharedmemory.d.ts 'SharedArrayBufferConstructor', // lib.es2018.asyncgenerator.d.ts 'AsyncGenerator', 'AsyncGeneratorFunction', 'AsyncGeneratorFunctionConstructor', // lib.es2018.asynciterable.d.ts 'AsyncIterable', 'AsyncIterableIterator', 'AsyncIterator', 'AsyncIteratorObject', // lib.es2019.array.d.ts 'FlatArray', // lib.es2020.bigint.d.ts 'BigInt64ArrayConstructor', 'BigIntConstructor', 'BigIntToLocaleStringOptions', 'BigUint64ArrayConstructor', // lib.es2020.promise.d.ts 'PromiseFulfilledResult', 'PromiseRejectedResult', 'PromiseSettledResult', // lib.es2020.symbol.wellknown.d.ts 'RegExpStringIterator', // lib.es2021.promise.d.ts 'AggregateErrorConstructor', // lib.es2021.weakref.d.ts 'FinalizationRegistryConstructor', 'WeakRefConstructor', // lib.es2022.error.d.ts 'ErrorOptions', // lib.es2022.regexp.d.ts 'RegExpIndicesArray', // lib.es2024.promise.d.ts 'PromiseWithResolvers', // lib.es2025.collection.d.ts 'ReadonlySetLike', // lib.es2025.iterator.d.ts 'IteratorObjectConstructor', // lib.esnext.disposable.d.ts 'AsyncDisposable', 'AsyncDisposableStackConstructor', 'Disposable', 'DisposableStackConstructor', 'SuppressedErrorConstructor', // lib.decorators.d.ts 'ClassAccessorDecoratorContext', 'ClassAccessorDecoratorResult', 'ClassAccessorDecoratorTarget', 'ClassDecoratorContext', 'ClassFieldDecoratorContext', 'ClassGetterDecoratorContext', 'ClassMemberDecoratorContext', 'ClassMethodDecoratorContext', 'ClassSetterDecoratorContext', 'DecoratorContext', 'DecoratorMetadata', 'DecoratorMetadataObject', // lib.decorators.legacy.d.ts 'ClassDecorator', 'MethodDecorator', 'ParameterDecorator', 'PropertyDecorator', ]; /** * @param {string|false|undefined} [str] * @returns {undefined|string|false} */ const stripPseudoTypes = (str) => { return str && str.replace(/(?:\.|<>|\.<>|\[\])$/v, ''); }; export default iterateJsdoc(({ context, node, report, settings, sourceCode, state, utils, }) => { /** @type {string[]} */ const foundTypedefValues = []; const { scopeManager, } = sourceCode; // When is this ever `null`? const globalScope = /** @type {import('eslint').Scope.Scope} */ ( scopeManager.globalScope ); const /** * @type {{ * checkUsedTypedefs: boolean * definedTypes: string[], * disableReporting: boolean, * markVariablesAsUsed: boolean, * }} */ { checkUsedTypedefs = false, definedTypes = [], disableReporting = false, markVariablesAsUsed = true, } = context.options[0] || {}; /** @type {(string|undefined)[]} */ let definedPreferredTypes = []; const { mode, preferredTypes, structuredTags, } = settings; if (Object.keys(preferredTypes).length) { definedPreferredTypes = /** @type {string[]} */ (Object.values(preferredTypes).map((preferredType) => { if (typeof preferredType === 'string') { // May become an empty string but will be filtered out below return stripPseudoTypes(preferredType); } if (!preferredType) { return undefined; } if (typeof preferredType !== 'object') { utils.reportSettings( 'Invalid `settings.jsdoc.preferredTypes`. Values must be falsy, a string, or an object.', ); } return stripPseudoTypes(preferredType.replacement); }) .filter(Boolean)); } const allComments = sourceCode.getAllComments(); const comments = getJSDocCommentBlocks(sourceCode); const globals = allComments .filter((comment) => { return (/^\s*globals/v).test(comment.value); }).flatMap((commentNode) => { return commentNode.value.replace(/^\s*globals/v, '').trim().split(/,\s*/v); }).concat(Object.keys(context.languageOptions.globals ?? [])); const typedefs = getDocumentNamepathDefiningTags(sourceCode); const typedefDeclarations = typedefs .map((tag) => { return tag.name; }); const importTags = settings.mode === 'typescript' ? /** @type {string[]} */ (comments.flatMap((doc) => { return doc.tags.filter(({ tag, }) => { return tag === 'import'; }); }).flatMap((tag) => { const { description, name, type, } = tag; const typePart = type ? `{${type}} ` : ''; const imprt = 'import ' + (description ? `${typePart}${name} ${description}` : `${typePart}${name}`); const importsExports = parseImportsExports(imprt.trim()); const types = []; const namedImports = Object.values(importsExports.namedImports || {})[0]?.[0]; if (namedImports) { if (namedImports.default) { types.push(namedImports.default); } if (namedImports.names) { types.push(...Object.keys(namedImports.names)); } } const namespaceImports = Object.values(importsExports.namespaceImports || {})[0]?.[0]; if (namespaceImports) { if (namespaceImports.namespace) { types.push(namespaceImports.namespace); } if (namespaceImports.default) { types.push(namespaceImports.default); } } return types; }).filter(Boolean)) : []; const ancestorNodes = []; let currentNode = node; // No need for Program node? while (currentNode?.parent) { ancestorNodes.push(currentNode); currentNode = currentNode.parent; } /** * @param {import('eslint').Rule.Node} ancestorNode * @returns {import('comment-parser').Spec[]} */ const getTemplateTags = function (ancestorNode) { const commentNode = getJSDocComment(sourceCode, ancestorNode, settings); if (!commentNode) { return []; } const jsdc = parseComment(commentNode, ''); return jsdc.tags.filter((tag) => { return tag.tag === 'template'; }); }; // `currentScope` may be `null` or `Program`, so in such a case, // we look to present tags instead const templateTags = ancestorNodes.length ? ancestorNodes.flatMap((ancestorNode) => { return getTemplateTags(ancestorNode); }) : // We err on the side of being too aggressive; checking only // present tags is not sufficient comments.flatMap((doc) => { return doc.tags.filter(({ tag, }) => { return tag === 'template'; }); }); const closureGenericTypes = templateTags.flatMap((tag) => { return utils.parseClosureTemplateTag(tag); }); // In modules, including Node, there is a global scope at top with the // Program scope inside const cjsOrESMScope = globalScope.childScopes[0]?.block?.type === 'Program'; /** * @param {import("eslint").Scope.Scope | null} scope * @returns {Set<string>} */ const getValidRuntimeIdentifiers = (scope) => { const result = new Set(); let scp = scope; /** * @param {import("eslint").Scope.Scope | null} sc */ const getChildScopes = (sc) => { if (sc) { // We must check child scopes because when multiple nodes find // the same comment block, only one node is reported by our code, // and it can be the children which are not reported. for (const childScope of sc.childScopes) { for (const { name, } of childScope.variables) { result.add(name); } for (const grandChildScope of childScope.childScopes) { getChildScopes(grandChildScope); } } } }; getChildScopes(scp); while (scp) { for (const { name, } of scp.variables) { result.add(name); } scp = scp.upper; } return result; }; /** * Recursively extracts types from a namespace declaration. * @param {string} prefix - The namespace prefix (e.g., "MyNamespace" or "Outer.Inner"). * @param {import('@typescript-eslint/types').TSESTree.TSModuleDeclaration} moduleDeclaration - The module declaration node. * @returns {string[]} Array of fully qualified type names. */ const getNamespaceTypes = (prefix, moduleDeclaration) => { /* c8 ignore next 3 -- Guard for ambient modules without body. */ if (!moduleDeclaration.body || moduleDeclaration.body.type !== 'TSModuleBlock') { return []; } return moduleDeclaration.body.body.flatMap((item) => { /** @type {import('@typescript-eslint/types').TSESTree.ProgramStatement | import('@typescript-eslint/types').TSESTree.NamedExportDeclarations | null} */ let declaration = item; if (item.type === 'ExportNamedDeclaration' && item.declaration) { declaration = item.declaration; } if (declaration.type === 'TSTypeAliasDeclaration' || declaration.type === 'ClassDeclaration') { /* c8 ignore next 4 -- Guard for anonymous class declarations. */ if (!declaration.id) { return []; } return [ `${prefix}.${declaration.id.name}`, ]; } if (declaration.type === 'TSInterfaceDeclaration') { return [ `${prefix}.${declaration.id.name}`, ...declaration.body.body.map((prop) => { // Only `TSPropertySignature` and `TSMethodSignature` have 'key'. if (prop.type !== 'TSPropertySignature' && prop.type !== 'TSMethodSignature') { return ''; } // Key can be computed or a literal, only handle Identifier. if (prop.key.type !== 'Identifier') { return ''; } const propName = prop.key.name; /* c8 ignore next -- `propName` is always truthy for Identifiers. */ return propName ? `${prefix}.${declaration.id.name}.${propName}` : ''; }).filter(Boolean), ]; } // Handle nested namespaces. if (declaration.type === 'TSModuleDeclaration') { /* c8 ignore next -- Nested string-literal modules aren't valid TS syntax. */ const nestedName = declaration.id?.type === 'Identifier' ? declaration.id.name : ''; /* c8 ignore next 3 -- Guard. */ if (!nestedName) { return []; } return [ `${prefix}.${nestedName}`, ...getNamespaceTypes(`${prefix}.${nestedName}`, declaration), ]; } if (declaration.type === 'TSImportEqualsDeclaration') { return [ `${prefix}.${declaration.id.name}`, ]; } if (declaration.type === 'TSEnumDeclaration') { return [ `${prefix}.${declaration.id.name}`, ...declaration.body.members.map((member) => { // Only handle Identifier member names, not string literals. if (member.id.type !== 'Identifier') { return ''; } return `${prefix}.${declaration.id.name}.${member.id.name}`; }).filter(Boolean), ]; } if (declaration.type === 'VariableDeclaration') { return declaration.declarations.flatMap((declarator) => { // Destructuring patterns are not handled. if (declarator.id.type !== 'Identifier') { return []; } return [ `${prefix}.${declarator.id.name}`, ]; }); } if (declaration.type === 'FunctionDeclaration' || declaration.type === 'TSDeclareFunction') { /* c8 ignore next 3 -- Guard for anonymous default-exported functions which can't exist in a namespace. */ if (!declaration.id) { return []; } return [ `${prefix}.${declaration.id.name}`, ]; } // Fallback for unhandled declaration types. return []; }); }; /** * We treat imports differently as we can't introspect their children. * @type {string[]} */ const imports = []; /** @type {Set<string>} */ const closedTypes = new Set(); const tsModuleVariables = scopeManager.scopes.filter(({ type, }) => { // @ts-expect-error TS return type === 'tsModule'; }).flatMap(({ variables, }) => { return variables.map(({ name, }) => { return name; }); }); const allDefinedTypes = new Set(globalScope.variables.map(({ name, }) => { return name; }) // If the file is a module, concat the variables from the module scope. .concat( cjsOrESMScope ? globalScope.childScopes.flatMap(({ variables, }) => { return variables; }).flatMap(({ identifiers, name, }) => { const globalItem = /** @type {import('estree').Identifier & {parent: import('@typescript-eslint/types').TSESTree.Node}} */ ( identifiers?.[0] )?.parent; switch (globalItem?.type) { case 'ClassDeclaration': closedTypes.add(name); return [ name, ...globalItem.body.body.map((item) => { const property = /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */ ( /** @type {import('@typescript-eslint/types').TSESTree.PropertyDefinition} */ ( item)?.key)?.name; /* c8 ignore next 3 -- Guard */ if (!property) { return ''; } return `${name}.${property}`; }).filter(Boolean), ]; case 'ImportDefaultSpecifier': case 'ImportNamespaceSpecifier': case 'ImportSpecifier': imports.push(name); break; case 'TSInterfaceDeclaration': return [ name, ...globalItem.body.body.map((item) => { const property = /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */ ( /** @type {import('@typescript-eslint/types').TSESTree.TSPropertySignature} */ ( item)?.key)?.name; /* c8 ignore next 3 -- Guard */ if (!property) { return ''; } return `${name}.${property}`; }).filter(Boolean), ]; case 'TSModuleDeclaration': closedTypes.add(name); return [ name, ...getNamespaceTypes(name, globalItem), ]; case 'VariableDeclarator': if (/** @type {import('@typescript-eslint/types').TSESTree.Identifier} */ ( /** @type {import('@typescript-eslint/types').TSESTree.CallExpression} */ ( globalItem?.init )?.callee)?.name === 'require' ) { imports.push(/** @type {import('@typescript-eslint/types').TSESTree.Identifier} */ ( globalItem.id ).name); break; } // Module scope names are also defined return [ name, ]; } return [ name, ]; /* c8 ignore next */ }) : [], ) .concat(extraTypes) .concat(typedefDeclarations) .concat(importTags) .concat(definedTypes) .concat(tsModuleVariables) .concat(/** @type {string[]} */ (definedPreferredTypes)) .concat((() => { let nodeToCheck = node; if (node?.type === 'ExportNamedDeclaration' || node?.type === 'ExportDefaultDeclaration') { nodeToCheck = /** @type {import('estree').ClassDeclaration & import('eslint').Rule.NodeParentExtension} */ ( /** @type {import('estree').ExportNamedDeclaration & import('eslint').Rule.NodeParentExtension} */ (nodeToCheck)?.declaration ); } // Other class members are not in scope, but we need them (e.g., for a // sibling property or method referenced by `{@link}`, or a member // referenced by `{@link}` from the class's own JSDoc block), and we // grab them here /** @type {import('estree').ClassBody|undefined} */ let classBody; /** @type {string|undefined} */ let className; if (nodeToCheck?.type === 'MethodDefinition' || nodeToCheck?.type === 'PropertyDefinition') { classBody = /** @type {import('estree').ClassBody} */ (nodeToCheck.parent); className = /** @type {import('estree').ClassDeclaration} */ ( nodeToCheck.parent?.parent )?.id?.name; } else if (nodeToCheck?.type === 'ClassDeclaration' || nodeToCheck?.type === 'ClassExpression') { classBody = nodeToCheck.body; className = nodeToCheck.id?.name; } if (classBody) { return classBody.body.flatMap((methodOrProp) => { if ( (methodOrProp.type === 'MethodDefinition' || methodOrProp.type === 'PropertyDefinition') && methodOrProp.key.type === 'Identifier' ) { return [ methodOrProp.key.name, `${className}.${methodOrProp.key.name}`, ]; } /* c8 ignore next 2 -- Not yet built */ return ''; }).filter(Boolean); } return []; })()) .concat((() => { // Detect static property assignments like `MyClass.Prop = ...` const programBody = /** @type {import('@typescript-eslint/types').TSESTree.Program} */ ( sourceCode.ast ).body; return programBody.flatMap((statement) => { if ( statement.type === 'ExpressionStatement' && statement.expression.type === 'AssignmentExpression' && statement.expression.left.type === 'MemberExpression' && statement.expression.left.object.type === 'Identifier' && statement.expression.left.property.type === 'Identifier' && closedTypes.has(statement.expression.left.object.name) ) { return [ `${statement.expression.left.object.name}.${statement.expression.left.property.name}`, ]; } return []; }); })()) .concat(...getValidRuntimeIdentifiers(node && ( (sourceCode.getScope && /* c8 ignore next 3 */ sourceCode.getScope(node)) || // @ts-expect-error ESLint 8 context.getScope() ))) .concat( settings.mode === 'jsdoc' ? [] : [ ...settings.mode === 'typescript' ? typescriptGlobals : [], ...closureGenericTypes, ], )); /** * @typedef {{ * parsedType: import('jsdoc-type-pratt-parser').RootResult; * tag: import('comment-parser').Spec|import('@es-joy/jsdoccomment').JsdocInlineTagNoType & { * line?: import('../iterateJsdoc.js').Integer * } * }} TypeAndTagInfo */ /** * @param {string} propertyName * @returns {(tag: (import('@es-joy/jsdoccomment').JsdocInlineTagNoType & { * name?: string, * type?: string, * line?: import('../iterateJsdoc.js').Integer * })|import('comment-parser').Spec & { * namepathOrURL?: string * } * ) => undefined|TypeAndTagInfo} */ const tagToParsedType = (propertyName) => { return (tag) => { try { const potentialType = tag[ /** @type {"type"|"name"|"namepathOrURL"} */ (propertyName) ]; return { parsedType: mode === 'permissive' ? tryParseType(/** @type {string} */ (potentialType)) : parseType(/** @type {string} */ (potentialType), mode), tag, }; } catch { return undefined; } }; }; const typeTags = utils.filterTags(({ tag, }) => { return tag !== 'import' && utils.tagMightHaveTypePosition(tag) && (tag !== 'suppress' || settings.mode !== 'closure'); }).map(tagToParsedType('type')); const namepathReferencingTags = utils.filterTags(({ tag, }) => { return utils.isNamepathReferencingTag(tag); }).map(tagToParsedType('name')); const namepathOrUrlReferencingTags = utils.filterAllTags(({ tag, }) => { return utils.isNamepathOrUrlReferencingTag(tag); }).map(tagToParsedType('namepathOrURL')); const definedNamesAndNamepaths = new Set(utils.filterTags(({ tag, }) => { return utils.isNameOrNamepathDefiningTag(tag); }).map(({ name, }) => { return name; })); const tagsWithTypes = /** @type {TypeAndTagInfo[]} */ ([ ...typeTags, ...namepathReferencingTags, ...namepathOrUrlReferencingTags, // Remove types which failed to parse ].filter(Boolean)); for (const { parsedType, tag, } of tagsWithTypes) { // eslint-disable-next-line complexity -- Refactor traverse(parsedType, (nde, parentNode) => { /** * @type {import('jsdoc-type-pratt-parser').NameResult & { * _parent?: import('jsdoc-type-pratt-parser').NonRootResult * }} */ // eslint-disable-next-line canonical/id-match -- Avoid clashes (nde)._parent = parentNode; const { type, value, } = /** @type {import('jsdoc-type-pratt-parser').NameResult} */ (nde); let val = value; /** @type {import('jsdoc-type-pratt-parser').NonRootResult|undefined} */ let currNode = nde; do { currNode = /** * @type {import('jsdoc-type-pratt-parser').NameResult & { * _parent?: import('jsdoc-type-pratt-parser').NonRootResult * }} */ (currNode)._parent; if ( // Avoid appending for imports and globals since we don't want to // check their properties which may or may not exist !imports.includes(val) && !globals.includes(val) && !importTags.includes(val) && !extraTypes.includes(val) && !typedefDeclarations.includes(val) && !globalTypes.includes(val) && currNode && 'right' in currNode && currNode.right?.type === 'JsdocTypeProperty' ) { val = val + '.' + currNode.right.value; } } while (currNode?.type === 'JsdocTypeNamePath'); if (type === 'JsdocTypeName') { const structuredTypes = structuredTags[tag.tag]?.type; const rootNamespace = val.split('.')[0]; const isNamespaceValid = (definedTypes.includes(rootNamespace) || allDefinedTypes.has(rootNamespace)) && !closedTypes.has(rootNamespace); if (!allDefinedTypes.has(val) && !definedNamesAndNamepaths.has(val) && (!Array.isArray(structuredTypes) || !structuredTypes.includes(val)) && !isNamespaceValid ) { const parent = /** * @type {import('jsdoc-type-pratt-parser').RootResult & { * _parent?: import('jsdoc-type-pratt-parser').NonRootResult * }} */ (nde)._parent; if (parent?.type === 'JsdocTypeTypeParameter') { return; } if (parent?.type === 'JsdocTypeFunction' && /** @type {import('jsdoc-type-pratt-parser').FunctionResult} */ (parent)?.typeParameters?.some((typeParam) => { return value === typeParam.name.value; }) ) { return; } if (parent?.type === 'JsdocTypeInfer' && value === parent.element.value) { allDefinedTypes.add(value); return; } if (!disableReporting) { report(`The type '${val}' is undefined.`, null, tag); } } else if (markVariablesAsUsed && !extraTypes.includes(val)) { if (sourceCode.markVariableAsUsed) { sourceCode.markVariableAsUsed(val); /* c8 ignore next 4 */ } else { // @ts-expect-error ESLint 8 context.markVariableAsUsed(val); } } if (checkUsedTypedefs && typedefDeclarations.includes(val)) { foundTypedefValues.push(val); } } }); } state.foundTypedefValues = foundTypedefValues; }, { // We use this method rather than checking at end of handler above because // in that case, it is invoked too many times and would thus report errors // too many times. exit ({ context, state, utils, }) { const { checkUsedTypedefs = false, } = context.options[0] || {}; if (!checkUsedTypedefs) { return; } const allComments = context.sourceCode.getAllComments(); const comments = allComments .filter((comment) => { return (/^\*(?!\*)/v).test(comment.value); }) .map((commentNode) => { return { doc: parseComment(commentNode, ''), loc: commentNode.loc, }; }); const typedefs = comments .flatMap(({ doc, loc, }) => { const tags = doc.tags.filter(({ tag, }) => { return utils.isNameOrNamepathDefiningTag(tag); }); if (!tags.length) { return []; } return { loc, tags, }; }); for (const typedef of typedefs) { if ( !state.foundTypedefValues.includes(typedef.tags[0].name) ) { context.report({ loc: /** @type {import('@eslint/core').SourceLocation} */ (typedef.loc), message: 'This typedef was not used within the file', }); } } }, iterateAllJsdocs: true, meta: { docs: { description: 'Besides some expected built-in types, prohibits any types not specified as globals or within `@typedef`.', url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/no-undefined-types.md#repos-sticky-header', }, schema: [ { additionalProperties: false, properties: { checkUsedTypedefs: { description: 'Whether to check typedefs for use within the file', type: 'boolean', }, definedTypes: { description: `This array can be populated to indicate other types which are automatically considered as defined (in addition to globals, etc.). Defaults to an empty array.`, items: { type: 'string', }, type: 'array', }, disableReporting: { description: `Whether to disable reporting of errors. Defaults to \`false\`. This may be set to \`true\` in order to take advantage of only marking defined variables as used or checking used typedefs.`, type: 'boolean', }, markVariablesAsUsed: { description: `Whether to mark variables as used for the purposes of the \`no-unused-vars\` rule when they are not found to be undefined. Defaults to \`true\`. May be set to \`false\` to enforce a practice of not importing types unless used in code.`, type: 'boolean', }, }, type: 'object', }, ], type: 'suggestion', }, });