| import iterateJsdoc from '../iterateJsdoc.js'; |
| import { |
| parseImportsExports, |
| } from 'parse-imports-exports'; |
| import semver from 'semver'; |
| import spdxExpressionParse from 'spdx-expression-parse'; |
| |
| const allowedKinds = new Set([ |
| 'class', |
| 'constant', |
| 'event', |
| 'external', |
| 'file', |
| 'function', |
| 'member', |
| 'mixin', |
| 'module', |
| 'namespace', |
| 'typedef', |
| ]); |
| |
| export default iterateJsdoc(({ |
| context, |
| report, |
| settings, |
| utils, |
| }) => { |
| const options = context.options[0] || {}; |
| const { |
| allowedAuthors = null, |
| allowedLicenses = null, |
| licensePattern = '/([^\n\r]*)/gv', |
| numericOnlyVariation = false, |
| } = options; |
| |
| utils.forEachPreferredTag('version', (jsdocParameter, targetTagName) => { |
| const version = /** @type {string} */ ( |
| utils.getTagDescription(jsdocParameter) |
| ).trim(); |
| if (!version) { |
| report( |
| `Missing JSDoc @${targetTagName} value.`, |
| null, |
| jsdocParameter, |
| ); |
| } else if (!semver.valid(version)) { |
| report( |
| `Invalid JSDoc @${targetTagName}: "${utils.getTagDescription(jsdocParameter)}".`, |
| null, |
| jsdocParameter, |
| ); |
| } |
| }); |
| |
| utils.forEachPreferredTag('kind', (jsdocParameter, targetTagName) => { |
| const kind = /** @type {string} */ ( |
| utils.getTagDescription(jsdocParameter) |
| ).trim(); |
| if (!kind) { |
| report( |
| `Missing JSDoc @${targetTagName} value.`, |
| null, |
| jsdocParameter, |
| ); |
| } else if (!allowedKinds.has(kind)) { |
| report( |
| `Invalid JSDoc @${targetTagName}: "${utils.getTagDescription(jsdocParameter)}"; ` + |
| `must be one of: ${[ |
| ...allowedKinds, |
| ].join(', ')}.`, |
| null, |
| jsdocParameter, |
| ); |
| } |
| }); |
| |
| if (numericOnlyVariation) { |
| utils.forEachPreferredTag('variation', (jsdocParameter, targetTagName) => { |
| const variation = /** @type {string} */ ( |
| utils.getTagDescription(jsdocParameter) |
| ).trim(); |
| if (!variation) { |
| report( |
| `Missing JSDoc @${targetTagName} value.`, |
| null, |
| jsdocParameter, |
| ); |
| } else if ( |
| !Number.isInteger(Number(variation)) || |
| Number(variation) <= 0 |
| ) { |
| report( |
| `Invalid JSDoc @${targetTagName}: "${utils.getTagDescription(jsdocParameter)}".`, |
| null, |
| jsdocParameter, |
| ); |
| } |
| }); |
| } |
| |
| utils.forEachPreferredTag('since', (jsdocParameter, targetTagName) => { |
| const version = /** @type {string} */ ( |
| utils.getTagDescription(jsdocParameter) |
| ).trim(); |
| if (!version) { |
| report( |
| `Missing JSDoc @${targetTagName} value.`, |
| null, |
| jsdocParameter, |
| ); |
| } else if (!semver.valid(version)) { |
| report( |
| `Invalid JSDoc @${targetTagName}: "${utils.getTagDescription(jsdocParameter)}".`, |
| null, |
| jsdocParameter, |
| ); |
| } |
| }); |
| utils.forEachPreferredTag('license', (jsdocParameter, targetTagName) => { |
| const licenseRegex = utils.getRegexFromString(licensePattern, 'g'); |
| const matches = /** @type {string} */ ( |
| utils.getTagDescription(jsdocParameter) |
| ).matchAll(licenseRegex); |
| let positiveMatch = false; |
| for (const match of matches) { |
| const license = match[1] || match[0]; |
| if (license) { |
| positiveMatch = true; |
| } |
| |
| if (!license.trim()) { |
| // Avoid reporting again as empty match |
| if (positiveMatch) { |
| return; |
| } |
| |
| report( |
| `Missing JSDoc @${targetTagName} value.`, |
| null, |
| jsdocParameter, |
| ); |
| } else if (allowedLicenses) { |
| if (allowedLicenses !== true && !allowedLicenses.includes(license)) { |
| report( |
| `Invalid JSDoc @${targetTagName}: "${license}"; expected one of ${allowedLicenses.join(', ')}.`, |
| null, |
| jsdocParameter, |
| ); |
| } |
| } else { |
| try { |
| spdxExpressionParse(license); |
| } catch { |
| report( |
| `Invalid JSDoc @${targetTagName}: "${license}"; expected SPDX expression: https://spdx.org/licenses/.`, |
| null, |
| jsdocParameter, |
| ); |
| } |
| } |
| } |
| }); |
| |
| if (settings.mode === 'typescript') { |
| utils.forEachPreferredTag('import', (tag) => { |
| const { |
| description, |
| name, |
| type, |
| } = tag; |
| const typePart = type ? `{${type}} ` : ''; |
| const imprt = 'import ' + (description ? |
| `${typePart}${name} ${description}` : |
| `${typePart}${name}`); |
| |
| const importsExports = parseImportsExports(imprt.trim()); |
| |
| if (importsExports.errors) { |
| report( |
| 'Bad @import tag', |
| null, |
| tag, |
| ); |
| } |
| }); |
| } |
| |
| utils.forEachPreferredTag('author', (jsdocParameter, targetTagName) => { |
| const author = /** @type {string} */ ( |
| utils.getTagDescription(jsdocParameter) |
| ).trim(); |
| if (!author) { |
| report( |
| `Missing JSDoc @${targetTagName} value.`, |
| null, |
| jsdocParameter, |
| ); |
| } else if (allowedAuthors && !allowedAuthors.includes(author)) { |
| report( |
| `Invalid JSDoc @${targetTagName}: "${utils.getTagDescription(jsdocParameter)}"; expected one of ${allowedAuthors.join(', ')}.`, |
| null, |
| jsdocParameter, |
| ); |
| } |
| }); |
| }, { |
| iterateAllJsdocs: true, |
| meta: { |
| docs: { |
| description: 'This rule checks the values for a handful of tags: `@version`, `@since`, `@license` and `@author`.', |
| url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/check-values.md#repos-sticky-header', |
| }, |
| schema: [ |
| { |
| additionalProperties: false, |
| properties: { |
| allowedAuthors: { |
| description: `An array of allowable author values. If absent, only non-whitespace will |
| be checked for.`, |
| items: { |
| type: 'string', |
| }, |
| type: 'array', |
| }, |
| allowedLicenses: { |
| anyOf: [ |
| { |
| items: { |
| type: 'string', |
| }, |
| type: 'array', |
| }, |
| { |
| type: 'boolean', |
| }, |
| ], |
| description: `An array of allowable license values or \`true\` to allow any license text. |
| If present as an array, will be used in place of [SPDX identifiers](https://spdx.org/licenses/).`, |
| }, |
| licensePattern: { |
| description: `A string to be converted into a \`RegExp\` (with \`v\` flag) and whose first |
| parenthetical grouping, if present, will match the portion of the license |
| description to check (if no grouping is present, then the whole portion |
| matched will be used). Defaults to \`/([^\\n\\r]*)/gv\`, i.e., the SPDX expression |
| is expected before any line breaks. |
| |
| Note that the \`/\` delimiters are optional, but necessary to add flags. |
| |
| Defaults to using the \`v\` flag, so to add your own flags, encapsulate |
| your expression as a string, but like a literal, e.g., \`/^mit$/vi\`.`, |
| type: 'string', |
| }, |
| numericOnlyVariation: { |
| description: `Whether to enable validation that \`@variation\` must be a number. Defaults to |
| \`false\`.`, |
| type: 'boolean', |
| }, |
| }, |
| type: 'object', |
| }, |
| ], |
| type: 'suggestion', |
| }, |
| }); |