Fix actions/tour
This commit is contained in:
34
node_modules/eslint-plugin-jsdoc/src/WarnSettings.js
generated
vendored
Normal file
34
node_modules/eslint-plugin-jsdoc/src/WarnSettings.js
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
const WarnSettings = function () {
|
||||
/** @type {WeakMap<object, Set<string>>} */
|
||||
const warnedSettings = new WeakMap();
|
||||
|
||||
return {
|
||||
/**
|
||||
* Warn only once for each context and setting
|
||||
* @param {{}} context
|
||||
* @param {string} setting
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasBeenWarned (context, setting) {
|
||||
return warnedSettings.has(context) && /** @type {Set<string>} */ (
|
||||
warnedSettings.get(context)
|
||||
).has(setting);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {{}} context
|
||||
* @param {string} setting
|
||||
* @returns {void}
|
||||
*/
|
||||
markSettingAsWarned (context, setting) {
|
||||
// c8 ignore else
|
||||
if (!warnedSettings.has(context)) {
|
||||
warnedSettings.set(context, new Set());
|
||||
}
|
||||
|
||||
/** @type {Set<string>} */ (warnedSettings.get(context)).add(setting);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default WarnSettings;
|
||||
358
node_modules/eslint-plugin-jsdoc/src/alignTransform.js
generated
vendored
Normal file
358
node_modules/eslint-plugin-jsdoc/src/alignTransform.js
generated
vendored
Normal file
@@ -0,0 +1,358 @@
|
||||
/**
|
||||
* Transform based on https://github.com/syavorsky/comment-parser/blob/master/src/transforms/align.ts
|
||||
*
|
||||
* It contains some customizations to align based on the tags, and some custom options.
|
||||
*/
|
||||
|
||||
import {
|
||||
// `comment-parser/primitives` export
|
||||
util,
|
||||
} from 'comment-parser';
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* hasNoTypes: boolean,
|
||||
* maxNamedTagLength: import('./iterateJsdoc.js').Integer,
|
||||
* maxUnnamedTagLength: import('./iterateJsdoc.js').Integer
|
||||
* }} TypelessInfo
|
||||
*/
|
||||
|
||||
const {
|
||||
rewireSource,
|
||||
} = util;
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* name: import('./iterateJsdoc.js').Integer,
|
||||
* start: import('./iterateJsdoc.js').Integer,
|
||||
* tag: import('./iterateJsdoc.js').Integer,
|
||||
* type: import('./iterateJsdoc.js').Integer
|
||||
* }} Width
|
||||
*/
|
||||
|
||||
/** @type {Width} */
|
||||
const zeroWidth = {
|
||||
name: 0,
|
||||
start: 0,
|
||||
tag: 0,
|
||||
type: 0,
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string[]} tags
|
||||
* @param {import('./iterateJsdoc.js').Integer} index
|
||||
* @param {import('comment-parser').Line[]} source
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const shouldAlign = (tags, index, source) => {
|
||||
const tag = source[index].tokens.tag.replace('@', '');
|
||||
const includesTag = tags.includes(tag);
|
||||
|
||||
if (includesTag) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tag !== '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let iterator = index; iterator >= 0; iterator--) {
|
||||
const previousTag = source[iterator].tokens.tag.replace('@', '');
|
||||
|
||||
if (previousTag !== '') {
|
||||
if (tags.includes(previousTag)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string[]} tags
|
||||
* @returns {(
|
||||
* width: Width,
|
||||
* line: {
|
||||
* tokens: import('comment-parser').Tokens
|
||||
* },
|
||||
* index: import('./iterateJsdoc.js').Integer,
|
||||
* source: import('comment-parser').Line[]
|
||||
* ) => Width}
|
||||
*/
|
||||
const getWidth = (tags) => {
|
||||
return (width, {
|
||||
tokens,
|
||||
}, index, source) => {
|
||||
if (!shouldAlign(tags, index, source)) {
|
||||
return width;
|
||||
}
|
||||
|
||||
return {
|
||||
name: Math.max(width.name, tokens.name.length),
|
||||
start: tokens.delimiter === '/**' ? tokens.start.length : width.start,
|
||||
tag: Math.max(width.tag, tokens.tag.length),
|
||||
type: Math.max(width.type, tokens.type.length),
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* description: string;
|
||||
* tags: import('comment-parser').Spec[];
|
||||
* problems: import('comment-parser').Problem[];
|
||||
* }} fields
|
||||
* @returns {TypelessInfo}
|
||||
*/
|
||||
const getTypelessInfo = (fields) => {
|
||||
const hasNoTypes = fields.tags.every(({
|
||||
type,
|
||||
}) => {
|
||||
return !type;
|
||||
});
|
||||
const maxNamedTagLength = Math.max(...fields.tags.map(({
|
||||
tag,
|
||||
name,
|
||||
}) => {
|
||||
return name.length === 0 ? -1 : tag.length;
|
||||
}).filter((length) => {
|
||||
return length !== -1;
|
||||
})) + 1;
|
||||
const maxUnnamedTagLength = Math.max(...fields.tags.map(({
|
||||
tag,
|
||||
name,
|
||||
}) => {
|
||||
return name.length === 0 ? tag.length : -1;
|
||||
}).filter((length) => {
|
||||
return length !== -1;
|
||||
})) + 1;
|
||||
return {
|
||||
hasNoTypes,
|
||||
maxNamedTagLength,
|
||||
maxUnnamedTagLength,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('./iterateJsdoc.js').Integer} len
|
||||
* @returns {string}
|
||||
*/
|
||||
const space = (len) => {
|
||||
return ''.padStart(len, ' ');
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* customSpacings: import('../src/rules/checkLineAlignment.js').CustomSpacings,
|
||||
* tags: string[],
|
||||
* indent: string,
|
||||
* preserveMainDescriptionPostDelimiter: boolean,
|
||||
* wrapIndent: string,
|
||||
* disableWrapIndent: boolean,
|
||||
* }} cfg
|
||||
* @returns {(
|
||||
* block: import('comment-parser').Block
|
||||
* ) => import('comment-parser').Block}
|
||||
*/
|
||||
const alignTransform = ({
|
||||
customSpacings,
|
||||
tags,
|
||||
indent,
|
||||
preserveMainDescriptionPostDelimiter,
|
||||
wrapIndent,
|
||||
disableWrapIndent,
|
||||
}) => {
|
||||
let intoTags = false;
|
||||
/** @type {Width} */
|
||||
let width;
|
||||
|
||||
/**
|
||||
* @param {import('comment-parser').Tokens} tokens
|
||||
* @param {TypelessInfo} typelessInfo
|
||||
* @returns {import('comment-parser').Tokens}
|
||||
*/
|
||||
const alignTokens = (tokens, typelessInfo) => {
|
||||
const nothingAfter = {
|
||||
delim: false,
|
||||
name: false,
|
||||
tag: false,
|
||||
type: false,
|
||||
};
|
||||
|
||||
if (tokens.description === '') {
|
||||
nothingAfter.name = true;
|
||||
tokens.postName = '';
|
||||
|
||||
if (tokens.name === '') {
|
||||
nothingAfter.type = true;
|
||||
tokens.postType = '';
|
||||
|
||||
if (tokens.type === '') {
|
||||
nothingAfter.tag = true;
|
||||
tokens.postTag = '';
|
||||
|
||||
/* c8 ignore next: Never happens because the !intoTags return. But it's here for consistency with the original align transform */
|
||||
if (tokens.tag === '') {
|
||||
nothingAfter.delim = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let untypedNameAdjustment = 0;
|
||||
let untypedTypeAdjustment = 0;
|
||||
if (typelessInfo.hasNoTypes) {
|
||||
nothingAfter.tag = true;
|
||||
tokens.postTag = '';
|
||||
if (tokens.name === '') {
|
||||
untypedNameAdjustment = typelessInfo.maxNamedTagLength - tokens.tag.length;
|
||||
} else {
|
||||
untypedNameAdjustment = typelessInfo.maxNamedTagLength > typelessInfo.maxUnnamedTagLength ? 0 :
|
||||
Math.max(0, typelessInfo.maxUnnamedTagLength - (tokens.tag.length + tokens.name.length + 1));
|
||||
untypedTypeAdjustment = typelessInfo.maxNamedTagLength - tokens.tag.length;
|
||||
}
|
||||
}
|
||||
|
||||
// Todo: Avoid fixing alignment of blocks with multiline wrapping of type
|
||||
if (tokens.tag === '' && tokens.type) {
|
||||
return tokens;
|
||||
}
|
||||
|
||||
const spacings = {
|
||||
postDelimiter: customSpacings?.postDelimiter || 1,
|
||||
postName: customSpacings?.postName || 1,
|
||||
postTag: customSpacings?.postTag || 1,
|
||||
postType: customSpacings?.postType || 1,
|
||||
};
|
||||
|
||||
tokens.postDelimiter = nothingAfter.delim ? '' : space(spacings.postDelimiter);
|
||||
|
||||
if (!nothingAfter.tag) {
|
||||
tokens.postTag = space(width.tag - tokens.tag.length + spacings.postTag);
|
||||
}
|
||||
|
||||
if (!nothingAfter.type) {
|
||||
tokens.postType = space(width.type - tokens.type.length + spacings.postType + untypedTypeAdjustment);
|
||||
}
|
||||
|
||||
if (!nothingAfter.name) {
|
||||
// If post name is empty for all lines (name width 0), don't add post name spacing.
|
||||
tokens.postName = width.name === 0 ? '' : space(width.name - tokens.name.length + spacings.postName + untypedNameAdjustment);
|
||||
}
|
||||
|
||||
return tokens;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('comment-parser').Line} line
|
||||
* @param {import('./iterateJsdoc.js').Integer} index
|
||||
* @param {import('comment-parser').Line[]} source
|
||||
* @param {TypelessInfo} typelessInfo
|
||||
* @param {string|false} indentTag
|
||||
* @returns {import('comment-parser').Line}
|
||||
*/
|
||||
const update = (line, index, source, typelessInfo, indentTag) => {
|
||||
/** @type {import('comment-parser').Tokens} */
|
||||
const tokens = {
|
||||
...line.tokens,
|
||||
};
|
||||
|
||||
if (tokens.tag !== '') {
|
||||
intoTags = true;
|
||||
}
|
||||
|
||||
const isEmpty =
|
||||
tokens.tag === '' &&
|
||||
tokens.name === '' &&
|
||||
tokens.type === '' &&
|
||||
tokens.description === '';
|
||||
|
||||
// dangling '*/'
|
||||
if (tokens.end === '*/' && isEmpty) {
|
||||
tokens.start = indent + ' ';
|
||||
|
||||
return {
|
||||
...line,
|
||||
tokens,
|
||||
};
|
||||
}
|
||||
|
||||
switch (tokens.delimiter) {
|
||||
case '/**':
|
||||
tokens.start = indent;
|
||||
break;
|
||||
case '*':
|
||||
tokens.start = indent + ' ';
|
||||
break;
|
||||
default:
|
||||
tokens.delimiter = '';
|
||||
|
||||
// compensate delimiter
|
||||
tokens.start = indent + ' ';
|
||||
}
|
||||
|
||||
if (!intoTags) {
|
||||
if (tokens.description === '') {
|
||||
tokens.postDelimiter = '';
|
||||
} else if (!preserveMainDescriptionPostDelimiter) {
|
||||
tokens.postDelimiter = ' ';
|
||||
}
|
||||
|
||||
return {
|
||||
...line,
|
||||
tokens,
|
||||
};
|
||||
}
|
||||
|
||||
const postHyphenSpacing = customSpacings?.postHyphen ?? 1;
|
||||
const hyphenSpacing = /^\s*-\s+/u;
|
||||
tokens.description = tokens.description.replace(
|
||||
hyphenSpacing, '-' + ''.padStart(postHyphenSpacing, ' '),
|
||||
);
|
||||
|
||||
// Not align.
|
||||
if (shouldAlign(tags, index, source)) {
|
||||
alignTokens(tokens, typelessInfo);
|
||||
if (!disableWrapIndent && indentTag) {
|
||||
tokens.postDelimiter += wrapIndent;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...line,
|
||||
tokens,
|
||||
};
|
||||
};
|
||||
|
||||
return ({
|
||||
source,
|
||||
...fields
|
||||
}) => {
|
||||
width = source.reduce(getWidth(tags), {
|
||||
...zeroWidth,
|
||||
});
|
||||
|
||||
const typelessInfo = getTypelessInfo(fields);
|
||||
|
||||
let tagIndentMode = false;
|
||||
|
||||
return rewireSource({
|
||||
...fields,
|
||||
source: source.map((line, index) => {
|
||||
const indentTag = !disableWrapIndent && tagIndentMode && !line.tokens.tag && line.tokens.description;
|
||||
const ret = update(line, index, source, typelessInfo, indentTag);
|
||||
|
||||
if (!disableWrapIndent && line.tokens.tag) {
|
||||
tagIndentMode = true;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}),
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export default alignTransform;
|
||||
169
node_modules/eslint-plugin-jsdoc/src/defaultTagOrder.js
generated
vendored
Normal file
169
node_modules/eslint-plugin-jsdoc/src/defaultTagOrder.js
generated
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
const defaultTagOrder = [
|
||||
{
|
||||
tags: [
|
||||
// Brief descriptions
|
||||
'summary',
|
||||
'typeSummary',
|
||||
|
||||
// Module/file-level
|
||||
'module',
|
||||
'exports',
|
||||
'file',
|
||||
'fileoverview',
|
||||
'overview',
|
||||
'import',
|
||||
|
||||
// Identifying (name, type)
|
||||
'template',
|
||||
'typedef',
|
||||
'interface',
|
||||
'record',
|
||||
'name',
|
||||
'kind',
|
||||
'type',
|
||||
'alias',
|
||||
'external',
|
||||
'host',
|
||||
'callback',
|
||||
'func',
|
||||
'function',
|
||||
'method',
|
||||
'class',
|
||||
'constructor',
|
||||
|
||||
// Relationships
|
||||
'modifies',
|
||||
'mixes',
|
||||
'mixin',
|
||||
'mixinClass',
|
||||
'mixinFunction',
|
||||
'namespace',
|
||||
'borrows',
|
||||
'constructs',
|
||||
'lends',
|
||||
'implements',
|
||||
'requires',
|
||||
|
||||
// Long descriptions
|
||||
'desc',
|
||||
'description',
|
||||
'classdesc',
|
||||
'tutorial',
|
||||
'copyright',
|
||||
'license',
|
||||
|
||||
// Simple annotations
|
||||
|
||||
// TypeScript
|
||||
'internal',
|
||||
'overload',
|
||||
|
||||
'const',
|
||||
'constant',
|
||||
'final',
|
||||
'global',
|
||||
'readonly',
|
||||
'abstract',
|
||||
'virtual',
|
||||
'var',
|
||||
'member',
|
||||
'memberof',
|
||||
'memberof!',
|
||||
'inner',
|
||||
'instance',
|
||||
'inheritdoc',
|
||||
'inheritDoc',
|
||||
'override',
|
||||
'hideconstructor',
|
||||
|
||||
// Core function/object info
|
||||
'param',
|
||||
'arg',
|
||||
'argument',
|
||||
'prop',
|
||||
'property',
|
||||
'return',
|
||||
'returns',
|
||||
|
||||
// Important behavior details
|
||||
'async',
|
||||
'generator',
|
||||
'default',
|
||||
'defaultvalue',
|
||||
'enum',
|
||||
'augments',
|
||||
'extends',
|
||||
'throws',
|
||||
'exception',
|
||||
'yield',
|
||||
'yields',
|
||||
'event',
|
||||
'fires',
|
||||
'emits',
|
||||
'listens',
|
||||
'this',
|
||||
|
||||
// TypeScript
|
||||
'satisfies',
|
||||
|
||||
// Access
|
||||
'static',
|
||||
'private',
|
||||
'protected',
|
||||
'public',
|
||||
'access',
|
||||
'package',
|
||||
|
||||
'-other',
|
||||
|
||||
// Supplementary descriptions
|
||||
'see',
|
||||
'example',
|
||||
|
||||
// METADATA
|
||||
|
||||
// Other Closure (undocumented) metadata
|
||||
'closurePrimitive',
|
||||
'customElement',
|
||||
'expose',
|
||||
'hidden',
|
||||
'idGenerator',
|
||||
'meaning',
|
||||
'ngInject',
|
||||
'owner',
|
||||
'wizaction',
|
||||
|
||||
// Other Closure (documented) metadata
|
||||
'define',
|
||||
'dict',
|
||||
'export',
|
||||
'externs',
|
||||
'implicitCast',
|
||||
'noalias',
|
||||
'nocollapse',
|
||||
'nocompile',
|
||||
'noinline',
|
||||
'nosideeffects',
|
||||
'polymer',
|
||||
'polymerBehavior',
|
||||
'preserve',
|
||||
'struct',
|
||||
'suppress',
|
||||
'unrestricted',
|
||||
|
||||
// @homer0/prettier-plugin-jsdoc metadata
|
||||
'category',
|
||||
|
||||
// Non-Closure metadata
|
||||
'ignore',
|
||||
'author',
|
||||
'version',
|
||||
'variation',
|
||||
'since',
|
||||
'deprecated',
|
||||
'todo',
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default defaultTagOrder;
|
||||
964
node_modules/eslint-plugin-jsdoc/src/exportParser.js
generated
vendored
Normal file
964
node_modules/eslint-plugin-jsdoc/src/exportParser.js
generated
vendored
Normal file
@@ -0,0 +1,964 @@
|
||||
import {
|
||||
findJSDocComment,
|
||||
} from '@es-joy/jsdoccomment';
|
||||
import debugModule from 'debug';
|
||||
|
||||
const debug = debugModule('requireExportJsdoc');
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* value: string
|
||||
* }} ValueObject
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* type?: string,
|
||||
* value?: ValueObject|import('eslint').Rule.Node,
|
||||
* props: {
|
||||
* [key: string]: CreatedNode|null,
|
||||
* },
|
||||
* special?: true,
|
||||
* globalVars?: CreatedNode,
|
||||
* exported?: boolean,
|
||||
* ANONYMOUS_DEFAULT?: import('eslint').Rule.Node
|
||||
* }} CreatedNode
|
||||
*/
|
||||
|
||||
/**
|
||||
* @returns {CreatedNode}
|
||||
*/
|
||||
const createNode = function () {
|
||||
return {
|
||||
props: {},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {CreatedNode|null} symbol
|
||||
* @returns {string|null}
|
||||
*/
|
||||
const getSymbolValue = function (symbol) {
|
||||
/* c8 ignore next 3 */
|
||||
if (!symbol) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/* c8 ignore else */
|
||||
if (symbol.type === 'literal') {
|
||||
return /** @type {ValueObject} */ (symbol.value).value;
|
||||
}
|
||||
/* c8 ignore next */
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('estree').Identifier} node
|
||||
* @param {CreatedNode} globals
|
||||
* @param {CreatedNode} scope
|
||||
* @param {SymbolOptions} opts
|
||||
* @returns {CreatedNode|null}
|
||||
*/
|
||||
const getIdentifier = function (node, globals, scope, opts) {
|
||||
if (opts.simpleIdentifier) {
|
||||
// Type is Identier for noncomputed properties
|
||||
const identifierLiteral = createNode();
|
||||
identifierLiteral.type = 'literal';
|
||||
identifierLiteral.value = {
|
||||
value: node.name,
|
||||
};
|
||||
|
||||
return identifierLiteral;
|
||||
}
|
||||
|
||||
/* c8 ignore next */
|
||||
const block = scope || globals;
|
||||
|
||||
// As scopes are not currently supported, they are not traversed upwards recursively
|
||||
if (block.props[node.name]) {
|
||||
return block.props[node.name];
|
||||
}
|
||||
|
||||
// Seems this will only be entered once scopes added and entered
|
||||
/* c8 ignore next 3 */
|
||||
if (globals.props[node.name]) {
|
||||
return globals.props[node.name];
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* @callback CreateSymbol
|
||||
* @param {import('eslint').Rule.Node|null} node
|
||||
* @param {CreatedNode} globals
|
||||
* @param {import('eslint').Rule.Node|null} value
|
||||
* @param {CreatedNode} [scope]
|
||||
* @param {boolean|SymbolOptions} [isGlobal]
|
||||
* @returns {CreatedNode|null}
|
||||
*/
|
||||
|
||||
/** @type {CreateSymbol} */
|
||||
let createSymbol; // eslint-disable-line prefer-const
|
||||
|
||||
/* eslint-disable complexity -- Temporary */
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* simpleIdentifier?: boolean
|
||||
* }} SymbolOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('eslint').Rule.Node} node
|
||||
* @param {CreatedNode} globals
|
||||
* @param {CreatedNode} scope
|
||||
* @param {SymbolOptions} [opt]
|
||||
* @returns {CreatedNode|null}
|
||||
*/
|
||||
const getSymbol = function (node, globals, scope, opt) {
|
||||
/* eslint-enable complexity -- Temporary */
|
||||
const opts = opt || {};
|
||||
/* c8 ignore next */
|
||||
switch (node.type) {
|
||||
case 'Identifier': {
|
||||
return getIdentifier(node, globals, scope, opts);
|
||||
}
|
||||
|
||||
case 'MemberExpression': {
|
||||
const obj = getSymbol(
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(node.object),
|
||||
globals,
|
||||
scope,
|
||||
opts,
|
||||
);
|
||||
const propertySymbol = getSymbol(
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(node.property),
|
||||
globals,
|
||||
scope,
|
||||
{
|
||||
simpleIdentifier: !node.computed,
|
||||
},
|
||||
);
|
||||
const propertyValue = getSymbolValue(propertySymbol);
|
||||
|
||||
/* c8 ignore else */
|
||||
if (obj && propertyValue && obj.props[propertyValue]) {
|
||||
const block = obj.props[propertyValue];
|
||||
|
||||
return block;
|
||||
}
|
||||
/* c8 ignore next 10 */
|
||||
/*
|
||||
if (opts.createMissingProps && propertyValue) {
|
||||
obj.props[propertyValue] = createNode();
|
||||
|
||||
return obj.props[propertyValue];
|
||||
}
|
||||
*/
|
||||
debug(`MemberExpression: Missing property ${
|
||||
/** @type {import('estree').PrivateIdentifier} */ (node.property).name
|
||||
}`);
|
||||
/* c8 ignore next 2 */
|
||||
return null;
|
||||
}
|
||||
|
||||
case 'ClassExpression': {
|
||||
return getSymbol(
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(node.body),
|
||||
globals,
|
||||
scope,
|
||||
opts,
|
||||
);
|
||||
}
|
||||
|
||||
/* c8 ignore next 7 -- No longer needed? */
|
||||
// @ts-expect-error TS OK
|
||||
case 'TSTypeAliasDeclaration':
|
||||
// @ts-expect-error TS OK
|
||||
// Fallthrough
|
||||
case 'TSEnumDeclaration':
|
||||
// @ts-expect-error TS OK
|
||||
case 'TSInterfaceDeclaration':
|
||||
case 'ClassDeclaration':
|
||||
case 'FunctionExpression': case 'FunctionDeclaration':
|
||||
case 'ArrowFunctionExpression': {
|
||||
const val = createNode();
|
||||
val.props.prototype = createNode();
|
||||
val.props.prototype.type = 'object';
|
||||
val.type = 'object';
|
||||
val.value = node;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
case 'AssignmentExpression': {
|
||||
return createSymbol(
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(node.left),
|
||||
globals,
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(node.right),
|
||||
scope,
|
||||
opts,
|
||||
);
|
||||
}
|
||||
|
||||
case 'ClassBody': {
|
||||
const val = createNode();
|
||||
for (const method of node.body) {
|
||||
val.props[
|
||||
/** @type {import('estree').Identifier} */ (
|
||||
/** @type {import('estree').MethodDefinition} */ (
|
||||
method
|
||||
).key
|
||||
).name
|
||||
] = createNode();
|
||||
/** @type {{[key: string]: CreatedNode}} */ (val.props)[
|
||||
/** @type {import('estree').Identifier} */ (
|
||||
/** @type {import('estree').MethodDefinition} */ (
|
||||
method
|
||||
).key
|
||||
).name
|
||||
].type = 'object';
|
||||
/** @type {{[key: string]: CreatedNode}} */ (val.props)[
|
||||
/** @type {import('estree').Identifier} */ (
|
||||
/** @type {import('estree').MethodDefinition} */ (
|
||||
method
|
||||
).key
|
||||
).name
|
||||
].value = /** @type {import('eslint').Rule.Node} */ (
|
||||
/** @type {import('estree').MethodDefinition} */ (method).value
|
||||
);
|
||||
}
|
||||
|
||||
val.type = 'object';
|
||||
val.value = node.parent;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
case 'ObjectExpression': {
|
||||
const val = createNode();
|
||||
val.type = 'object';
|
||||
for (const prop of node.properties) {
|
||||
if ([
|
||||
// typescript-eslint, espree, acorn, etc.
|
||||
'SpreadElement',
|
||||
|
||||
// @babel/eslint-parser
|
||||
'ExperimentalSpreadProperty',
|
||||
].includes(prop.type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const propVal = getSymbol(
|
||||
/** @type {import('eslint').Rule.Node} */ (
|
||||
/** @type {import('estree').Property} */
|
||||
(prop).value
|
||||
),
|
||||
globals,
|
||||
scope,
|
||||
opts,
|
||||
);
|
||||
/* c8 ignore next 8 */
|
||||
if (propVal) {
|
||||
val.props[
|
||||
/** @type {import('estree').PrivateIdentifier} */
|
||||
(
|
||||
/** @type {import('estree').Property} */ (prop).key
|
||||
).name
|
||||
] = propVal;
|
||||
}
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
case 'Literal': {
|
||||
const val = createNode();
|
||||
val.type = 'literal';
|
||||
val.value = node;
|
||||
|
||||
return val;
|
||||
}
|
||||
}
|
||||
/* c8 ignore next */
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CreatedNode} block
|
||||
* @param {string} name
|
||||
* @param {CreatedNode|null} value
|
||||
* @param {CreatedNode} globals
|
||||
* @param {boolean|SymbolOptions|undefined} isGlobal
|
||||
* @returns {void}
|
||||
*/
|
||||
const createBlockSymbol = function (block, name, value, globals, isGlobal) {
|
||||
block.props[name] = value;
|
||||
if (isGlobal && globals.props.window && globals.props.window.special) {
|
||||
globals.props.window.props[name] = value;
|
||||
}
|
||||
};
|
||||
|
||||
createSymbol = function (node, globals, value, scope, isGlobal) {
|
||||
const block = scope || globals;
|
||||
/* c8 ignore next 3 */
|
||||
if (!node) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let symbol;
|
||||
switch (node.type) {
|
||||
case 'FunctionDeclaration':
|
||||
/* c8 ignore next */
|
||||
// @ts-expect-error TS OK
|
||||
// Fall through
|
||||
case 'TSEnumDeclaration': case 'TSInterfaceDeclaration':
|
||||
/* c8 ignore next */
|
||||
// @ts-expect-error TS OK
|
||||
// Fall through
|
||||
case 'TSTypeAliasDeclaration': case 'ClassDeclaration': {
|
||||
const nde = /** @type {import('estree').ClassDeclaration} */ (node);
|
||||
/* c8 ignore else */
|
||||
if (nde.id && nde.id.type === 'Identifier') {
|
||||
return createSymbol(
|
||||
/** @type {import('eslint').Rule.Node} */ (nde.id),
|
||||
globals,
|
||||
node,
|
||||
globals,
|
||||
);
|
||||
}
|
||||
/* c8 ignore next 2 */
|
||||
break;
|
||||
}
|
||||
|
||||
case 'Identifier': {
|
||||
const nde = /** @type {import('estree').Identifier} */ (node);
|
||||
if (value) {
|
||||
const valueSymbol = getSymbol(value, globals, block);
|
||||
/* c8 ignore else */
|
||||
if (valueSymbol) {
|
||||
createBlockSymbol(block, nde.name, valueSymbol, globals, isGlobal);
|
||||
|
||||
return block.props[nde.name];
|
||||
}
|
||||
/* c8 ignore next */
|
||||
debug('Identifier: Missing value symbol for %s', nde.name);
|
||||
} else {
|
||||
createBlockSymbol(block, nde.name, createNode(), globals, isGlobal);
|
||||
|
||||
return block.props[nde.name];
|
||||
}
|
||||
/* c8 ignore next 2 */
|
||||
break;
|
||||
}
|
||||
|
||||
case 'MemberExpression': {
|
||||
const nde = /** @type {import('estree').MemberExpression} */ (node);
|
||||
symbol = getSymbol(
|
||||
/** @type {import('eslint').Rule.Node} */ (nde.object), globals, block,
|
||||
);
|
||||
|
||||
const propertySymbol = getSymbol(
|
||||
/** @type {import('eslint').Rule.Node} */ (nde.property),
|
||||
globals,
|
||||
block,
|
||||
{
|
||||
simpleIdentifier: !nde.computed,
|
||||
},
|
||||
);
|
||||
const propertyValue = getSymbolValue(propertySymbol);
|
||||
if (symbol && propertyValue) {
|
||||
createBlockSymbol(symbol, propertyValue, getSymbol(
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(value), globals, block,
|
||||
), globals, isGlobal);
|
||||
return symbol.props[propertyValue];
|
||||
}
|
||||
|
||||
debug(
|
||||
'MemberExpression: Missing symbol: %s',
|
||||
/** @type {import('estree').Identifier} */ (
|
||||
nde.property
|
||||
).name,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates variables from variable definitions
|
||||
* @param {import('eslint').Rule.Node} node
|
||||
* @param {CreatedNode} globals
|
||||
* @param {import('./rules/requireJsdoc.js').RequireJsdocOpts} opts
|
||||
* @returns {void}
|
||||
*/
|
||||
const initVariables = function (node, globals, opts) {
|
||||
switch (node.type) {
|
||||
case 'Program': {
|
||||
for (const childNode of node.body) {
|
||||
initVariables(
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(childNode),
|
||||
globals,
|
||||
opts,
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'ExpressionStatement': {
|
||||
initVariables(
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(node.expression),
|
||||
globals,
|
||||
opts,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'VariableDeclaration': {
|
||||
for (const declaration of node.declarations) {
|
||||
// let and const
|
||||
const symbol = createSymbol(
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(declaration.id),
|
||||
globals,
|
||||
null,
|
||||
globals,
|
||||
);
|
||||
if (opts.initWindow && node.kind === 'var' && globals.props.window) {
|
||||
// If var, also add to window
|
||||
globals.props.window.props[
|
||||
/** @type {import('estree').Identifier} */
|
||||
(declaration.id).name
|
||||
] = symbol;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'ExportNamedDeclaration': {
|
||||
if (node.declaration) {
|
||||
initVariables(
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(node.declaration),
|
||||
globals,
|
||||
opts,
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* eslint-disable complexity -- Temporary */
|
||||
|
||||
/**
|
||||
* Populates variable maps using AST
|
||||
* @param {import('eslint').Rule.Node} node
|
||||
* @param {CreatedNode} globals
|
||||
* @param {import('./rules/requireJsdoc.js').RequireJsdocOpts} opt
|
||||
* @param {true} [isExport]
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const mapVariables = function (node, globals, opt, isExport) {
|
||||
/* eslint-enable complexity -- Temporary */
|
||||
/* c8 ignore next */
|
||||
const opts = opt || {};
|
||||
/* c8 ignore next */
|
||||
switch (node.type) {
|
||||
case 'Program': {
|
||||
if (opts.ancestorsOnly) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const childNode of node.body) {
|
||||
mapVariables(
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(childNode),
|
||||
globals,
|
||||
opts,
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'ExpressionStatement': {
|
||||
mapVariables(
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(node.expression),
|
||||
globals,
|
||||
opts,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'AssignmentExpression': {
|
||||
createSymbol(
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(node.left),
|
||||
globals,
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(node.right),
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'VariableDeclaration': {
|
||||
for (const declaration of node.declarations) {
|
||||
const isGlobal = Boolean(opts.initWindow && node.kind === 'var' && globals.props.window);
|
||||
const symbol = createSymbol(
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(declaration.id),
|
||||
globals,
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(declaration.init),
|
||||
globals,
|
||||
isGlobal,
|
||||
);
|
||||
if (symbol && isExport) {
|
||||
symbol.exported = true;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'FunctionDeclaration': {
|
||||
/* c8 ignore next 10 */
|
||||
if (/** @type {import('estree').Identifier} */ (node.id).type === 'Identifier') {
|
||||
createSymbol(
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(node.id),
|
||||
globals,
|
||||
node,
|
||||
globals,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'ExportDefaultDeclaration': {
|
||||
const symbol = createSymbol(
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(node.declaration),
|
||||
globals,
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(node.declaration),
|
||||
);
|
||||
if (symbol) {
|
||||
symbol.exported = true;
|
||||
/* c8 ignore next 6 */
|
||||
} else {
|
||||
// if (!node.id) {
|
||||
globals.ANONYMOUS_DEFAULT = /** @type {import('eslint').Rule.Node} */ (
|
||||
node.declaration
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'ExportNamedDeclaration': {
|
||||
if (node.declaration) {
|
||||
if (node.declaration.type === 'VariableDeclaration') {
|
||||
mapVariables(
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(node.declaration),
|
||||
globals,
|
||||
opts,
|
||||
true,
|
||||
);
|
||||
} else {
|
||||
const symbol = createSymbol(
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(node.declaration),
|
||||
globals,
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(node.declaration),
|
||||
);
|
||||
/* c8 ignore next 3 */
|
||||
if (symbol) {
|
||||
symbol.exported = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const specifier of node.specifiers) {
|
||||
mapVariables(
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(specifier),
|
||||
globals,
|
||||
opts,
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'ExportSpecifier': {
|
||||
const symbol = getSymbol(
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(node.local),
|
||||
globals,
|
||||
globals,
|
||||
);
|
||||
/* c8 ignore next 3 */
|
||||
if (symbol) {
|
||||
symbol.exported = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'ClassDeclaration': {
|
||||
createSymbol(
|
||||
/** @type {import('eslint').Rule.Node|null} */ (node.id),
|
||||
globals,
|
||||
/** @type {import('eslint').Rule.Node} */ (node.body),
|
||||
globals,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
/* c8 ignore next */
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('eslint').Rule.Node} node
|
||||
* @param {CreatedNode|ValueObject|string|undefined|
|
||||
* import('eslint').Rule.Node} block
|
||||
* @param {(CreatedNode|ValueObject|string|
|
||||
* import('eslint').Rule.Node)[]} [cache]
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const findNode = function (node, block, cache) {
|
||||
let blockCache = cache || [];
|
||||
if (!block || blockCache.includes(block)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
blockCache = blockCache.slice();
|
||||
blockCache.push(block);
|
||||
|
||||
if (
|
||||
typeof block === 'object' &&
|
||||
'type' in block &&
|
||||
(block.type === 'object' || block.type === 'MethodDefinition') &&
|
||||
block.value === node
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof block !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const props = ('props' in block && block.props) || ('body' in block && block.body);
|
||||
for (const propval of Object.values(props || {})) {
|
||||
if (Array.isArray(propval)) {
|
||||
/* c8 ignore next 5 */
|
||||
if (propval.some((val) => {
|
||||
return findNode(node, val, blockCache);
|
||||
})) {
|
||||
return true;
|
||||
}
|
||||
} else if (findNode(node, propval, blockCache)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const exportTypes = new Set([
|
||||
'ExportNamedDeclaration', 'ExportDefaultDeclaration',
|
||||
]);
|
||||
const ignorableNestedTypes = new Set([
|
||||
'FunctionDeclaration', 'ArrowFunctionExpression', 'FunctionExpression',
|
||||
]);
|
||||
|
||||
/**
|
||||
* @param {import('eslint').Rule.Node} nde
|
||||
* @returns {import('eslint').Rule.Node|false}
|
||||
*/
|
||||
const getExportAncestor = function (nde) {
|
||||
let node = nde;
|
||||
let idx = 0;
|
||||
const ignorableIfDeep = ignorableNestedTypes.has(nde?.type);
|
||||
while (node) {
|
||||
// Ignore functions nested more deeply than say `export default function () {}`
|
||||
if (idx >= 2 && ignorableIfDeep) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (exportTypes.has(node.type)) {
|
||||
return node;
|
||||
}
|
||||
|
||||
node = node.parent;
|
||||
idx++;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const canBeExportedByAncestorType = new Set([
|
||||
'TSPropertySignature',
|
||||
'TSMethodSignature',
|
||||
'ClassProperty',
|
||||
'PropertyDefinition',
|
||||
'Method',
|
||||
]);
|
||||
|
||||
const canExportChildrenType = new Set([
|
||||
'TSInterfaceBody',
|
||||
'TSInterfaceDeclaration',
|
||||
'TSTypeLiteral',
|
||||
'TSTypeAliasDeclaration',
|
||||
'TSTypeParameterInstantiation',
|
||||
'TSTypeReference',
|
||||
'ClassDeclaration',
|
||||
'ClassBody',
|
||||
'ClassDefinition',
|
||||
'ClassExpression',
|
||||
'Program',
|
||||
]);
|
||||
|
||||
/**
|
||||
* @param {import('eslint').Rule.Node} nde
|
||||
* @returns {false|import('eslint').Rule.Node}
|
||||
*/
|
||||
const isExportByAncestor = function (nde) {
|
||||
if (!canBeExportedByAncestorType.has(nde.type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let node = nde.parent;
|
||||
while (node) {
|
||||
if (exportTypes.has(node.type)) {
|
||||
return node;
|
||||
}
|
||||
|
||||
if (!canExportChildrenType.has(node.type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
node = node.parent;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {CreatedNode} block
|
||||
* @param {import('eslint').Rule.Node} node
|
||||
* @param {CreatedNode[]} [cache] Currently unused
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const findExportedNode = function (block, node, cache) {
|
||||
/* c8 ignore next 3 */
|
||||
if (block === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const blockCache = cache || [];
|
||||
const {
|
||||
props,
|
||||
} = block;
|
||||
for (const propval of Object.values(props)) {
|
||||
const pval = /** @type {CreatedNode} */ (propval);
|
||||
blockCache.push(pval);
|
||||
if (pval.exported && (node === pval.value || findNode(node, pval.value))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// No need to check `propval` for exported nodes as ESM
|
||||
// exports are only global
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('eslint').Rule.Node} node
|
||||
* @param {CreatedNode} globals
|
||||
* @param {import('./rules/requireJsdoc.js').RequireJsdocOpts} opt
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const isNodeExported = function (node, globals, opt) {
|
||||
const moduleExports = globals.props.module?.props?.exports;
|
||||
if (
|
||||
opt.initModuleExports && moduleExports && findNode(node, moduleExports)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (opt.initWindow && globals.props.window && findNode(node, globals.props.window)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (opt.esm && findExportedNode(globals, node)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('eslint').Rule.Node} node
|
||||
* @param {CreatedNode} globalVars
|
||||
* @param {import('./rules/requireJsdoc.js').RequireJsdocOpts} opts
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const parseRecursive = function (node, globalVars, opts) {
|
||||
// Iterate from top using recursion - stop at first processed node from top
|
||||
if (node.parent && parseRecursive(node.parent, globalVars, opts)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return mapVariables(node, globalVars, opts);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('eslint').Rule.Node} ast
|
||||
* @param {import('eslint').Rule.Node} node
|
||||
* @param {import('./rules/requireJsdoc.js').RequireJsdocOpts} opt
|
||||
* @returns {CreatedNode}
|
||||
*/
|
||||
const parse = function (ast, node, opt) {
|
||||
/* c8 ignore next 6 */
|
||||
const opts = opt || {
|
||||
ancestorsOnly: false,
|
||||
esm: true,
|
||||
initModuleExports: true,
|
||||
initWindow: true,
|
||||
};
|
||||
|
||||
const globalVars = createNode();
|
||||
if (opts.initModuleExports) {
|
||||
globalVars.props.module = createNode();
|
||||
globalVars.props.module.props.exports = createNode();
|
||||
globalVars.props.exports = globalVars.props.module.props.exports;
|
||||
}
|
||||
|
||||
if (opts.initWindow) {
|
||||
globalVars.props.window = createNode();
|
||||
globalVars.props.window.special = true;
|
||||
}
|
||||
|
||||
if (opts.ancestorsOnly) {
|
||||
parseRecursive(node, globalVars, opts);
|
||||
} else {
|
||||
initVariables(ast, globalVars, opts);
|
||||
mapVariables(ast, globalVars, opts);
|
||||
}
|
||||
|
||||
return {
|
||||
globalVars,
|
||||
props: {},
|
||||
};
|
||||
};
|
||||
|
||||
const accessibilityNodes = new Set([
|
||||
'PropertyDefinition',
|
||||
'MethodDefinition',
|
||||
]);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('eslint').Rule.Node} node
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const isPrivate = (node) => {
|
||||
return accessibilityNodes.has(node.type) &&
|
||||
(
|
||||
'accessibility' in node &&
|
||||
node.accessibility !== 'public' && node.accessibility !== undefined
|
||||
) ||
|
||||
'key' in node &&
|
||||
node.key.type === 'PrivateIdentifier';
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('eslint').Rule.Node} node
|
||||
* @param {import('eslint').SourceCode} sourceCode
|
||||
* @param {import('./rules/requireJsdoc.js').RequireJsdocOpts} opt
|
||||
* @param {import('./iterateJsdoc.js').Settings} settings
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const isUncommentedExport = function (node, sourceCode, opt, settings) {
|
||||
// console.log({node});
|
||||
// Optimize with ancestor check for esm
|
||||
if (opt.esm) {
|
||||
if (isPrivate(node) ||
|
||||
node.parent && isPrivate(node.parent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const exportNode = getExportAncestor(node);
|
||||
|
||||
// Is export node comment
|
||||
if (exportNode && !findJSDocComment(exportNode, sourceCode, settings)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Some typescript types are not in variable map, but inherit exported (interface property and method)
|
||||
*/
|
||||
if (
|
||||
isExportByAncestor(node) &&
|
||||
!findJSDocComment(node, sourceCode, settings)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const ast = /** @type {unknown} */ (sourceCode.ast);
|
||||
|
||||
const parseResult = parse(
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(ast),
|
||||
node,
|
||||
opt,
|
||||
);
|
||||
|
||||
return isNodeExported(
|
||||
node, /** @type {CreatedNode} */ (parseResult.globalVars), opt,
|
||||
);
|
||||
};
|
||||
|
||||
export default {
|
||||
isUncommentedExport,
|
||||
parse,
|
||||
};
|
||||
969
node_modules/eslint-plugin-jsdoc/src/getDefaultTagStructureForMode.js
generated
vendored
Normal file
969
node_modules/eslint-plugin-jsdoc/src/getDefaultTagStructureForMode.js
generated
vendored
Normal file
@@ -0,0 +1,969 @@
|
||||
/**
|
||||
* @typedef {Map<string, Map<string, (string|boolean)>>} TagStructure
|
||||
*/
|
||||
/**
|
||||
* @param {import('./jsdocUtils.js').ParserMode} mode
|
||||
* @returns {TagStructure}
|
||||
*/
|
||||
const getDefaultTagStructureForMode = (mode) => {
|
||||
const isJsdoc = mode === 'jsdoc';
|
||||
const isClosure = mode === 'closure';
|
||||
const isTypescript = mode === 'typescript';
|
||||
const isPermissive = mode === 'permissive';
|
||||
|
||||
const isJsdocOrPermissive = isJsdoc || isPermissive;
|
||||
const isJsdocOrTypescript = isJsdoc || isTypescript;
|
||||
const isTypescriptOrClosure = isTypescript || isClosure;
|
||||
const isClosureOrPermissive = isClosure || isPermissive;
|
||||
const isJsdocTypescriptOrPermissive = isJsdocOrTypescript || isPermissive;
|
||||
|
||||
// Properties:
|
||||
// `namepathRole` - 'namepath-referencing'|'namepath-defining'|'namepath-or-url-referencing'|'text'|false
|
||||
// `typeAllowed` - boolean
|
||||
// `nameRequired` - boolean
|
||||
// `typeRequired` - boolean
|
||||
// `typeOrNameRequired` - boolean
|
||||
|
||||
// All of `typeAllowed` have a signature with "type" except for
|
||||
// `augments`/`extends` ("namepath")
|
||||
// `param`/`arg`/`argument` (no signature)
|
||||
// `property`/`prop` (no signature)
|
||||
// `modifies` (undocumented)
|
||||
|
||||
// None of the `namepathRole: 'namepath-defining'` show as having curly
|
||||
// brackets for their name/namepath
|
||||
|
||||
// Among `namepath-defining` and `namepath-referencing`, these do not seem
|
||||
// to allow curly brackets in their doc signature or examples (`modifies`
|
||||
// references namepaths within its type brackets and `param` is
|
||||
// name-defining but not namepath-defining, so not part of these groups)
|
||||
|
||||
// Todo: Should support special processing for "name" as distinct from
|
||||
// "namepath" (e.g., param can't define a namepath)
|
||||
|
||||
// Todo: Should support a `tutorialID` type (for `@tutorial` block and
|
||||
// inline)
|
||||
|
||||
/**
|
||||
* @type {TagStructure}
|
||||
*/
|
||||
return new Map([
|
||||
[
|
||||
'alias', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Signature seems to require a "namepath" (and no counter-examples)
|
||||
[
|
||||
'namepathRole', 'namepath-defining',
|
||||
],
|
||||
|
||||
// "namepath"
|
||||
[
|
||||
'typeOrNameRequired', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'arg', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
[
|
||||
'namepathRole', 'namepath-defining',
|
||||
],
|
||||
|
||||
// See `param`
|
||||
[
|
||||
'nameRequired', true,
|
||||
],
|
||||
|
||||
// Has no formal signature in the docs but shows curly brackets
|
||||
// in the examples
|
||||
[
|
||||
'typeAllowed', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'argument', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
[
|
||||
'namepathRole', 'namepath-defining',
|
||||
],
|
||||
|
||||
// See `param`
|
||||
[
|
||||
'nameRequired', true,
|
||||
],
|
||||
|
||||
// Has no formal signature in the docs but shows curly brackets
|
||||
// in the examples
|
||||
[
|
||||
'typeAllowed', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'augments', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Signature seems to require a "namepath" (and no counter-examples)
|
||||
[
|
||||
'namepathRole', 'namepath-referencing',
|
||||
],
|
||||
|
||||
// Does not show curly brackets in either the signature or examples
|
||||
[
|
||||
'typeAllowed', true,
|
||||
],
|
||||
|
||||
// "namepath"
|
||||
[
|
||||
'typeOrNameRequired', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'borrows', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// `borrows` has a different format, however, so needs special parsing;
|
||||
// seems to require both, and as "namepath"'s
|
||||
[
|
||||
'namepathRole', 'namepath-referencing',
|
||||
],
|
||||
|
||||
// "namepath"
|
||||
[
|
||||
'typeOrNameRequired', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'callback', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Seems to require a "namepath" in the signature (with no
|
||||
// counter-examples); TypeScript does not enforce but seems
|
||||
// problematic as not attached so presumably not useable without it
|
||||
[
|
||||
'namepathRole', 'namepath-defining',
|
||||
],
|
||||
|
||||
// "namepath"
|
||||
[
|
||||
'nameRequired', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'class', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Allows for "name"'s in signature, but indicated as optional
|
||||
[
|
||||
'namepathRole', 'namepath-defining',
|
||||
],
|
||||
|
||||
// Not in use, but should be this value if using to power `empty-tags`
|
||||
[
|
||||
'nameAllowed', true,
|
||||
],
|
||||
|
||||
[
|
||||
'typeAllowed', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'const', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Allows for "name"'s in signature, but indicated as optional
|
||||
[
|
||||
'namepathRole', 'namepath-defining',
|
||||
],
|
||||
|
||||
[
|
||||
'typeAllowed', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
[
|
||||
'constant', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Allows for "name"'s in signature, but indicated as optional
|
||||
[
|
||||
'namepathRole', 'namepath-defining',
|
||||
],
|
||||
|
||||
[
|
||||
'typeAllowed', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
[
|
||||
'constructor', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Allows for "name"'s in signature, but indicated as optional
|
||||
[
|
||||
'namepathRole', 'namepath-defining',
|
||||
],
|
||||
|
||||
[
|
||||
'typeAllowed', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'constructs', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Allows for "name"'s in signature, but indicated as optional
|
||||
[
|
||||
'namepathRole', 'namepath-defining',
|
||||
],
|
||||
|
||||
[
|
||||
'nameRequired', false,
|
||||
],
|
||||
|
||||
[
|
||||
'typeAllowed', false,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'define', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
[
|
||||
'typeRequired', isClosure,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'emits', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Signature seems to require a "name" (of an event) and no counter-examples
|
||||
[
|
||||
'namepathRole', 'namepath-referencing',
|
||||
],
|
||||
|
||||
[
|
||||
'nameRequired', true,
|
||||
],
|
||||
|
||||
[
|
||||
'typeAllowed', false,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'enum', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Has example showing curly brackets but not in doc signature
|
||||
[
|
||||
'typeAllowed', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'event', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// The doc signature of `event` seems to require a "name"
|
||||
[
|
||||
'nameRequired', true,
|
||||
],
|
||||
|
||||
// Appears to require a "name" in its signature, albeit somewhat
|
||||
// different from other "name"'s (including as described
|
||||
// at https://jsdoc.app/about-namepaths.html )
|
||||
[
|
||||
'namepathRole', 'namepath-defining',
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'exception', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Shows curly brackets in the signature and in the examples
|
||||
[
|
||||
'typeAllowed', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
// Closure
|
||||
[
|
||||
'export', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
[
|
||||
'typeAllowed', isClosureOrPermissive,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'exports', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
[
|
||||
'namepathRole', 'namepath-defining',
|
||||
],
|
||||
|
||||
[
|
||||
'nameRequired', isJsdoc,
|
||||
],
|
||||
|
||||
[
|
||||
'typeAllowed', isClosureOrPermissive,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'extends', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Signature seems to require a "namepath" (and no counter-examples)
|
||||
[
|
||||
'namepathRole', 'namepath-referencing',
|
||||
],
|
||||
|
||||
// Does not show curly brackets in either the signature or examples
|
||||
[
|
||||
'typeAllowed', isTypescriptOrClosure || isPermissive,
|
||||
],
|
||||
|
||||
[
|
||||
'nameRequired', isJsdoc,
|
||||
],
|
||||
|
||||
// "namepath"
|
||||
[
|
||||
'typeOrNameRequired', isTypescriptOrClosure || isPermissive,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'external', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Appears to require a "name" in its signature, albeit somewhat
|
||||
// different from other "name"'s (including as described
|
||||
// at https://jsdoc.app/about-namepaths.html )
|
||||
[
|
||||
'namepathRole', 'namepath-defining',
|
||||
],
|
||||
|
||||
// "name" (and a special syntax for the `external` name)
|
||||
[
|
||||
'nameRequired', true,
|
||||
],
|
||||
|
||||
[
|
||||
'typeAllowed', false,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'fires', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Signature seems to require a "name" (of an event) and no
|
||||
// counter-examples
|
||||
[
|
||||
'namepathRole', 'namepath-referencing',
|
||||
],
|
||||
|
||||
[
|
||||
'nameRequired', true,
|
||||
],
|
||||
|
||||
[
|
||||
'typeAllowed', false,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'function', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Allows for "name"'s in signature, but indicated as optional
|
||||
[
|
||||
'namepathRole', 'namepath-defining',
|
||||
],
|
||||
|
||||
[
|
||||
'nameRequired', false,
|
||||
],
|
||||
|
||||
[
|
||||
'typeAllowed', false,
|
||||
],
|
||||
])),
|
||||
],
|
||||
[
|
||||
'func', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Allows for "name"'s in signature, but indicated as optional
|
||||
[
|
||||
'namepathRole', 'namepath-defining',
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'host', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Appears to require a "name" in its signature, albeit somewhat
|
||||
// different from other "name"'s (including as described
|
||||
// at https://jsdoc.app/about-namepaths.html )
|
||||
[
|
||||
'namepathRole', 'namepath-defining',
|
||||
],
|
||||
|
||||
// See `external`
|
||||
[
|
||||
'nameRequired', true,
|
||||
],
|
||||
|
||||
[
|
||||
'typeAllowed', false,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'interface', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Allows for "name" in signature, but indicates as optional
|
||||
[
|
||||
'namepathRole',
|
||||
isJsdocTypescriptOrPermissive ? 'namepath-defining' : false,
|
||||
],
|
||||
|
||||
// Not in use, but should be this value if using to power `empty-tags`
|
||||
[
|
||||
'nameAllowed', isClosure,
|
||||
],
|
||||
|
||||
[
|
||||
'typeAllowed', false,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'internal', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// https://www.typescriptlang.org/tsconfig/#stripInternal
|
||||
[
|
||||
'namepathRole', false,
|
||||
],
|
||||
// Not in use, but should be this value if using to power `empty-tags`
|
||||
[
|
||||
'nameAllowed', false,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'implements', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Shows curly brackets in the doc signature and examples
|
||||
// "typeExpression"
|
||||
[
|
||||
'typeRequired', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'lends', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Signature seems to require a "namepath" (and no counter-examples)
|
||||
[
|
||||
'namepathRole', 'namepath-referencing',
|
||||
],
|
||||
|
||||
// "namepath"
|
||||
[
|
||||
'typeOrNameRequired', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'link', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Signature seems to require a namepath OR URL and might be checked as such.
|
||||
[
|
||||
'namepathRole', 'namepath-or-url-referencing',
|
||||
],
|
||||
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'linkcode', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Synonym for "link"
|
||||
// Signature seems to require a namepath OR URL and might be checked as such.
|
||||
[
|
||||
'namepathRole', 'namepath-or-url-referencing',
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'linkplain', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Synonym for "link"
|
||||
// Signature seems to require a namepath OR URL and might be checked as such.
|
||||
[
|
||||
'namepathRole', 'namepath-or-url-referencing',
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'listens', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Signature seems to require a "name" (of an event) and no
|
||||
// counter-examples
|
||||
[
|
||||
'namepathRole', 'namepath-referencing',
|
||||
],
|
||||
|
||||
[
|
||||
'nameRequired', true,
|
||||
],
|
||||
|
||||
[
|
||||
'typeAllowed', false,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'member', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Allows for "name"'s in signature, but indicated as optional
|
||||
[
|
||||
'namepathRole', 'namepath-defining',
|
||||
],
|
||||
|
||||
// Has example showing curly brackets but not in doc signature
|
||||
[
|
||||
'typeAllowed', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'memberof', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Signature seems to require a "namepath" (and no counter-examples),
|
||||
// though it allows an incomplete namepath ending with connecting symbol
|
||||
[
|
||||
'namepathRole', 'namepath-referencing',
|
||||
],
|
||||
|
||||
// "namepath"
|
||||
[
|
||||
'typeOrNameRequired', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
[
|
||||
'memberof!', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Signature seems to require a "namepath" (and no counter-examples),
|
||||
// though it allows an incomplete namepath ending with connecting symbol
|
||||
[
|
||||
'namepathRole', 'namepath-referencing',
|
||||
],
|
||||
|
||||
// "namepath"
|
||||
[
|
||||
'typeOrNameRequired', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'method', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Allows for "name"'s in signature, but indicated as optional
|
||||
[
|
||||
'namepathRole', 'namepath-defining',
|
||||
],
|
||||
])),
|
||||
],
|
||||
[
|
||||
'mixes', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Signature seems to require a "OtherObjectPath" with no
|
||||
// counter-examples
|
||||
[
|
||||
'namepathRole', 'namepath-referencing',
|
||||
],
|
||||
|
||||
// "OtherObjectPath"
|
||||
[
|
||||
'typeOrNameRequired', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'mixin', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Allows for "name"'s in signature, but indicated as optional
|
||||
[
|
||||
'namepathRole', 'namepath-defining',
|
||||
],
|
||||
|
||||
[
|
||||
'nameRequired', false,
|
||||
],
|
||||
|
||||
[
|
||||
'typeAllowed', false,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'modifies', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Has no documentation, but test example has curly brackets, and
|
||||
// "name" would be suggested rather than "namepath" based on example;
|
||||
// not sure if name is required
|
||||
[
|
||||
'typeAllowed', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'module', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Optional "name" and no curly brackets
|
||||
// this block impacts `no-undefined-types` and `valid-types` (search for
|
||||
// "isNamepathDefiningTag|tagMightHaveNamepath|tagMightHaveEitherTypeOrNamePosition")
|
||||
[
|
||||
'namepathRole', isJsdoc ? 'namepath-defining' : 'text',
|
||||
],
|
||||
|
||||
// Shows the signature with curly brackets but not in the example
|
||||
[
|
||||
'typeAllowed', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'name', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Seems to require a "namepath" in the signature (with no
|
||||
// counter-examples)
|
||||
[
|
||||
'namepathRole', 'namepath-defining',
|
||||
],
|
||||
|
||||
// "namepath"
|
||||
[
|
||||
'nameRequired', true,
|
||||
],
|
||||
|
||||
// "namepath"
|
||||
[
|
||||
'typeOrNameRequired', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'namespace', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Allows for "name"'s in signature, but indicated as optional
|
||||
[
|
||||
'namepathRole', 'namepath-defining',
|
||||
],
|
||||
|
||||
// Shows the signature with curly brackets but not in the example
|
||||
[
|
||||
'typeAllowed', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
[
|
||||
'package', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Shows the signature with curly brackets but not in the example
|
||||
// "typeExpression"
|
||||
[
|
||||
'typeAllowed', isClosureOrPermissive,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'param', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
[
|
||||
'namepathRole', 'namepath-defining',
|
||||
],
|
||||
|
||||
// Though no signature provided requiring, per
|
||||
// https://jsdoc.app/tags-param.html:
|
||||
// "The @param tag requires you to specify the name of the parameter you
|
||||
// are documenting."
|
||||
[
|
||||
'nameRequired', true,
|
||||
],
|
||||
|
||||
// Has no formal signature in the docs but shows curly brackets
|
||||
// in the examples
|
||||
[
|
||||
'typeAllowed', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'private', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Shows the signature with curly brackets but not in the example
|
||||
// "typeExpression"
|
||||
[
|
||||
'typeAllowed', isClosureOrPermissive,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'prop', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
[
|
||||
'namepathRole', 'namepath-defining',
|
||||
],
|
||||
|
||||
// See `property`
|
||||
[
|
||||
'nameRequired', true,
|
||||
],
|
||||
|
||||
// Has no formal signature in the docs but shows curly brackets
|
||||
// in the examples
|
||||
[
|
||||
'typeAllowed', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'property', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
[
|
||||
'namepathRole', 'namepath-defining',
|
||||
],
|
||||
|
||||
// No docs indicate required, but since parallel to `param`, we treat as
|
||||
// such:
|
||||
[
|
||||
'nameRequired', true,
|
||||
],
|
||||
|
||||
// Has no formal signature in the docs but shows curly brackets
|
||||
// in the examples
|
||||
[
|
||||
'typeAllowed', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'protected', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Shows the signature with curly brackets but not in the example
|
||||
// "typeExpression"
|
||||
[
|
||||
'typeAllowed', isClosureOrPermissive,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'public', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Does not show a signature nor show curly brackets in the example
|
||||
[
|
||||
'typeAllowed', isClosureOrPermissive,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'requires', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// <someModuleName>
|
||||
[
|
||||
'namepathRole', 'namepath-referencing',
|
||||
],
|
||||
|
||||
[
|
||||
'nameRequired', true,
|
||||
],
|
||||
|
||||
[
|
||||
'typeAllowed', false,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'returns', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Shows curly brackets in the signature and in the examples
|
||||
[
|
||||
'typeAllowed', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
[
|
||||
'return', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Shows curly brackets in the signature and in the examples
|
||||
[
|
||||
'typeAllowed', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'satisfies', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Shows curly brackets in the doc signature and examples
|
||||
[
|
||||
'typeRequired', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'see', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Signature allows for "namepath" or text, so user must configure to
|
||||
// 'namepath-referencing' to enforce checks
|
||||
[
|
||||
'namepathRole', 'text',
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'static', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Does not show a signature nor show curly brackets in the example
|
||||
[
|
||||
'typeAllowed', isClosureOrPermissive,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'suppress', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
[
|
||||
'namepathRole', !isClosure,
|
||||
],
|
||||
[
|
||||
'typeRequired', isClosure,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'template', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
[
|
||||
'namepathRole', isJsdoc ? 'text' : 'namepath-referencing',
|
||||
],
|
||||
|
||||
[
|
||||
'nameRequired', !isJsdoc,
|
||||
],
|
||||
|
||||
// Though defines `namepathRole: 'namepath-defining'` in a sense, it is
|
||||
// not parseable in the same way for template (e.g., allowing commas),
|
||||
// so not adding
|
||||
[
|
||||
'typeAllowed', isTypescriptOrClosure || isPermissive,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'this', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Signature seems to require a "namepath" (and no counter-examples)
|
||||
// Not used with namepath in Closure/TypeScript, however
|
||||
[
|
||||
'namepathRole', isJsdoc ? 'namepath-referencing' : false,
|
||||
],
|
||||
|
||||
[
|
||||
'typeRequired', isTypescriptOrClosure,
|
||||
],
|
||||
|
||||
// namepath
|
||||
[
|
||||
'typeOrNameRequired', isJsdoc,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'throws', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Shows curly brackets in the signature and in the examples
|
||||
[
|
||||
'typeAllowed', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'tutorial', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// (a tutorial ID)
|
||||
[
|
||||
'nameRequired', true,
|
||||
],
|
||||
|
||||
[
|
||||
'typeAllowed', false,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'type', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Shows curly brackets in the doc signature and examples
|
||||
// "typeName"
|
||||
[
|
||||
'typeRequired', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'typedef', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Seems to require a "namepath" in the signature (with no
|
||||
// counter-examples)
|
||||
[
|
||||
'namepathRole', 'namepath-defining',
|
||||
],
|
||||
|
||||
// TypeScript may allow it to be dropped if followed by @property or @member;
|
||||
// also shown as missing in Closure
|
||||
// "namepath"
|
||||
[
|
||||
'nameRequired', isJsdocOrPermissive,
|
||||
],
|
||||
|
||||
// Is not `typeRequired` for TypeScript because it gives an error:
|
||||
// JSDoc '@typedef' tag should either have a type annotation or be followed by '@property' or '@member' tags.
|
||||
|
||||
// Has example showing curly brackets but not in doc signature
|
||||
[
|
||||
'typeAllowed', true,
|
||||
],
|
||||
|
||||
// TypeScript may allow it to be dropped if followed by @property or @member
|
||||
// "namepath"
|
||||
[
|
||||
'typeOrNameRequired', !isTypescript,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'var', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Allows for "name"'s in signature, but indicated as optional
|
||||
[
|
||||
'namepathRole', 'namepath-defining',
|
||||
],
|
||||
|
||||
// Has example showing curly brackets but not in doc signature
|
||||
[
|
||||
'typeAllowed', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
|
||||
[
|
||||
'yields', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Shows curly brackets in the signature and in the examples
|
||||
[
|
||||
'typeAllowed', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
[
|
||||
'yield', new Map(/** @type {[string, string|boolean][]} */ ([
|
||||
// Shows curly brackets in the signature and in the examples
|
||||
[
|
||||
'typeAllowed', true,
|
||||
],
|
||||
])),
|
||||
],
|
||||
]);
|
||||
};
|
||||
|
||||
export default getDefaultTagStructureForMode;
|
||||
606
node_modules/eslint-plugin-jsdoc/src/getJsdocProcessorPlugin.js
generated
vendored
Normal file
606
node_modules/eslint-plugin-jsdoc/src/getJsdocProcessorPlugin.js
generated
vendored
Normal file
@@ -0,0 +1,606 @@
|
||||
// Todo: Support TS by fenced block type
|
||||
|
||||
import {readFileSync} from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import * as espree from 'espree';
|
||||
import {
|
||||
getRegexFromString,
|
||||
forEachPreferredTag,
|
||||
getTagDescription,
|
||||
getPreferredTagName,
|
||||
hasTag,
|
||||
} from './jsdocUtils.js';
|
||||
import {
|
||||
parseComment,
|
||||
} from '@es-joy/jsdoccomment';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const {version} = JSON.parse(
|
||||
// @ts-expect-error `Buffer` is ok for `JSON.parse`
|
||||
readFileSync(join(__dirname, '../package.json'))
|
||||
);
|
||||
|
||||
// const zeroBasedLineIndexAdjust = -1;
|
||||
const likelyNestedJSDocIndentSpace = 1;
|
||||
const preTagSpaceLength = 1;
|
||||
|
||||
// If a space is present, we should ignore it
|
||||
const firstLinePrefixLength = preTagSpaceLength;
|
||||
|
||||
const hasCaptionRegex = /^\s*<caption>([\s\S]*?)<\/caption>/u;
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @returns {string}
|
||||
*/
|
||||
const escapeStringRegexp = (str) => {
|
||||
return str.replaceAll(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @param {string} ch
|
||||
* @returns {import('./iterateJsdoc.js').Integer}
|
||||
*/
|
||||
const countChars = (str, ch) => {
|
||||
return (str.match(new RegExp(escapeStringRegexp(ch), 'gu')) || []).length;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
* @returns {[
|
||||
* import('./iterateJsdoc.js').Integer,
|
||||
* import('./iterateJsdoc.js').Integer
|
||||
* ]}
|
||||
*/
|
||||
const getLinesCols = (text) => {
|
||||
const matchLines = countChars(text, '\n');
|
||||
|
||||
const colDelta = matchLines ?
|
||||
text.slice(text.lastIndexOf('\n') + 1).length :
|
||||
text.length;
|
||||
|
||||
return [
|
||||
matchLines, colDelta,
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {number} Integer
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} JsdocProcessorOptions
|
||||
* @property {boolean} [captionRequired]
|
||||
* @property {Integer} [paddedIndent]
|
||||
* @property {boolean} [checkDefaults]
|
||||
* @property {boolean} [checkParams]
|
||||
* @property {boolean} [checkExamples]
|
||||
* @property {boolean} [checkProperties]
|
||||
* @property {string} [matchingFileName]
|
||||
* @property {string} [matchingFileNameDefaults]
|
||||
* @property {string} [matchingFileNameParams]
|
||||
* @property {string} [matchingFileNameProperties]
|
||||
* @property {string} [exampleCodeRegex]
|
||||
* @property {string} [rejectExampleCodeRegex]
|
||||
* @property {"script"|"module"} [sourceType]
|
||||
* @property {import('eslint').Linter.FlatConfigParserModule} [parser]
|
||||
*/
|
||||
|
||||
/**
|
||||
* We use a function for the ability of the user to pass in a config, but
|
||||
* without requiring all users of the plugin to do so.
|
||||
* @param {JsdocProcessorOptions} [options]
|
||||
*/
|
||||
export const getJsdocProcessorPlugin = (options = {}) => {
|
||||
const {
|
||||
exampleCodeRegex = null,
|
||||
rejectExampleCodeRegex = null,
|
||||
checkExamples = true,
|
||||
checkDefaults = false,
|
||||
checkParams = false,
|
||||
checkProperties = false,
|
||||
matchingFileName = null,
|
||||
matchingFileNameDefaults = null,
|
||||
matchingFileNameParams = null,
|
||||
matchingFileNameProperties = null,
|
||||
paddedIndent = 0,
|
||||
captionRequired = false,
|
||||
sourceType = 'module',
|
||||
parser = undefined
|
||||
} = options;
|
||||
|
||||
/** @type {RegExp} */
|
||||
let exampleCodeRegExp;
|
||||
/** @type {RegExp} */
|
||||
let rejectExampleCodeRegExp;
|
||||
|
||||
if (exampleCodeRegex) {
|
||||
exampleCodeRegExp = getRegexFromString(exampleCodeRegex);
|
||||
}
|
||||
|
||||
if (rejectExampleCodeRegex) {
|
||||
rejectExampleCodeRegExp = getRegexFromString(rejectExampleCodeRegex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {{
|
||||
* targetTagName: string,
|
||||
* ext: string,
|
||||
* codeStartLine: number,
|
||||
* codeStartCol: number,
|
||||
* nonJSPrefacingCols: number,
|
||||
* commentLineCols: [number, number]
|
||||
* }[]}
|
||||
*/
|
||||
const otherInfo = [];
|
||||
|
||||
/** @type {import('eslint').Linter.LintMessage[]} */
|
||||
let extraMessages = [];
|
||||
|
||||
/**
|
||||
* @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc
|
||||
* @param {string} jsFileName
|
||||
* @param {[number, number]} commentLineCols
|
||||
*/
|
||||
const getTextsAndFileNames = (jsdoc, jsFileName, commentLineCols) => {
|
||||
/**
|
||||
* @type {{
|
||||
* text: string,
|
||||
* filename: string|null|undefined
|
||||
* }[]}
|
||||
*/
|
||||
const textsAndFileNames = [];
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* filename: string|null,
|
||||
* defaultFileName: string|undefined,
|
||||
* source: string,
|
||||
* targetTagName: string,
|
||||
* rules?: import('eslint').Linter.RulesRecord|undefined,
|
||||
* lines?: import('./iterateJsdoc.js').Integer,
|
||||
* cols?: import('./iterateJsdoc.js').Integer,
|
||||
* skipInit?: boolean,
|
||||
* ext: string,
|
||||
* sources?: {
|
||||
* nonJSPrefacingCols: import('./iterateJsdoc.js').Integer,
|
||||
* nonJSPrefacingLines: import('./iterateJsdoc.js').Integer,
|
||||
* string: string,
|
||||
* }[],
|
||||
* tag?: import('comment-parser').Spec & {
|
||||
* line?: import('./iterateJsdoc.js').Integer,
|
||||
* }|{
|
||||
* line: import('./iterateJsdoc.js').Integer,
|
||||
* }
|
||||
* }} cfg
|
||||
*/
|
||||
const checkSource = ({
|
||||
filename,
|
||||
ext,
|
||||
defaultFileName,
|
||||
lines = 0,
|
||||
cols = 0,
|
||||
skipInit,
|
||||
source,
|
||||
targetTagName,
|
||||
sources = [],
|
||||
tag = {
|
||||
line: 0,
|
||||
},
|
||||
}) => {
|
||||
if (!skipInit) {
|
||||
sources.push({
|
||||
nonJSPrefacingCols: cols,
|
||||
nonJSPrefacingLines: lines,
|
||||
string: source,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* nonJSPrefacingCols: import('./iterateJsdoc.js').Integer,
|
||||
* nonJSPrefacingLines: import('./iterateJsdoc.js').Integer,
|
||||
* string: string
|
||||
* }} cfg
|
||||
*/
|
||||
const addSourceInfo = function ({
|
||||
nonJSPrefacingCols,
|
||||
nonJSPrefacingLines,
|
||||
string,
|
||||
}) {
|
||||
const src = paddedIndent ?
|
||||
string.replaceAll(new RegExp(`(^|\n) {${paddedIndent}}(?!$)`, 'gu'), '\n') :
|
||||
string;
|
||||
|
||||
// Programmatic ESLint API: https://eslint.org/docs/developer-guide/nodejs-api
|
||||
const file = filename || defaultFileName;
|
||||
|
||||
if (!('line' in tag)) {
|
||||
tag.line = tag.source[0].number;
|
||||
}
|
||||
|
||||
// NOTE: `tag.line` can be 0 if of form `/** @tag ... */`
|
||||
const codeStartLine = /**
|
||||
* @type {import('comment-parser').Spec & {
|
||||
* line: import('./iterateJsdoc.js').Integer,
|
||||
* }}
|
||||
*/ (tag).line + nonJSPrefacingLines;
|
||||
const codeStartCol = likelyNestedJSDocIndentSpace;
|
||||
|
||||
textsAndFileNames.push({
|
||||
text: src,
|
||||
filename: file,
|
||||
});
|
||||
otherInfo.push({
|
||||
targetTagName,
|
||||
ext,
|
||||
codeStartLine,
|
||||
codeStartCol,
|
||||
nonJSPrefacingCols,
|
||||
commentLineCols
|
||||
});
|
||||
};
|
||||
|
||||
for (const targetSource of sources) {
|
||||
addSourceInfo(targetSource);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string|null} filename
|
||||
* @param {string} [ext] Since `eslint-plugin-markdown` v2, and
|
||||
* ESLint 7, this is the default which other JS-fenced rules will used.
|
||||
* Formerly "md" was the default.
|
||||
* @returns {{
|
||||
* defaultFileName: string|undefined,
|
||||
* filename: string|null,
|
||||
* ext: string
|
||||
* }}
|
||||
*/
|
||||
const getFilenameInfo = (filename, ext = 'md/*.js') => {
|
||||
let defaultFileName;
|
||||
if (!filename) {
|
||||
if (typeof jsFileName === 'string' && jsFileName.includes('.')) {
|
||||
defaultFileName = jsFileName.replace(/\.[^.]*$/u, `.${ext}`);
|
||||
} else {
|
||||
defaultFileName = `dummy.${ext}`;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ext,
|
||||
defaultFileName,
|
||||
filename,
|
||||
};
|
||||
};
|
||||
|
||||
if (checkDefaults) {
|
||||
const filenameInfo = getFilenameInfo(matchingFileNameDefaults, 'jsdoc-defaults');
|
||||
forEachPreferredTag(jsdoc, 'default', (tag, targetTagName) => {
|
||||
if (!tag.description.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkSource({
|
||||
source: `(${getTagDescription(tag)})`,
|
||||
targetTagName,
|
||||
...filenameInfo,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (checkParams) {
|
||||
const filenameInfo = getFilenameInfo(matchingFileNameParams, 'jsdoc-params');
|
||||
forEachPreferredTag(jsdoc, 'param', (tag, targetTagName) => {
|
||||
if (!tag.default || !tag.default.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkSource({
|
||||
source: `(${tag.default})`,
|
||||
targetTagName,
|
||||
...filenameInfo,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (checkProperties) {
|
||||
const filenameInfo = getFilenameInfo(matchingFileNameProperties, 'jsdoc-properties');
|
||||
forEachPreferredTag(jsdoc, 'property', (tag, targetTagName) => {
|
||||
if (!tag.default || !tag.default.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkSource({
|
||||
source: `(${tag.default})`,
|
||||
targetTagName,
|
||||
...filenameInfo,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (!checkExamples) {
|
||||
return textsAndFileNames;
|
||||
}
|
||||
|
||||
const tagName = /** @type {string} */ (getPreferredTagName(jsdoc, {
|
||||
tagName: 'example',
|
||||
}));
|
||||
if (!hasTag(jsdoc, tagName)) {
|
||||
return textsAndFileNames;
|
||||
}
|
||||
|
||||
const matchingFilenameInfo = getFilenameInfo(matchingFileName);
|
||||
|
||||
forEachPreferredTag(jsdoc, 'example', (tag, targetTagName) => {
|
||||
let source = /** @type {string} */ (getTagDescription(tag));
|
||||
const match = source.match(hasCaptionRegex);
|
||||
|
||||
if (captionRequired && (!match || !match[1].trim())) {
|
||||
extraMessages.push({
|
||||
line: 1 + commentLineCols[0] + (tag.line ?? tag.source[0].number),
|
||||
column: commentLineCols[1] + 1,
|
||||
severity: 2,
|
||||
message: `@${targetTagName} error - Caption is expected for examples.`,
|
||||
ruleId: 'jsdoc/example-missing-caption'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
source = source.replace(hasCaptionRegex, '');
|
||||
const [
|
||||
lines,
|
||||
cols,
|
||||
] = match ? getLinesCols(match[0]) : [
|
||||
0, 0,
|
||||
];
|
||||
|
||||
if (exampleCodeRegex && !exampleCodeRegExp.test(source) ||
|
||||
rejectExampleCodeRegex && rejectExampleCodeRegExp.test(source)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sources = [];
|
||||
let skipInit = false;
|
||||
if (exampleCodeRegex) {
|
||||
let nonJSPrefacingCols = 0;
|
||||
let nonJSPrefacingLines = 0;
|
||||
|
||||
let startingIndex = 0;
|
||||
let lastStringCount = 0;
|
||||
|
||||
let exampleCode;
|
||||
exampleCodeRegExp.lastIndex = 0;
|
||||
while ((exampleCode = exampleCodeRegExp.exec(source)) !== null) {
|
||||
const {
|
||||
index,
|
||||
'0': n0,
|
||||
'1': n1,
|
||||
} = exampleCode;
|
||||
|
||||
// Count anything preceding user regex match (can affect line numbering)
|
||||
const preMatch = source.slice(startingIndex, index);
|
||||
|
||||
const [
|
||||
preMatchLines,
|
||||
colDelta,
|
||||
] = getLinesCols(preMatch);
|
||||
|
||||
let nonJSPreface;
|
||||
let nonJSPrefaceLineCount;
|
||||
if (n1) {
|
||||
const idx = n0.indexOf(n1);
|
||||
nonJSPreface = n0.slice(0, idx);
|
||||
nonJSPrefaceLineCount = countChars(nonJSPreface, '\n');
|
||||
} else {
|
||||
nonJSPreface = '';
|
||||
nonJSPrefaceLineCount = 0;
|
||||
}
|
||||
|
||||
nonJSPrefacingLines += lastStringCount + preMatchLines + nonJSPrefaceLineCount;
|
||||
|
||||
// Ignore `preMatch` delta if newlines here
|
||||
if (nonJSPrefaceLineCount) {
|
||||
const charsInLastLine = nonJSPreface.slice(nonJSPreface.lastIndexOf('\n') + 1).length;
|
||||
|
||||
nonJSPrefacingCols += charsInLastLine;
|
||||
} else {
|
||||
nonJSPrefacingCols += colDelta + nonJSPreface.length;
|
||||
}
|
||||
|
||||
const string = n1 || n0;
|
||||
sources.push({
|
||||
nonJSPrefacingCols,
|
||||
nonJSPrefacingLines,
|
||||
string,
|
||||
});
|
||||
startingIndex = exampleCodeRegExp.lastIndex;
|
||||
lastStringCount = countChars(string, '\n');
|
||||
if (!exampleCodeRegExp.global) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
skipInit = true;
|
||||
}
|
||||
|
||||
checkSource({
|
||||
cols,
|
||||
lines,
|
||||
skipInit,
|
||||
source,
|
||||
sources,
|
||||
tag,
|
||||
targetTagName,
|
||||
...matchingFilenameInfo,
|
||||
});
|
||||
});
|
||||
|
||||
return textsAndFileNames;
|
||||
};
|
||||
|
||||
// See https://eslint.org/docs/latest/extend/plugins#processors-in-plugins
|
||||
// See https://eslint.org/docs/latest/extend/custom-processors
|
||||
// From https://github.com/eslint/eslint/issues/14745#issuecomment-869457265
|
||||
/*
|
||||
{
|
||||
"files": ["*.js", "*.ts"],
|
||||
"processor": "jsdoc/example" // a pretended value here
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"*.js/*_jsdoc-example.js",
|
||||
"*.ts/*_jsdoc-example.js",
|
||||
"*.js/*_jsdoc-example.ts"
|
||||
],
|
||||
"rules": {
|
||||
// specific rules for examples in jsdoc only here
|
||||
// And other rules for `.js` and `.ts` will also be enabled for them
|
||||
}
|
||||
}
|
||||
*/
|
||||
return {
|
||||
meta: {
|
||||
name: 'eslint-plugin-jsdoc/processor',
|
||||
version,
|
||||
},
|
||||
processors: {
|
||||
examples: {
|
||||
meta: {
|
||||
name: 'eslint-plugin-jsdoc/preprocessor',
|
||||
version,
|
||||
},
|
||||
/**
|
||||
* @param {string} text
|
||||
* @param {string} filename
|
||||
*/
|
||||
preprocess (text, filename) {
|
||||
try {
|
||||
let ast;
|
||||
|
||||
// May be running a second time so catch and ignore
|
||||
try {
|
||||
ast = parser
|
||||
// @ts-expect-error Ok
|
||||
? parser.parseForESLint(text, {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType,
|
||||
comment: true
|
||||
}).ast
|
||||
: espree.parse(text, {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType,
|
||||
comment: true
|
||||
});
|
||||
} catch (err) {
|
||||
return [text];
|
||||
}
|
||||
|
||||
/** @type {[number, number][]} */
|
||||
const commentLineCols = [];
|
||||
const jsdocComments = /** @type {import('estree').Comment[]} */ (
|
||||
/**
|
||||
* @type {import('estree').Program & {
|
||||
* comments?: import('estree').Comment[]
|
||||
* }}
|
||||
*/
|
||||
(ast).comments
|
||||
).filter((comment) => {
|
||||
return (/^\*\s/u).test(comment.value);
|
||||
}).map((comment) => {
|
||||
/* c8 ignore next -- Unsupporting processors only? */
|
||||
const [start] = comment.range ?? [];
|
||||
const textToStart = text.slice(0, start);
|
||||
|
||||
const [lines, cols] = getLinesCols(textToStart);
|
||||
|
||||
// const lines = [...textToStart.matchAll(/\n/gu)].length
|
||||
// const lastLinePos = textToStart.lastIndexOf('\n');
|
||||
// const cols = lastLinePos === -1
|
||||
// ? 0
|
||||
// : textToStart.slice(lastLinePos).length;
|
||||
commentLineCols.push([lines, cols]);
|
||||
return parseComment(comment);
|
||||
});
|
||||
|
||||
return [
|
||||
text,
|
||||
...jsdocComments.flatMap((jsdoc, idx) => {
|
||||
return getTextsAndFileNames(
|
||||
jsdoc,
|
||||
filename,
|
||||
commentLineCols[idx]
|
||||
);
|
||||
}).filter(Boolean)
|
||||
];
|
||||
/* c8 ignore next 3 */
|
||||
} catch (err) {
|
||||
console.log('err', filename, err);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {import('eslint').Linter.LintMessage[][]} messages
|
||||
* @param {string} filename
|
||||
*/
|
||||
postprocess ([jsMessages, ...messages], filename) {
|
||||
messages.forEach((message, idx) => {
|
||||
const {
|
||||
targetTagName,
|
||||
codeStartLine,
|
||||
codeStartCol,
|
||||
nonJSPrefacingCols,
|
||||
commentLineCols
|
||||
} = otherInfo[idx];
|
||||
|
||||
message.forEach((msg) => {
|
||||
const {
|
||||
message,
|
||||
ruleId,
|
||||
severity,
|
||||
fatal,
|
||||
line,
|
||||
column,
|
||||
endColumn,
|
||||
endLine,
|
||||
|
||||
// Todo: Make fixable
|
||||
// fix
|
||||
// fix: {range: [number, number], text: string}
|
||||
// suggestions: {desc: , messageId:, fix: }[],
|
||||
} = msg;
|
||||
|
||||
const [codeCtxLine, codeCtxColumn] = commentLineCols;
|
||||
const startLine = codeCtxLine + codeStartLine + line;
|
||||
const startCol = 1 + // Seems to need one more now
|
||||
codeCtxColumn + codeStartCol + (
|
||||
// This might not work for line 0, but line 0 is unlikely for examples
|
||||
line <= 1 ? nonJSPrefacingCols + firstLinePrefixLength : preTagSpaceLength
|
||||
) + column;
|
||||
|
||||
msg.message = '@' + targetTagName + ' ' + (severity === 2 ? 'error' : 'warning') +
|
||||
(ruleId ? ' (' + ruleId + ')' : '') + ': ' +
|
||||
(fatal ? 'Fatal: ' : '') +
|
||||
message;
|
||||
msg.line = startLine;
|
||||
msg.column = startCol;
|
||||
msg.endLine = endLine ? startLine + endLine : startLine;
|
||||
// added `- column` to offset what `endColumn` already seemed to include
|
||||
msg.endColumn = endColumn ? startCol - column + endColumn : startCol;
|
||||
});
|
||||
});
|
||||
|
||||
const ret = [...jsMessages].concat(...messages, ...extraMessages);
|
||||
extraMessages = [];
|
||||
return ret;
|
||||
},
|
||||
supportsAutofix: true
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
11
node_modules/eslint-plugin-jsdoc/src/import-worker.mjs
generated
vendored
Normal file
11
node_modules/eslint-plugin-jsdoc/src/import-worker.mjs
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import { runAsWorker } from 'synckit'
|
||||
|
||||
runAsWorker(async (imprt) => {
|
||||
const { parseImports } = await import('parse-imports');
|
||||
try {
|
||||
// ESLint doesn't support async rules
|
||||
return [...await parseImports(imprt)];
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
387
node_modules/eslint-plugin-jsdoc/src/index.js
generated
vendored
Normal file
387
node_modules/eslint-plugin-jsdoc/src/index.js
generated
vendored
Normal file
@@ -0,0 +1,387 @@
|
||||
import checkAccess from './rules/checkAccess.js';
|
||||
import checkAlignment from './rules/checkAlignment.js';
|
||||
import checkExamples from './rules/checkExamples.js';
|
||||
import checkIndentation from './rules/checkIndentation.js';
|
||||
import checkLineAlignment from './rules/checkLineAlignment.js';
|
||||
import checkParamNames from './rules/checkParamNames.js';
|
||||
import checkPropertyNames from './rules/checkPropertyNames.js';
|
||||
import checkSyntax from './rules/checkSyntax.js';
|
||||
import checkTagNames from './rules/checkTagNames.js';
|
||||
import checkTemplateNames from './rules/checkTemplateNames.js';
|
||||
import checkTypes from './rules/checkTypes.js';
|
||||
import checkValues from './rules/checkValues.js';
|
||||
import convertToJsdocComments from './rules/convertToJsdocComments.js';
|
||||
import emptyTags from './rules/emptyTags.js';
|
||||
import implementsOnClasses from './rules/implementsOnClasses.js';
|
||||
import importsAsDependencies from './rules/importsAsDependencies.js';
|
||||
import informativeDocs from './rules/informativeDocs.js';
|
||||
import matchDescription from './rules/matchDescription.js';
|
||||
import matchName from './rules/matchName.js';
|
||||
import multilineBlocks from './rules/multilineBlocks.js';
|
||||
import noBadBlocks from './rules/noBadBlocks.js';
|
||||
import noBlankBlockDescriptions from './rules/noBlankBlockDescriptions.js';
|
||||
import noBlankBlocks from './rules/noBlankBlocks.js';
|
||||
import noDefaults from './rules/noDefaults.js';
|
||||
import noMissingSyntax from './rules/noMissingSyntax.js';
|
||||
import noMultiAsterisks from './rules/noMultiAsterisks.js';
|
||||
import noRestrictedSyntax from './rules/noRestrictedSyntax.js';
|
||||
import noTypes from './rules/noTypes.js';
|
||||
import noUndefinedTypes from './rules/noUndefinedTypes.js';
|
||||
import requireAsteriskPrefix from './rules/requireAsteriskPrefix.js';
|
||||
import requireDescription from './rules/requireDescription.js';
|
||||
import requireDescriptionCompleteSentence from './rules/requireDescriptionCompleteSentence.js';
|
||||
import requireExample from './rules/requireExample.js';
|
||||
import requireFileOverview from './rules/requireFileOverview.js';
|
||||
import requireHyphenBeforeParamDescription from './rules/requireHyphenBeforeParamDescription.js';
|
||||
import requireJsdoc from './rules/requireJsdoc.js';
|
||||
import requireParam from './rules/requireParam.js';
|
||||
import requireParamDescription from './rules/requireParamDescription.js';
|
||||
import requireParamName from './rules/requireParamName.js';
|
||||
import requireParamType from './rules/requireParamType.js';
|
||||
import requireProperty from './rules/requireProperty.js';
|
||||
import requirePropertyDescription from './rules/requirePropertyDescription.js';
|
||||
import requirePropertyName from './rules/requirePropertyName.js';
|
||||
import requirePropertyType from './rules/requirePropertyType.js';
|
||||
import requireReturns from './rules/requireReturns.js';
|
||||
import requireReturnsCheck from './rules/requireReturnsCheck.js';
|
||||
import requireReturnsDescription from './rules/requireReturnsDescription.js';
|
||||
import requireReturnsType from './rules/requireReturnsType.js';
|
||||
import requireTemplate from './rules/requireTemplate.js';
|
||||
import requireThrows from './rules/requireThrows.js';
|
||||
import requireYields from './rules/requireYields.js';
|
||||
import requireYieldsCheck from './rules/requireYieldsCheck.js';
|
||||
import sortTags from './rules/sortTags.js';
|
||||
import tagLines from './rules/tagLines.js';
|
||||
import textEscaping from './rules/textEscaping.js';
|
||||
import validTypes from './rules/validTypes.js';
|
||||
|
||||
import { getJsdocProcessorPlugin } from './getJsdocProcessorPlugin.js';
|
||||
|
||||
/**
|
||||
* @type {import('eslint').ESLint.Plugin & {
|
||||
* configs: Record<
|
||||
* "recommended"|"recommended-error"|"recommended-typescript"|
|
||||
* "recommended-typescript-error"|"recommended-typescript-flavor"|
|
||||
* "recommended-typescript-flavor-error"|"flat/recommended"|
|
||||
* "flat/recommended-error"|"flat/recommended-typescript"|
|
||||
* "flat/recommended-typescript-error"|
|
||||
* "flat/recommended-typescript-flavor"|
|
||||
* "flat/recommended-typescript-flavor-error",
|
||||
* import('eslint').Linter.FlatConfig
|
||||
* >
|
||||
* }}
|
||||
*/
|
||||
const index = {
|
||||
// @ts-expect-error Ok
|
||||
configs: {},
|
||||
rules: {
|
||||
'check-access': checkAccess,
|
||||
'check-alignment': checkAlignment,
|
||||
'check-examples': checkExamples,
|
||||
'check-indentation': checkIndentation,
|
||||
'check-line-alignment': checkLineAlignment,
|
||||
'check-param-names': checkParamNames,
|
||||
'check-property-names': checkPropertyNames,
|
||||
'check-syntax': checkSyntax,
|
||||
'check-tag-names': checkTagNames,
|
||||
'check-template-names': checkTemplateNames,
|
||||
'check-types': checkTypes,
|
||||
'check-values': checkValues,
|
||||
'convert-to-jsdoc-comments': convertToJsdocComments,
|
||||
'empty-tags': emptyTags,
|
||||
'implements-on-classes': implementsOnClasses,
|
||||
'imports-as-dependencies': importsAsDependencies,
|
||||
'informative-docs': informativeDocs,
|
||||
'match-description': matchDescription,
|
||||
'match-name': matchName,
|
||||
'multiline-blocks': multilineBlocks,
|
||||
'no-bad-blocks': noBadBlocks,
|
||||
'no-blank-block-descriptions': noBlankBlockDescriptions,
|
||||
'no-blank-blocks': noBlankBlocks,
|
||||
'no-defaults': noDefaults,
|
||||
'no-missing-syntax': noMissingSyntax,
|
||||
'no-multi-asterisks': noMultiAsterisks,
|
||||
'no-restricted-syntax': noRestrictedSyntax,
|
||||
'no-types': noTypes,
|
||||
'no-undefined-types': noUndefinedTypes,
|
||||
'require-asterisk-prefix': requireAsteriskPrefix,
|
||||
'require-description': requireDescription,
|
||||
'require-description-complete-sentence': requireDescriptionCompleteSentence,
|
||||
'require-example': requireExample,
|
||||
'require-file-overview': requireFileOverview,
|
||||
'require-hyphen-before-param-description': requireHyphenBeforeParamDescription,
|
||||
'require-jsdoc': requireJsdoc,
|
||||
'require-param': requireParam,
|
||||
'require-param-description': requireParamDescription,
|
||||
'require-param-name': requireParamName,
|
||||
'require-param-type': requireParamType,
|
||||
'require-property': requireProperty,
|
||||
'require-property-description': requirePropertyDescription,
|
||||
'require-property-name': requirePropertyName,
|
||||
'require-property-type': requirePropertyType,
|
||||
'require-returns': requireReturns,
|
||||
'require-returns-check': requireReturnsCheck,
|
||||
'require-returns-description': requireReturnsDescription,
|
||||
'require-returns-type': requireReturnsType,
|
||||
'require-template': requireTemplate,
|
||||
'require-throws': requireThrows,
|
||||
'require-yields': requireYields,
|
||||
'require-yields-check': requireYieldsCheck,
|
||||
'sort-tags': sortTags,
|
||||
'tag-lines': tagLines,
|
||||
'text-escaping': textEscaping,
|
||||
'valid-types': validTypes,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {"warn"|"error"} warnOrError
|
||||
* @param {string} [flatName]
|
||||
* @returns {import('eslint').Linter.FlatConfig}
|
||||
*/
|
||||
const createRecommendedRuleset = (warnOrError, flatName) => {
|
||||
return {
|
||||
...(flatName ? {name: 'jsdoc/' + flatName} : {}),
|
||||
// @ts-expect-error Ok
|
||||
plugins:
|
||||
flatName ? {
|
||||
jsdoc: index,
|
||||
} : [
|
||||
'jsdoc',
|
||||
],
|
||||
rules: {
|
||||
'jsdoc/check-access': warnOrError,
|
||||
'jsdoc/check-alignment': warnOrError,
|
||||
'jsdoc/check-examples': 'off',
|
||||
'jsdoc/check-indentation': 'off',
|
||||
'jsdoc/check-line-alignment': 'off',
|
||||
'jsdoc/check-param-names': warnOrError,
|
||||
'jsdoc/check-property-names': warnOrError,
|
||||
'jsdoc/check-syntax': 'off',
|
||||
'jsdoc/check-tag-names': warnOrError,
|
||||
'jsdoc/check-template-names': 'off',
|
||||
'jsdoc/check-types': warnOrError,
|
||||
'jsdoc/check-values': warnOrError,
|
||||
'jsdoc/convert-to-jsdoc-comments': 'off',
|
||||
'jsdoc/empty-tags': warnOrError,
|
||||
'jsdoc/implements-on-classes': warnOrError,
|
||||
'jsdoc/imports-as-dependencies': 'off',
|
||||
'jsdoc/informative-docs': 'off',
|
||||
'jsdoc/match-description': 'off',
|
||||
'jsdoc/match-name': 'off',
|
||||
'jsdoc/multiline-blocks': warnOrError,
|
||||
'jsdoc/no-bad-blocks': 'off',
|
||||
'jsdoc/no-blank-block-descriptions': 'off',
|
||||
'jsdoc/no-blank-blocks': 'off',
|
||||
'jsdoc/no-defaults': warnOrError,
|
||||
'jsdoc/no-missing-syntax': 'off',
|
||||
'jsdoc/no-multi-asterisks': warnOrError,
|
||||
'jsdoc/no-restricted-syntax': 'off',
|
||||
'jsdoc/no-types': 'off',
|
||||
'jsdoc/no-undefined-types': warnOrError,
|
||||
'jsdoc/require-asterisk-prefix': 'off',
|
||||
'jsdoc/require-description': 'off',
|
||||
'jsdoc/require-description-complete-sentence': 'off',
|
||||
'jsdoc/require-example': 'off',
|
||||
'jsdoc/require-file-overview': 'off',
|
||||
'jsdoc/require-hyphen-before-param-description': 'off',
|
||||
'jsdoc/require-jsdoc': warnOrError,
|
||||
'jsdoc/require-param': warnOrError,
|
||||
'jsdoc/require-param-description': warnOrError,
|
||||
'jsdoc/require-param-name': warnOrError,
|
||||
'jsdoc/require-param-type': warnOrError,
|
||||
'jsdoc/require-property': warnOrError,
|
||||
'jsdoc/require-property-description': warnOrError,
|
||||
'jsdoc/require-property-name': warnOrError,
|
||||
'jsdoc/require-property-type': warnOrError,
|
||||
'jsdoc/require-returns': warnOrError,
|
||||
'jsdoc/require-returns-check': warnOrError,
|
||||
'jsdoc/require-returns-description': warnOrError,
|
||||
'jsdoc/require-returns-type': warnOrError,
|
||||
'jsdoc/require-template': 'off',
|
||||
'jsdoc/require-throws': 'off',
|
||||
'jsdoc/require-yields': warnOrError,
|
||||
'jsdoc/require-yields-check': warnOrError,
|
||||
'jsdoc/sort-tags': 'off',
|
||||
'jsdoc/tag-lines': warnOrError,
|
||||
'jsdoc/text-escaping': 'off',
|
||||
'jsdoc/valid-types': warnOrError,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {"warn"|"error"} warnOrError
|
||||
* @param {string} [flatName]
|
||||
* @returns {import('eslint').Linter.FlatConfig}
|
||||
*/
|
||||
const createRecommendedTypeScriptRuleset = (warnOrError, flatName) => {
|
||||
const ruleset = createRecommendedRuleset(warnOrError, flatName);
|
||||
|
||||
return {
|
||||
...ruleset,
|
||||
rules: {
|
||||
...ruleset.rules,
|
||||
/* eslint-disable indent -- Extra indent to avoid use by auto-rule-editing */
|
||||
'jsdoc/check-tag-names': [
|
||||
warnOrError, {
|
||||
typed: true,
|
||||
},
|
||||
],
|
||||
'jsdoc/no-types': warnOrError,
|
||||
'jsdoc/no-undefined-types': 'off',
|
||||
'jsdoc/require-param-type': 'off',
|
||||
'jsdoc/require-property-type': 'off',
|
||||
'jsdoc/require-returns-type': 'off',
|
||||
/* eslint-enable indent */
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {"warn"|"error"} warnOrError
|
||||
* @param {string} [flatName]
|
||||
* @returns {import('eslint').Linter.FlatConfig}
|
||||
*/
|
||||
const createRecommendedTypeScriptFlavorRuleset = (warnOrError, flatName) => {
|
||||
const ruleset = createRecommendedRuleset(warnOrError, flatName);
|
||||
|
||||
return {
|
||||
...ruleset,
|
||||
rules: {
|
||||
...ruleset.rules,
|
||||
/* eslint-disable indent -- Extra indent to avoid use by auto-rule-editing */
|
||||
'jsdoc/no-undefined-types': 'off',
|
||||
/* eslint-enable indent */
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/* c8 ignore next 3 -- TS */
|
||||
if (!index.configs) {
|
||||
throw new Error('TypeScript guard');
|
||||
}
|
||||
|
||||
index.configs.recommended = createRecommendedRuleset('warn');
|
||||
index.configs['recommended-error'] = createRecommendedRuleset('error');
|
||||
index.configs['recommended-typescript'] = createRecommendedTypeScriptRuleset('warn');
|
||||
index.configs['recommended-typescript-error'] = createRecommendedTypeScriptRuleset('error');
|
||||
index.configs['recommended-typescript-flavor'] = createRecommendedTypeScriptFlavorRuleset('warn');
|
||||
index.configs['recommended-typescript-flavor-error'] = createRecommendedTypeScriptFlavorRuleset('error');
|
||||
|
||||
index.configs['flat/recommended'] = createRecommendedRuleset('warn', 'flat/recommended');
|
||||
index.configs['flat/recommended-error'] = createRecommendedRuleset('error', 'flat/recommended-error');
|
||||
index.configs['flat/recommended-typescript'] = createRecommendedTypeScriptRuleset('warn', 'flat/recommended-typescript');
|
||||
index.configs['flat/recommended-typescript-error'] = createRecommendedTypeScriptRuleset('error', 'flat/recommended-typescript-error');
|
||||
index.configs['flat/recommended-typescript-flavor'] = createRecommendedTypeScriptFlavorRuleset('warn', 'flat/recommended-typescript-flavor');
|
||||
index.configs['flat/recommended-typescript-flavor-error'] = createRecommendedTypeScriptFlavorRuleset('error', 'flat/recommended-typescript-flavor-error');
|
||||
|
||||
index.configs.examples = /** @type {import('eslint').Linter.FlatConfig[]} */ ([
|
||||
{
|
||||
name: 'jsdoc/examples/processor',
|
||||
files: ['**/*.js'],
|
||||
plugins: {
|
||||
examples: getJsdocProcessorPlugin()
|
||||
},
|
||||
processor: 'examples/examples',
|
||||
},
|
||||
{
|
||||
name: 'jsdoc/examples/rules',
|
||||
files: ['**/*.md/*.js'],
|
||||
rules: {
|
||||
// "always" newline rule at end unlikely in sample code
|
||||
'eol-last': 0,
|
||||
|
||||
// Wouldn't generally expect example paths to resolve relative to JS file
|
||||
'import/no-unresolved': 0,
|
||||
|
||||
// Snippets likely too short to always include import/export info
|
||||
'import/unambiguous': 0,
|
||||
|
||||
'jsdoc/require-file-overview': 0,
|
||||
|
||||
// The end of a multiline comment would end the comment the example is in.
|
||||
'jsdoc/require-jsdoc': 0,
|
||||
|
||||
// Unlikely to have inadvertent debugging within examples
|
||||
'no-console': 0,
|
||||
|
||||
// Often wish to start `@example` code after newline; also may use
|
||||
// empty lines for spacing
|
||||
'no-multiple-empty-lines': 0,
|
||||
|
||||
// Many variables in examples will be `undefined`
|
||||
'no-undef': 0,
|
||||
|
||||
// Common to define variables for clarity without always using them
|
||||
'no-unused-vars': 0,
|
||||
|
||||
// See import/no-unresolved
|
||||
'node/no-missing-import': 0,
|
||||
'node/no-missing-require': 0,
|
||||
|
||||
// Can generally look nicer to pad a little even if code imposes more stringency
|
||||
'padded-blocks': 0,
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
index.configs['default-expressions'] = /** @type {import('eslint').Linter.FlatConfig[]} */ ([
|
||||
{
|
||||
files: ['**/*.js'],
|
||||
name: 'jsdoc/default-expressions/processor',
|
||||
plugins: {
|
||||
examples: getJsdocProcessorPlugin({
|
||||
checkDefaults: true,
|
||||
checkParams: true,
|
||||
checkProperties: true
|
||||
})
|
||||
},
|
||||
processor: 'examples/examples'
|
||||
},
|
||||
{
|
||||
name: 'jsdoc/default-expressions/rules',
|
||||
files: ['**/*.jsdoc-defaults', '**/*.jsdoc-params', '**/*.jsdoc-properties'],
|
||||
rules: {
|
||||
...index.configs.examples[1].rules,
|
||||
'chai-friendly/no-unused-expressions': 0,
|
||||
'no-empty-function': 0,
|
||||
'no-new': 0,
|
||||
'no-unused-expressions': 0,
|
||||
quotes: [
|
||||
'error', 'double',
|
||||
],
|
||||
semi: [
|
||||
'error', 'never',
|
||||
],
|
||||
strict: 0
|
||||
},
|
||||
}
|
||||
]);
|
||||
|
||||
index.configs['examples-and-default-expressions'] = /** @type {import('eslint').Linter.FlatConfig[]} */ ([
|
||||
{
|
||||
name: 'jsdoc/examples-and-default-expressions',
|
||||
plugins: {
|
||||
examples: getJsdocProcessorPlugin({
|
||||
checkDefaults: true,
|
||||
checkParams: true,
|
||||
checkProperties: true
|
||||
})
|
||||
},
|
||||
},
|
||||
...index.configs.examples.map((config) => {
|
||||
return {
|
||||
...config,
|
||||
plugins: {}
|
||||
};
|
||||
}),
|
||||
...index.configs['default-expressions'].map((config) => {
|
||||
return {
|
||||
...config,
|
||||
plugins: {}
|
||||
};
|
||||
})
|
||||
]);
|
||||
|
||||
export default index;
|
||||
2479
node_modules/eslint-plugin-jsdoc/src/iterateJsdoc.js
generated
vendored
Normal file
2479
node_modules/eslint-plugin-jsdoc/src/iterateJsdoc.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1880
node_modules/eslint-plugin-jsdoc/src/jsdocUtils.js
generated
vendored
Normal file
1880
node_modules/eslint-plugin-jsdoc/src/jsdocUtils.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
45
node_modules/eslint-plugin-jsdoc/src/rules/checkAccess.js
generated
vendored
Normal file
45
node_modules/eslint-plugin-jsdoc/src/rules/checkAccess.js
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
const accessLevels = [
|
||||
'package', 'private', 'protected', 'public',
|
||||
];
|
||||
|
||||
export default iterateJsdoc(({
|
||||
report,
|
||||
utils,
|
||||
}) => {
|
||||
utils.forEachPreferredTag('access', (jsdocParameter, targetTagName) => {
|
||||
const desc = jsdocParameter.name + ' ' + jsdocParameter.description;
|
||||
|
||||
if (!accessLevels.includes(desc.trim())) {
|
||||
report(
|
||||
`Missing valid JSDoc @${targetTagName} level.`,
|
||||
null,
|
||||
jsdocParameter,
|
||||
);
|
||||
}
|
||||
});
|
||||
const accessLength = utils.getTags('access').length;
|
||||
const individualTagLength = utils.getPresentTags(accessLevels).length;
|
||||
if (accessLength && individualTagLength) {
|
||||
report(
|
||||
'The @access tag may not be used with specific access-control tags (@package, @private, @protected, or @public).',
|
||||
);
|
||||
}
|
||||
|
||||
if (accessLength > 1 || individualTagLength > 1) {
|
||||
report(
|
||||
'At most one access-control tag may be present on a jsdoc block.',
|
||||
);
|
||||
}
|
||||
}, {
|
||||
checkPrivate: true,
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Checks that `@access` tags have a valid value.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/check-access.md#repos-sticky-header',
|
||||
},
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
63
node_modules/eslint-plugin-jsdoc/src/rules/checkAlignment.js
generated
vendored
Normal file
63
node_modules/eslint-plugin-jsdoc/src/rules/checkAlignment.js
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
/**
|
||||
* @param {string} string
|
||||
* @returns {string}
|
||||
*/
|
||||
const trimStart = (string) => {
|
||||
return string.replace(/^\s+/u, '');
|
||||
};
|
||||
|
||||
export default iterateJsdoc(({
|
||||
sourceCode,
|
||||
jsdocNode,
|
||||
report,
|
||||
indent,
|
||||
}) => {
|
||||
// `indent` is whitespace from line 1 (`/**`), so slice and account for "/".
|
||||
const indentLevel = indent.length + 1;
|
||||
const sourceLines = sourceCode.getText(jsdocNode).split('\n')
|
||||
.slice(1)
|
||||
.map((line) => {
|
||||
return line.split('*')[0];
|
||||
})
|
||||
.filter((line) => {
|
||||
return !trimStart(line).length;
|
||||
});
|
||||
|
||||
/** @type {import('eslint').Rule.ReportFixer} */
|
||||
const fix = (fixer) => {
|
||||
const replacement = sourceCode.getText(jsdocNode).split('\n')
|
||||
.map((line, index) => {
|
||||
// Ignore the first line and all lines not starting with `*`
|
||||
const ignored = !index || trimStart(line.split('*')[0]).length;
|
||||
|
||||
return ignored ? line : `${indent} ${trimStart(line)}`;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
return fixer.replaceText(jsdocNode, replacement);
|
||||
};
|
||||
|
||||
sourceLines.some((line, lineNum) => {
|
||||
if (line.length !== indentLevel) {
|
||||
report('Expected JSDoc block to be aligned.', fix, {
|
||||
line: lineNum + 1,
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}, {
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Reports invalid alignment of JSDoc block asterisks.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/check-alignment.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'code',
|
||||
type: 'layout',
|
||||
},
|
||||
});
|
||||
593
node_modules/eslint-plugin-jsdoc/src/rules/checkExamples.js
generated
vendored
Normal file
593
node_modules/eslint-plugin-jsdoc/src/rules/checkExamples.js
generated
vendored
Normal file
@@ -0,0 +1,593 @@
|
||||
// Todo: When replace `CLIEngine` with `ESLint` when feature set complete per https://github.com/eslint/eslint/issues/14745
|
||||
// https://github.com/eslint/eslint/blob/master/docs/user-guide/migrating-to-7.0.0.md#-the-cliengine-class-has-been-deprecated
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
import eslint, {
|
||||
ESLint,
|
||||
} from 'eslint';
|
||||
import semver from 'semver';
|
||||
|
||||
const {
|
||||
// @ts-expect-error Older ESLint
|
||||
CLIEngine,
|
||||
} = eslint;
|
||||
|
||||
const zeroBasedLineIndexAdjust = -1;
|
||||
const likelyNestedJSDocIndentSpace = 1;
|
||||
const preTagSpaceLength = 1;
|
||||
|
||||
// If a space is present, we should ignore it
|
||||
const firstLinePrefixLength = preTagSpaceLength;
|
||||
|
||||
const hasCaptionRegex = /^\s*<caption>([\s\S]*?)<\/caption>/u;
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @returns {string}
|
||||
*/
|
||||
const escapeStringRegexp = (str) => {
|
||||
return str.replaceAll(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @param {string} ch
|
||||
* @returns {import('../iterateJsdoc.js').Integer}
|
||||
*/
|
||||
const countChars = (str, ch) => {
|
||||
return (str.match(new RegExp(escapeStringRegexp(ch), 'gu')) || []).length;
|
||||
};
|
||||
|
||||
/** @type {import('eslint').Linter.RulesRecord} */
|
||||
const defaultMdRules = {
|
||||
// "always" newline rule at end unlikely in sample code
|
||||
'eol-last': 0,
|
||||
|
||||
// Wouldn't generally expect example paths to resolve relative to JS file
|
||||
'import/no-unresolved': 0,
|
||||
|
||||
// Snippets likely too short to always include import/export info
|
||||
'import/unambiguous': 0,
|
||||
|
||||
'jsdoc/require-file-overview': 0,
|
||||
|
||||
// The end of a multiline comment would end the comment the example is in.
|
||||
'jsdoc/require-jsdoc': 0,
|
||||
|
||||
// Unlikely to have inadvertent debugging within examples
|
||||
'no-console': 0,
|
||||
|
||||
// Often wish to start `@example` code after newline; also may use
|
||||
// empty lines for spacing
|
||||
'no-multiple-empty-lines': 0,
|
||||
|
||||
// Many variables in examples will be `undefined`
|
||||
'no-undef': 0,
|
||||
|
||||
// Common to define variables for clarity without always using them
|
||||
'no-unused-vars': 0,
|
||||
|
||||
// See import/no-unresolved
|
||||
'node/no-missing-import': 0,
|
||||
'node/no-missing-require': 0,
|
||||
|
||||
// Can generally look nicer to pad a little even if code imposes more stringency
|
||||
'padded-blocks': 0,
|
||||
};
|
||||
|
||||
/** @type {import('eslint').Linter.RulesRecord} */
|
||||
const defaultExpressionRules = {
|
||||
...defaultMdRules,
|
||||
'chai-friendly/no-unused-expressions': 'off',
|
||||
'no-empty-function': 'off',
|
||||
'no-new': 'off',
|
||||
'no-unused-expressions': 'off',
|
||||
quotes: [
|
||||
'error', 'double',
|
||||
],
|
||||
semi: [
|
||||
'error', 'never',
|
||||
],
|
||||
strict: 'off',
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
* @returns {[
|
||||
* import('../iterateJsdoc.js').Integer,
|
||||
* import('../iterateJsdoc.js').Integer
|
||||
* ]}
|
||||
*/
|
||||
const getLinesCols = (text) => {
|
||||
const matchLines = countChars(text, '\n');
|
||||
|
||||
const colDelta = matchLines ?
|
||||
text.slice(text.lastIndexOf('\n') + 1).length :
|
||||
text.length;
|
||||
|
||||
return [
|
||||
matchLines, colDelta,
|
||||
];
|
||||
};
|
||||
|
||||
export default iterateJsdoc(({
|
||||
report,
|
||||
utils,
|
||||
context,
|
||||
globalState,
|
||||
}) => {
|
||||
if (semver.gte(ESLint.version, '8.0.0')) {
|
||||
report(
|
||||
'This rule does not work for ESLint 8+; you should disable this rule and use' +
|
||||
'the processor mentioned in the docs.',
|
||||
null,
|
||||
{
|
||||
column: 1,
|
||||
line: 1,
|
||||
},
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!globalState.has('checkExamples-matchingFileName')) {
|
||||
globalState.set('checkExamples-matchingFileName', new Map());
|
||||
}
|
||||
|
||||
const matchingFileNameMap = /** @type {Map<string, string>} */ (
|
||||
globalState.get('checkExamples-matchingFileName')
|
||||
);
|
||||
|
||||
const options = context.options[0] || {};
|
||||
let {
|
||||
exampleCodeRegex = null,
|
||||
rejectExampleCodeRegex = null,
|
||||
} = options;
|
||||
const {
|
||||
checkDefaults = false,
|
||||
checkParams = false,
|
||||
checkProperties = false,
|
||||
noDefaultExampleRules = false,
|
||||
checkEslintrc = true,
|
||||
matchingFileName = null,
|
||||
matchingFileNameDefaults = null,
|
||||
matchingFileNameParams = null,
|
||||
matchingFileNameProperties = null,
|
||||
paddedIndent = 0,
|
||||
baseConfig = {},
|
||||
configFile,
|
||||
allowInlineConfig = true,
|
||||
reportUnusedDisableDirectives = true,
|
||||
captionRequired = false,
|
||||
} = options;
|
||||
|
||||
// Make this configurable?
|
||||
/**
|
||||
* @type {never[]}
|
||||
*/
|
||||
const rulePaths = [];
|
||||
|
||||
const mdRules = noDefaultExampleRules ? undefined : defaultMdRules;
|
||||
|
||||
const expressionRules = noDefaultExampleRules ? undefined : defaultExpressionRules;
|
||||
|
||||
if (exampleCodeRegex) {
|
||||
exampleCodeRegex = utils.getRegexFromString(exampleCodeRegex);
|
||||
}
|
||||
|
||||
if (rejectExampleCodeRegex) {
|
||||
rejectExampleCodeRegex = utils.getRegexFromString(rejectExampleCodeRegex);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* filename: string,
|
||||
* defaultFileName: string|undefined,
|
||||
* source: string,
|
||||
* targetTagName: string,
|
||||
* rules?: import('eslint').Linter.RulesRecord|undefined,
|
||||
* lines?: import('../iterateJsdoc.js').Integer,
|
||||
* cols?: import('../iterateJsdoc.js').Integer,
|
||||
* skipInit?: boolean,
|
||||
* sources?: {
|
||||
* nonJSPrefacingCols: import('../iterateJsdoc.js').Integer,
|
||||
* nonJSPrefacingLines: import('../iterateJsdoc.js').Integer,
|
||||
* string: string,
|
||||
* }[],
|
||||
* tag?: import('comment-parser').Spec & {
|
||||
* line?: import('../iterateJsdoc.js').Integer,
|
||||
* }|{
|
||||
* line: import('../iterateJsdoc.js').Integer,
|
||||
* }
|
||||
* }} cfg
|
||||
*/
|
||||
const checkSource = ({
|
||||
filename,
|
||||
defaultFileName,
|
||||
rules = expressionRules,
|
||||
lines = 0,
|
||||
cols = 0,
|
||||
skipInit,
|
||||
source,
|
||||
targetTagName,
|
||||
sources = [],
|
||||
tag = {
|
||||
line: 0,
|
||||
},
|
||||
}) => {
|
||||
if (!skipInit) {
|
||||
sources.push({
|
||||
nonJSPrefacingCols: cols,
|
||||
nonJSPrefacingLines: lines,
|
||||
string: source,
|
||||
});
|
||||
}
|
||||
|
||||
// Todo: Make fixable
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* nonJSPrefacingCols: import('../iterateJsdoc.js').Integer,
|
||||
* nonJSPrefacingLines: import('../iterateJsdoc.js').Integer,
|
||||
* string: string
|
||||
* }} cfg
|
||||
*/
|
||||
const checkRules = function ({
|
||||
nonJSPrefacingCols,
|
||||
nonJSPrefacingLines,
|
||||
string,
|
||||
}) {
|
||||
const cliConfig = {
|
||||
allowInlineConfig,
|
||||
baseConfig,
|
||||
configFile,
|
||||
reportUnusedDisableDirectives,
|
||||
rulePaths,
|
||||
rules,
|
||||
useEslintrc: checkEslintrc,
|
||||
};
|
||||
const cliConfigStr = JSON.stringify(cliConfig);
|
||||
|
||||
const src = paddedIndent ?
|
||||
string.replaceAll(new RegExp(`(^|\n) {${paddedIndent}}(?!$)`, 'gu'), '\n') :
|
||||
string;
|
||||
|
||||
// Programmatic ESLint API: https://eslint.org/docs/developer-guide/nodejs-api
|
||||
const fileNameMapKey = filename ?
|
||||
'a' + cliConfigStr + filename :
|
||||
'b' + cliConfigStr + defaultFileName;
|
||||
const file = filename || defaultFileName;
|
||||
let cliFile;
|
||||
if (matchingFileNameMap.has(fileNameMapKey)) {
|
||||
cliFile = matchingFileNameMap.get(fileNameMapKey);
|
||||
} else {
|
||||
const cli = new CLIEngine(cliConfig);
|
||||
let config;
|
||||
if (filename || checkEslintrc) {
|
||||
config = cli.getConfigForFile(file);
|
||||
}
|
||||
|
||||
// We need a new instance to ensure that the rules that may only
|
||||
// be available to `file` (if it has its own `.eslintrc`),
|
||||
// will be defined.
|
||||
cliFile = new CLIEngine({
|
||||
allowInlineConfig,
|
||||
baseConfig: {
|
||||
...baseConfig,
|
||||
...config,
|
||||
},
|
||||
configFile,
|
||||
reportUnusedDisableDirectives,
|
||||
rulePaths,
|
||||
rules,
|
||||
useEslintrc: false,
|
||||
});
|
||||
matchingFileNameMap.set(fileNameMapKey, cliFile);
|
||||
}
|
||||
|
||||
const {
|
||||
results: [
|
||||
{
|
||||
messages,
|
||||
},
|
||||
],
|
||||
} = cliFile.executeOnText(src);
|
||||
|
||||
if (!('line' in tag)) {
|
||||
tag.line = tag.source[0].number;
|
||||
}
|
||||
|
||||
// NOTE: `tag.line` can be 0 if of form `/** @tag ... */`
|
||||
const codeStartLine = /**
|
||||
* @type {import('comment-parser').Spec & {
|
||||
* line: import('../iterateJsdoc.js').Integer,
|
||||
* }}
|
||||
*/ (tag).line + nonJSPrefacingLines;
|
||||
const codeStartCol = likelyNestedJSDocIndentSpace;
|
||||
|
||||
for (const {
|
||||
message,
|
||||
line,
|
||||
column,
|
||||
severity,
|
||||
ruleId,
|
||||
} of messages) {
|
||||
const startLine = codeStartLine + line + zeroBasedLineIndexAdjust;
|
||||
const startCol = codeStartCol + (
|
||||
|
||||
// This might not work for line 0, but line 0 is unlikely for examples
|
||||
line <= 1 ? nonJSPrefacingCols + firstLinePrefixLength : preTagSpaceLength
|
||||
) + column;
|
||||
|
||||
report(
|
||||
'@' + targetTagName + ' ' + (severity === 2 ? 'error' : 'warning') +
|
||||
(ruleId ? ' (' + ruleId + ')' : '') + ': ' +
|
||||
message,
|
||||
null,
|
||||
{
|
||||
column: startCol,
|
||||
line: startLine,
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
for (const targetSource of sources) {
|
||||
checkRules(targetSource);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} filename
|
||||
* @param {string} [ext] Since `eslint-plugin-markdown` v2, and
|
||||
* ESLint 7, this is the default which other JS-fenced rules will used.
|
||||
* Formerly "md" was the default.
|
||||
* @returns {{defaultFileName: string|undefined, filename: string}}
|
||||
*/
|
||||
const getFilenameInfo = (filename, ext = 'md/*.js') => {
|
||||
let defaultFileName;
|
||||
if (!filename) {
|
||||
const jsFileName = context.getFilename();
|
||||
if (typeof jsFileName === 'string' && jsFileName.includes('.')) {
|
||||
defaultFileName = jsFileName.replace(/\.[^.]*$/u, `.${ext}`);
|
||||
} else {
|
||||
defaultFileName = `dummy.${ext}`;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
defaultFileName,
|
||||
filename,
|
||||
};
|
||||
};
|
||||
|
||||
if (checkDefaults) {
|
||||
const filenameInfo = getFilenameInfo(matchingFileNameDefaults, 'jsdoc-defaults');
|
||||
utils.forEachPreferredTag('default', (tag, targetTagName) => {
|
||||
if (!tag.description.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkSource({
|
||||
source: `(${utils.getTagDescription(tag)})`,
|
||||
targetTagName,
|
||||
...filenameInfo,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (checkParams) {
|
||||
const filenameInfo = getFilenameInfo(matchingFileNameParams, 'jsdoc-params');
|
||||
utils.forEachPreferredTag('param', (tag, targetTagName) => {
|
||||
if (!tag.default || !tag.default.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkSource({
|
||||
source: `(${tag.default})`,
|
||||
targetTagName,
|
||||
...filenameInfo,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (checkProperties) {
|
||||
const filenameInfo = getFilenameInfo(matchingFileNameProperties, 'jsdoc-properties');
|
||||
utils.forEachPreferredTag('property', (tag, targetTagName) => {
|
||||
if (!tag.default || !tag.default.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkSource({
|
||||
source: `(${tag.default})`,
|
||||
targetTagName,
|
||||
...filenameInfo,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const tagName = /** @type {string} */ (utils.getPreferredTagName({
|
||||
tagName: 'example',
|
||||
}));
|
||||
if (!utils.hasTag(tagName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const matchingFilenameInfo = getFilenameInfo(matchingFileName);
|
||||
|
||||
utils.forEachPreferredTag('example', (tag, targetTagName) => {
|
||||
let source = /** @type {string} */ (utils.getTagDescription(tag));
|
||||
const match = source.match(hasCaptionRegex);
|
||||
|
||||
if (captionRequired && (!match || !match[1].trim())) {
|
||||
report('Caption is expected for examples.', null, tag);
|
||||
}
|
||||
|
||||
source = source.replace(hasCaptionRegex, '');
|
||||
const [
|
||||
lines,
|
||||
cols,
|
||||
] = match ? getLinesCols(match[0]) : [
|
||||
0, 0,
|
||||
];
|
||||
|
||||
if (exampleCodeRegex && !exampleCodeRegex.test(source) ||
|
||||
rejectExampleCodeRegex && rejectExampleCodeRegex.test(source)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sources = [];
|
||||
let skipInit = false;
|
||||
if (exampleCodeRegex) {
|
||||
let nonJSPrefacingCols = 0;
|
||||
let nonJSPrefacingLines = 0;
|
||||
|
||||
let startingIndex = 0;
|
||||
let lastStringCount = 0;
|
||||
|
||||
let exampleCode;
|
||||
exampleCodeRegex.lastIndex = 0;
|
||||
while ((exampleCode = exampleCodeRegex.exec(source)) !== null) {
|
||||
const {
|
||||
index,
|
||||
'0': n0,
|
||||
'1': n1,
|
||||
} = exampleCode;
|
||||
|
||||
// Count anything preceding user regex match (can affect line numbering)
|
||||
const preMatch = source.slice(startingIndex, index);
|
||||
|
||||
const [
|
||||
preMatchLines,
|
||||
colDelta,
|
||||
] = getLinesCols(preMatch);
|
||||
|
||||
let nonJSPreface;
|
||||
let nonJSPrefaceLineCount;
|
||||
if (n1) {
|
||||
const idx = n0.indexOf(n1);
|
||||
nonJSPreface = n0.slice(0, idx);
|
||||
nonJSPrefaceLineCount = countChars(nonJSPreface, '\n');
|
||||
} else {
|
||||
nonJSPreface = '';
|
||||
nonJSPrefaceLineCount = 0;
|
||||
}
|
||||
|
||||
nonJSPrefacingLines += lastStringCount + preMatchLines + nonJSPrefaceLineCount;
|
||||
|
||||
// Ignore `preMatch` delta if newlines here
|
||||
if (nonJSPrefaceLineCount) {
|
||||
const charsInLastLine = nonJSPreface.slice(nonJSPreface.lastIndexOf('\n') + 1).length;
|
||||
|
||||
nonJSPrefacingCols += charsInLastLine;
|
||||
} else {
|
||||
nonJSPrefacingCols += colDelta + nonJSPreface.length;
|
||||
}
|
||||
|
||||
const string = n1 || n0;
|
||||
sources.push({
|
||||
nonJSPrefacingCols,
|
||||
nonJSPrefacingLines,
|
||||
string,
|
||||
});
|
||||
startingIndex = exampleCodeRegex.lastIndex;
|
||||
lastStringCount = countChars(string, '\n');
|
||||
if (!exampleCodeRegex.global) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
skipInit = true;
|
||||
}
|
||||
|
||||
checkSource({
|
||||
cols,
|
||||
lines,
|
||||
rules: mdRules,
|
||||
skipInit,
|
||||
source,
|
||||
sources,
|
||||
tag,
|
||||
targetTagName,
|
||||
...matchingFilenameInfo,
|
||||
});
|
||||
});
|
||||
}, {
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Ensures that (JavaScript) examples within JSDoc adhere to ESLint rules.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/check-examples.md#repos-sticky-header',
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
allowInlineConfig: {
|
||||
default: true,
|
||||
type: 'boolean',
|
||||
},
|
||||
baseConfig: {
|
||||
type: 'object',
|
||||
},
|
||||
captionRequired: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
checkDefaults: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
checkEslintrc: {
|
||||
default: true,
|
||||
type: 'boolean',
|
||||
},
|
||||
checkParams: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
checkProperties: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
configFile: {
|
||||
type: 'string',
|
||||
},
|
||||
exampleCodeRegex: {
|
||||
type: 'string',
|
||||
},
|
||||
matchingFileName: {
|
||||
type: 'string',
|
||||
},
|
||||
matchingFileNameDefaults: {
|
||||
type: 'string',
|
||||
},
|
||||
matchingFileNameParams: {
|
||||
type: 'string',
|
||||
},
|
||||
matchingFileNameProperties: {
|
||||
type: 'string',
|
||||
},
|
||||
noDefaultExampleRules: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
paddedIndent: {
|
||||
default: 0,
|
||||
type: 'integer',
|
||||
},
|
||||
rejectExampleCodeRegex: {
|
||||
type: 'string',
|
||||
},
|
||||
reportUnusedDisableDirectives: {
|
||||
default: true,
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
75
node_modules/eslint-plugin-jsdoc/src/rules/checkIndentation.js
generated
vendored
Normal file
75
node_modules/eslint-plugin-jsdoc/src/rules/checkIndentation.js
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @param {string[]} excludeTags
|
||||
* @returns {string}
|
||||
*/
|
||||
const maskExcludedContent = (str, excludeTags) => {
|
||||
const regContent = new RegExp(`([ \\t]+\\*)[ \\t]@(?:${excludeTags.join('|')})(?=[ \\n])([\\w|\\W]*?\\n)(?=[ \\t]*\\*(?:[ \\t]*@\\w+\\s|\\/))`, 'gu');
|
||||
|
||||
return str.replace(regContent, (_match, margin, code) => {
|
||||
return (margin + '\n').repeat(code.match(/\n/gu).length);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @returns {string}
|
||||
*/
|
||||
const maskCodeBlocks = (str) => {
|
||||
const regContent = /([ \t]+\*)[ \t]```[^\n]*?([\w|\W]*?\n)(?=[ \t]*\*(?:[ \t]*(?:```|@\w+\s)|\/))/gu;
|
||||
|
||||
return str.replaceAll(regContent, (_match, margin, code) => {
|
||||
return (margin + '\n').repeat(code.match(/\n/gu).length);
|
||||
});
|
||||
};
|
||||
|
||||
export default iterateJsdoc(({
|
||||
sourceCode,
|
||||
jsdocNode,
|
||||
report,
|
||||
context,
|
||||
}) => {
|
||||
const options = context.options[0] || {};
|
||||
const /** @type {{excludeTags: string[]}} */ {
|
||||
excludeTags = [
|
||||
'example',
|
||||
],
|
||||
} = options;
|
||||
|
||||
const reg = /^(?:\/?\**|[ \t]*)\*[ \t]{2}/gmu;
|
||||
const textWithoutCodeBlocks = maskCodeBlocks(sourceCode.getText(jsdocNode));
|
||||
const text = excludeTags.length ? maskExcludedContent(textWithoutCodeBlocks, excludeTags) : textWithoutCodeBlocks;
|
||||
|
||||
if (reg.test(text)) {
|
||||
const lineBreaks = text.slice(0, reg.lastIndex).match(/\n/gu) || [];
|
||||
report('There must be no indentation.', null, {
|
||||
line: lineBreaks.length,
|
||||
});
|
||||
}
|
||||
}, {
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Reports invalid padding inside JSDoc blocks.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/check-indentation.md#repos-sticky-header',
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
excludeTags: {
|
||||
items: {
|
||||
pattern: '^\\S+$',
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'layout',
|
||||
},
|
||||
});
|
||||
372
node_modules/eslint-plugin-jsdoc/src/rules/checkLineAlignment.js
generated
vendored
Normal file
372
node_modules/eslint-plugin-jsdoc/src/rules/checkLineAlignment.js
generated
vendored
Normal file
@@ -0,0 +1,372 @@
|
||||
import alignTransform from '../alignTransform.js';
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
import {
|
||||
transforms,
|
||||
} from 'comment-parser';
|
||||
|
||||
const {
|
||||
flow: commentFlow,
|
||||
} = transforms;
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* postDelimiter: import('../iterateJsdoc.js').Integer,
|
||||
* postHyphen: import('../iterateJsdoc.js').Integer,
|
||||
* postName: import('../iterateJsdoc.js').Integer,
|
||||
* postTag: import('../iterateJsdoc.js').Integer,
|
||||
* postType: import('../iterateJsdoc.js').Integer,
|
||||
* }} CustomSpacings
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {import('../iterateJsdoc.js').Utils} utils
|
||||
* @param {import('comment-parser').Spec & {
|
||||
* line: import('../iterateJsdoc.js').Integer
|
||||
* }} tag
|
||||
* @param {CustomSpacings} customSpacings
|
||||
*/
|
||||
const checkNotAlignedPerTag = (utils, tag, customSpacings) => {
|
||||
/*
|
||||
start +
|
||||
delimiter +
|
||||
postDelimiter +
|
||||
tag +
|
||||
postTag +
|
||||
type +
|
||||
postType +
|
||||
name +
|
||||
postName +
|
||||
description +
|
||||
end +
|
||||
lineEnd
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {"tag"|"type"|"name"|"description"} ContentProp
|
||||
*/
|
||||
|
||||
/** @type {("postDelimiter"|"postTag"|"postType"|"postName")[]} */
|
||||
let spacerProps;
|
||||
/** @type {ContentProp[]} */
|
||||
let contentProps;
|
||||
const mightHaveNamepath = utils.tagMightHaveNamepath(tag.tag);
|
||||
if (mightHaveNamepath) {
|
||||
spacerProps = [
|
||||
'postDelimiter', 'postTag', 'postType', 'postName',
|
||||
];
|
||||
contentProps = [
|
||||
'tag', 'type', 'name', 'description',
|
||||
];
|
||||
} else {
|
||||
spacerProps = [
|
||||
'postDelimiter', 'postTag', 'postType',
|
||||
];
|
||||
contentProps = [
|
||||
'tag', 'type', 'description',
|
||||
];
|
||||
}
|
||||
|
||||
const {
|
||||
tokens,
|
||||
} = tag.source[0];
|
||||
|
||||
/**
|
||||
* @param {import('../iterateJsdoc.js').Integer} idx
|
||||
* @param {(notRet: boolean, contentProp: ContentProp) => void} [callbck]
|
||||
*/
|
||||
const followedBySpace = (idx, callbck) => {
|
||||
const nextIndex = idx + 1;
|
||||
|
||||
return spacerProps.slice(nextIndex).some((spacerProp, innerIdx) => {
|
||||
const contentProp = contentProps[nextIndex + innerIdx];
|
||||
|
||||
const spacePropVal = tokens[spacerProp];
|
||||
|
||||
const ret = spacePropVal;
|
||||
|
||||
if (callbck) {
|
||||
callbck(!ret, contentProp);
|
||||
}
|
||||
|
||||
return ret && (callbck || !contentProp);
|
||||
});
|
||||
};
|
||||
|
||||
const postHyphenSpacing = customSpacings?.postHyphen ?? 1;
|
||||
const exactHyphenSpacing = new RegExp(`^\\s*-\\s{${postHyphenSpacing},${postHyphenSpacing}}(?!\\s)`, 'u');
|
||||
const hasNoHyphen = !(/^\s*-(?!$)(?=\s)/u).test(tokens.description);
|
||||
const hasExactHyphenSpacing = exactHyphenSpacing.test(
|
||||
tokens.description,
|
||||
);
|
||||
|
||||
// If checking alignment on multiple lines, need to check other `source`
|
||||
// items
|
||||
// Go through `post*` spacing properties and exit to indicate problem if
|
||||
// extra spacing detected
|
||||
const ok = !spacerProps.some((spacerProp, idx) => {
|
||||
const contentProp = contentProps[idx];
|
||||
const contentPropVal = tokens[contentProp];
|
||||
const spacerPropVal = tokens[spacerProp];
|
||||
const spacing = customSpacings?.[spacerProp] || 1;
|
||||
|
||||
// There will be extra alignment if...
|
||||
|
||||
// 1. The spaces don't match the space it should have (1 or custom spacing) OR
|
||||
return spacerPropVal.length !== spacing && spacerPropVal.length !== 0 ||
|
||||
|
||||
// 2. There is a (single) space, no immediate content, and yet another
|
||||
// space is found subsequently (not separated by intervening content)
|
||||
spacerPropVal && !contentPropVal && followedBySpace(idx);
|
||||
}) && (hasNoHyphen || hasExactHyphenSpacing);
|
||||
if (ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fix = () => {
|
||||
for (const [
|
||||
idx,
|
||||
spacerProp,
|
||||
] of spacerProps.entries()) {
|
||||
const contentProp = contentProps[idx];
|
||||
const contentPropVal = tokens[contentProp];
|
||||
|
||||
if (contentPropVal) {
|
||||
const spacing = customSpacings?.[spacerProp] || 1;
|
||||
tokens[spacerProp] = ''.padStart(spacing, ' ');
|
||||
followedBySpace(idx, (hasSpace, contentPrp) => {
|
||||
if (hasSpace) {
|
||||
tokens[contentPrp] = '';
|
||||
}
|
||||
});
|
||||
} else {
|
||||
tokens[spacerProp] = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasExactHyphenSpacing) {
|
||||
const hyphenSpacing = /^\s*-\s+/u;
|
||||
tokens.description = tokens.description.replace(
|
||||
hyphenSpacing, '-' + ''.padStart(postHyphenSpacing, ' '),
|
||||
);
|
||||
}
|
||||
|
||||
utils.setTag(tag, tokens);
|
||||
};
|
||||
|
||||
utils.reportJSDoc('Expected JSDoc block lines to not be aligned.', tag, fix, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {object} cfg
|
||||
* @param {CustomSpacings} cfg.customSpacings
|
||||
* @param {string} cfg.indent
|
||||
* @param {import('comment-parser').Block} cfg.jsdoc
|
||||
* @param {import('eslint').Rule.Node & {
|
||||
* range: [number, number]
|
||||
* }} cfg.jsdocNode
|
||||
* @param {boolean} cfg.preserveMainDescriptionPostDelimiter
|
||||
* @param {import('../iterateJsdoc.js').Report} cfg.report
|
||||
* @param {string[]} cfg.tags
|
||||
* @param {import('../iterateJsdoc.js').Utils} cfg.utils
|
||||
* @param {string} cfg.wrapIndent
|
||||
* @param {boolean} cfg.disableWrapIndent
|
||||
* @returns {void}
|
||||
*/
|
||||
const checkAlignment = ({
|
||||
customSpacings,
|
||||
indent,
|
||||
jsdoc,
|
||||
jsdocNode,
|
||||
preserveMainDescriptionPostDelimiter,
|
||||
report,
|
||||
tags,
|
||||
utils,
|
||||
wrapIndent,
|
||||
disableWrapIndent,
|
||||
}) => {
|
||||
const transform = commentFlow(
|
||||
alignTransform({
|
||||
customSpacings,
|
||||
indent,
|
||||
preserveMainDescriptionPostDelimiter,
|
||||
tags,
|
||||
wrapIndent,
|
||||
disableWrapIndent,
|
||||
}),
|
||||
);
|
||||
const transformedJsdoc = transform(jsdoc);
|
||||
|
||||
const comment = '/*' +
|
||||
/**
|
||||
* @type {import('eslint').Rule.Node & {
|
||||
* range: [number, number], value: string
|
||||
* }}
|
||||
*/ (jsdocNode).value + '*/';
|
||||
|
||||
const formatted = utils.stringify(transformedJsdoc)
|
||||
.trimStart();
|
||||
|
||||
if (comment !== formatted) {
|
||||
report(
|
||||
'Expected JSDoc block lines to be aligned.',
|
||||
/** @type {import('eslint').Rule.ReportFixer} */ (fixer) => {
|
||||
return fixer.replaceText(jsdocNode, formatted);
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default iterateJsdoc(({
|
||||
indent,
|
||||
jsdoc,
|
||||
jsdocNode,
|
||||
report,
|
||||
context,
|
||||
utils,
|
||||
}) => {
|
||||
const {
|
||||
tags: applicableTags = [
|
||||
'param', 'arg', 'argument', 'property', 'prop', 'returns', 'return',
|
||||
],
|
||||
preserveMainDescriptionPostDelimiter,
|
||||
customSpacings,
|
||||
wrapIndent = '',
|
||||
disableWrapIndent = false,
|
||||
} = context.options[1] || {};
|
||||
|
||||
if (context.options[0] === 'always') {
|
||||
// Skip if it contains only a single line.
|
||||
if (!(
|
||||
/**
|
||||
* @type {import('eslint').Rule.Node & {
|
||||
* range: [number, number], value: string
|
||||
* }}
|
||||
*/
|
||||
(jsdocNode).value.includes('\n')
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkAlignment({
|
||||
customSpacings,
|
||||
indent,
|
||||
jsdoc,
|
||||
jsdocNode,
|
||||
preserveMainDescriptionPostDelimiter,
|
||||
report,
|
||||
tags: applicableTags,
|
||||
utils,
|
||||
wrapIndent,
|
||||
disableWrapIndent,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const foundTags = utils.getPresentTags(applicableTags);
|
||||
if (context.options[0] !== 'any') {
|
||||
for (const tag of foundTags) {
|
||||
checkNotAlignedPerTag(
|
||||
utils,
|
||||
/**
|
||||
* @type {import('comment-parser').Spec & {
|
||||
* line: import('../iterateJsdoc.js').Integer
|
||||
* }}
|
||||
*/
|
||||
(tag),
|
||||
customSpacings,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (const tag of foundTags) {
|
||||
if (tag.source.length > 1) {
|
||||
let idx = 0;
|
||||
for (const {
|
||||
tokens,
|
||||
// Avoid the tag line
|
||||
} of tag.source.slice(1)) {
|
||||
idx++;
|
||||
|
||||
if (
|
||||
!tokens.description ||
|
||||
// Avoid first lines after multiline type
|
||||
tokens.type ||
|
||||
tokens.name
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't include a single separating space/tab
|
||||
if (!disableWrapIndent && tokens.postDelimiter.slice(1) !== wrapIndent) {
|
||||
utils.reportJSDoc('Expected wrap indent', {
|
||||
line: tag.source[0].number + idx,
|
||||
}, () => {
|
||||
tokens.postDelimiter = tokens.postDelimiter.charAt(0) + wrapIndent;
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Reports invalid alignment of JSDoc block lines.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/check-line-alignment.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'whitespace',
|
||||
schema: [
|
||||
{
|
||||
enum: [
|
||||
'always', 'never', 'any',
|
||||
],
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
customSpacings: {
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
postDelimiter: {
|
||||
type: 'integer',
|
||||
},
|
||||
postHyphen: {
|
||||
type: 'integer',
|
||||
},
|
||||
postName: {
|
||||
type: 'integer',
|
||||
},
|
||||
postTag: {
|
||||
type: 'integer',
|
||||
},
|
||||
postType: {
|
||||
type: 'integer',
|
||||
},
|
||||
},
|
||||
},
|
||||
preserveMainDescriptionPostDelimiter: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
tags: {
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
wrapIndent: {
|
||||
type: 'string',
|
||||
},
|
||||
disableWrapIndent: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'layout',
|
||||
},
|
||||
});
|
||||
454
node_modules/eslint-plugin-jsdoc/src/rules/checkParamNames.js
generated
vendored
Normal file
454
node_modules/eslint-plugin-jsdoc/src/rules/checkParamNames.js
generated
vendored
Normal file
@@ -0,0 +1,454 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
/**
|
||||
* @param {string} targetTagName
|
||||
* @param {boolean} allowExtraTrailingParamDocs
|
||||
* @param {boolean} checkDestructured
|
||||
* @param {boolean} checkRestProperty
|
||||
* @param {RegExp} checkTypesRegex
|
||||
* @param {boolean} disableExtraPropertyReporting
|
||||
* @param {boolean} disableMissingParamChecks
|
||||
* @param {boolean} enableFixer
|
||||
* @param {import('../jsdocUtils.js').ParamNameInfo[]} functionParameterNames
|
||||
* @param {import('comment-parser').Block} jsdoc
|
||||
* @param {import('../iterateJsdoc.js').Utils} utils
|
||||
* @param {import('../iterateJsdoc.js').Report} report
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const validateParameterNames = (
|
||||
targetTagName,
|
||||
allowExtraTrailingParamDocs,
|
||||
checkDestructured,
|
||||
checkRestProperty,
|
||||
checkTypesRegex,
|
||||
disableExtraPropertyReporting,
|
||||
disableMissingParamChecks,
|
||||
enableFixer,
|
||||
functionParameterNames, jsdoc, utils, report,
|
||||
) => {
|
||||
const paramTags = Object.entries(jsdoc.tags).filter(([
|
||||
, tag,
|
||||
]) => {
|
||||
return tag.tag === targetTagName;
|
||||
});
|
||||
const paramTagsNonNested = paramTags.filter(([
|
||||
, tag,
|
||||
]) => {
|
||||
return !tag.name.includes('.');
|
||||
});
|
||||
|
||||
let dotted = 0;
|
||||
let thisOffset = 0;
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
return paramTags.some(([
|
||||
, tag,
|
||||
], index) => {
|
||||
/** @type {import('../iterateJsdoc.js').Integer} */
|
||||
let tagsIndex;
|
||||
const dupeTagInfo = paramTags.find(([
|
||||
tgsIndex,
|
||||
tg,
|
||||
], idx) => {
|
||||
tagsIndex = Number(tgsIndex);
|
||||
|
||||
return tg.name === tag.name && idx !== index;
|
||||
});
|
||||
if (dupeTagInfo) {
|
||||
utils.reportJSDoc(`Duplicate @${targetTagName} "${tag.name}"`, dupeTagInfo[1], enableFixer ? () => {
|
||||
utils.removeTag(tagsIndex);
|
||||
} : null);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tag.name.includes('.')) {
|
||||
dotted++;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
let functionParameterName = functionParameterNames[index - dotted + thisOffset];
|
||||
if (functionParameterName === 'this' && tag.name.trim() !== 'this') {
|
||||
++thisOffset;
|
||||
functionParameterName = functionParameterNames[index - dotted + thisOffset];
|
||||
}
|
||||
|
||||
if (!functionParameterName) {
|
||||
if (allowExtraTrailingParamDocs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
report(
|
||||
`@${targetTagName} "${tag.name}" does not match an existing function parameter.`,
|
||||
null,
|
||||
tag,
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
typeof functionParameterName === 'object' &&
|
||||
'name' in functionParameterName &&
|
||||
Array.isArray(functionParameterName.name)
|
||||
) {
|
||||
const actualName = tag.name.trim();
|
||||
const expectedName = functionParameterName.name[index];
|
||||
if (actualName === expectedName) {
|
||||
thisOffset--;
|
||||
return false;
|
||||
}
|
||||
report(
|
||||
`Expected @${targetTagName} name to be "${expectedName}". Got "${actualName}".`,
|
||||
null,
|
||||
tag,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Array.isArray(functionParameterName)) {
|
||||
if (!checkDestructured) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tag.type && tag.type.search(checkTypesRegex) === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const [
|
||||
parameterName,
|
||||
{
|
||||
names: properties,
|
||||
hasPropertyRest,
|
||||
rests,
|
||||
annotationParamName,
|
||||
},
|
||||
] =
|
||||
/**
|
||||
* @type {[string | undefined, import('../jsdocUtils.js').FlattendRootInfo & {
|
||||
* annotationParamName?: string | undefined;
|
||||
}]} */ (functionParameterName);
|
||||
if (annotationParamName !== undefined) {
|
||||
const name = tag.name.trim();
|
||||
if (name !== annotationParamName) {
|
||||
report(`@${targetTagName} "${name}" does not match parameter name "${annotationParamName}"`, null, tag);
|
||||
}
|
||||
}
|
||||
|
||||
const tagName = parameterName === undefined ? tag.name.trim() : parameterName;
|
||||
const expectedNames = properties.map((name) => {
|
||||
return `${tagName}.${name}`;
|
||||
});
|
||||
const actualNames = paramTags.map(([
|
||||
, paramTag,
|
||||
]) => {
|
||||
return paramTag.name.trim();
|
||||
});
|
||||
const actualTypes = paramTags.map(([
|
||||
, paramTag,
|
||||
]) => {
|
||||
return paramTag.type;
|
||||
});
|
||||
|
||||
const missingProperties = [];
|
||||
|
||||
/** @type {string[]} */
|
||||
const notCheckingNames = [];
|
||||
|
||||
for (const [
|
||||
idx,
|
||||
name,
|
||||
] of expectedNames.entries()) {
|
||||
if (notCheckingNames.some((notCheckingName) => {
|
||||
return name.startsWith(notCheckingName);
|
||||
})) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const actualNameIdx = actualNames.findIndex((actualName) => {
|
||||
return utils.comparePaths(name)(actualName);
|
||||
});
|
||||
if (actualNameIdx === -1) {
|
||||
if (!checkRestProperty && rests[idx]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const missingIndex = actualNames.findIndex((actualName) => {
|
||||
return utils.pathDoesNotBeginWith(name, actualName);
|
||||
});
|
||||
const line = tag.source[0].number - 1 + (missingIndex > -1 ? missingIndex : actualNames.length);
|
||||
missingProperties.push({
|
||||
name,
|
||||
tagPlacement: {
|
||||
line: line === 0 ? 1 : line,
|
||||
},
|
||||
});
|
||||
} else if (actualTypes[actualNameIdx].search(checkTypesRegex) === -1 && actualTypes[actualNameIdx] !== '') {
|
||||
notCheckingNames.push(name);
|
||||
}
|
||||
}
|
||||
|
||||
const hasMissing = missingProperties.length;
|
||||
if (hasMissing) {
|
||||
for (const {
|
||||
tagPlacement,
|
||||
name: missingProperty,
|
||||
} of missingProperties) {
|
||||
report(`Missing @${targetTagName} "${missingProperty}"`, null, tagPlacement);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasPropertyRest || checkRestProperty) {
|
||||
/** @type {[string, import('comment-parser').Spec][]} */
|
||||
const extraProperties = [];
|
||||
for (const [
|
||||
idx,
|
||||
name,
|
||||
] of actualNames.entries()) {
|
||||
const match = name.startsWith(tag.name.trim() + '.');
|
||||
if (
|
||||
match && !expectedNames.some(
|
||||
utils.comparePaths(name),
|
||||
) && !utils.comparePaths(name)(tag.name) &&
|
||||
(!disableExtraPropertyReporting || properties.some((prop) => {
|
||||
return prop.split('.').length >= name.split('.').length - 1;
|
||||
}))
|
||||
) {
|
||||
extraProperties.push([
|
||||
name, paramTags[idx][1],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (extraProperties.length) {
|
||||
for (const [
|
||||
extraProperty,
|
||||
tg,
|
||||
] of extraProperties) {
|
||||
report(`@${targetTagName} "${extraProperty}" does not exist on ${tag.name}`, null, tg);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return hasMissing;
|
||||
}
|
||||
|
||||
let funcParamName;
|
||||
if (typeof functionParameterName === 'object') {
|
||||
const {
|
||||
name,
|
||||
} = functionParameterName;
|
||||
funcParamName = name;
|
||||
} else {
|
||||
funcParamName = functionParameterName;
|
||||
}
|
||||
|
||||
if (funcParamName !== tag.name.trim()) {
|
||||
// Todo: Improve for array or object child items
|
||||
const actualNames = paramTagsNonNested.map(([
|
||||
, {
|
||||
name,
|
||||
},
|
||||
]) => {
|
||||
return name.trim();
|
||||
});
|
||||
|
||||
const expectedNames = functionParameterNames.map((item, idx) => {
|
||||
if (/**
|
||||
* @type {[string|undefined, (import('../jsdocUtils.js').FlattendRootInfo & {
|
||||
* annotationParamName?: string,
|
||||
})]} */ (item)?.[1]?.names) {
|
||||
return actualNames[idx];
|
||||
}
|
||||
|
||||
return item;
|
||||
}).filter((item) => {
|
||||
return item !== 'this';
|
||||
});
|
||||
|
||||
// When disableMissingParamChecks is true tag names can be omitted.
|
||||
// Report when the tag names do not match the expected names or they are used out of order.
|
||||
if (disableMissingParamChecks) {
|
||||
const usedExpectedNames = expectedNames.map(a => a?.toString()).filter(expectedName => expectedName && actualNames.includes(expectedName));
|
||||
const usedInOrder = actualNames.every((actualName, idx) => actualName === usedExpectedNames[idx]);
|
||||
if (usedInOrder) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
report(
|
||||
`Expected @${targetTagName} names to be "${
|
||||
expectedNames.map((expectedName) => {
|
||||
return typeof expectedName === 'object' &&
|
||||
'name' in expectedName &&
|
||||
expectedName.restElement
|
||||
? '...' + expectedName.name
|
||||
: expectedName;
|
||||
}).join(', ')
|
||||
}". Got "${actualNames.join(', ')}".`,
|
||||
null,
|
||||
tag,
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} targetTagName
|
||||
* @param {boolean} _allowExtraTrailingParamDocs
|
||||
* @param {{
|
||||
* name: string,
|
||||
* idx: import('../iterateJsdoc.js').Integer
|
||||
* }[]} jsdocParameterNames
|
||||
* @param {import('comment-parser').Block} jsdoc
|
||||
* @param {Function} report
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const validateParameterNamesDeep = (
|
||||
targetTagName, _allowExtraTrailingParamDocs,
|
||||
jsdocParameterNames, jsdoc, report,
|
||||
) => {
|
||||
/** @type {string} */
|
||||
let lastRealParameter;
|
||||
|
||||
return jsdocParameterNames.some(({
|
||||
name: jsdocParameterName,
|
||||
idx,
|
||||
}) => {
|
||||
const isPropertyPath = jsdocParameterName.includes('.');
|
||||
|
||||
if (isPropertyPath) {
|
||||
if (!lastRealParameter) {
|
||||
report(`@${targetTagName} path declaration ("${jsdocParameterName}") appears before any real parameter.`, null, jsdoc.tags[idx]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
let pathRootNodeName = jsdocParameterName.slice(0, jsdocParameterName.indexOf('.'));
|
||||
|
||||
if (pathRootNodeName.endsWith('[]')) {
|
||||
pathRootNodeName = pathRootNodeName.slice(0, -2);
|
||||
}
|
||||
|
||||
if (pathRootNodeName !== lastRealParameter) {
|
||||
report(
|
||||
`@${targetTagName} path declaration ("${jsdocParameterName}") root node name ("${pathRootNodeName}") ` +
|
||||
`does not match previous real parameter name ("${lastRealParameter}").`,
|
||||
null,
|
||||
jsdoc.tags[idx],
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
lastRealParameter = jsdocParameterName;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
export default iterateJsdoc(({
|
||||
context,
|
||||
jsdoc,
|
||||
report,
|
||||
utils,
|
||||
}) => {
|
||||
const {
|
||||
allowExtraTrailingParamDocs,
|
||||
checkDestructured = true,
|
||||
checkRestProperty = false,
|
||||
checkTypesPattern = '/^(?:[oO]bject|[aA]rray|PlainObject|Generic(?:Object|Array))$/',
|
||||
enableFixer = false,
|
||||
useDefaultObjectProperties = false,
|
||||
disableExtraPropertyReporting = false,
|
||||
disableMissingParamChecks = false,
|
||||
} = context.options[0] || {};
|
||||
|
||||
const checkTypesRegex = utils.getRegexFromString(checkTypesPattern);
|
||||
|
||||
const jsdocParameterNamesDeep = utils.getJsdocTagsDeep('param');
|
||||
if (!jsdocParameterNamesDeep || !jsdocParameterNamesDeep.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const functionParameterNames = utils.getFunctionParameterNames(useDefaultObjectProperties);
|
||||
|
||||
const targetTagName = /** @type {string} */ (utils.getPreferredTagName({
|
||||
tagName: 'param',
|
||||
}));
|
||||
const isError = validateParameterNames(
|
||||
targetTagName,
|
||||
allowExtraTrailingParamDocs,
|
||||
checkDestructured,
|
||||
checkRestProperty,
|
||||
checkTypesRegex,
|
||||
disableExtraPropertyReporting,
|
||||
disableMissingParamChecks,
|
||||
enableFixer,
|
||||
functionParameterNames,
|
||||
jsdoc,
|
||||
utils,
|
||||
report,
|
||||
);
|
||||
|
||||
if (isError || !checkDestructured) {
|
||||
return;
|
||||
}
|
||||
|
||||
validateParameterNamesDeep(
|
||||
targetTagName, allowExtraTrailingParamDocs, jsdocParameterNamesDeep, jsdoc, report,
|
||||
);
|
||||
}, {
|
||||
contextDefaults: [
|
||||
'ArrowFunctionExpression', 'FunctionDeclaration', 'FunctionExpression', 'TSDeclareFunction',
|
||||
// Add this to above defaults
|
||||
'TSMethodSignature'
|
||||
],
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Ensures that parameter names in JSDoc match those in the function declaration.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/check-param-names.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
allowExtraTrailingParamDocs: {
|
||||
type: 'boolean',
|
||||
},
|
||||
checkDestructured: {
|
||||
type: 'boolean',
|
||||
},
|
||||
checkRestProperty: {
|
||||
type: 'boolean',
|
||||
},
|
||||
checkTypesPattern: {
|
||||
type: 'string',
|
||||
},
|
||||
disableExtraPropertyReporting: {
|
||||
type: 'boolean',
|
||||
},
|
||||
disableMissingParamChecks: {
|
||||
type: 'boolean',
|
||||
},
|
||||
enableFixer: {
|
||||
type: 'boolean',
|
||||
},
|
||||
useDefaultObjectProperties: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
152
node_modules/eslint-plugin-jsdoc/src/rules/checkPropertyNames.js
generated
vendored
Normal file
152
node_modules/eslint-plugin-jsdoc/src/rules/checkPropertyNames.js
generated
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
/**
|
||||
* @param {string} targetTagName
|
||||
* @param {boolean} enableFixer
|
||||
* @param {import('comment-parser').Block} jsdoc
|
||||
* @param {import('../iterateJsdoc.js').Utils} utils
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const validatePropertyNames = (
|
||||
targetTagName,
|
||||
enableFixer,
|
||||
jsdoc, utils,
|
||||
) => {
|
||||
const propertyTags = Object.entries(jsdoc.tags).filter(([
|
||||
, tag,
|
||||
]) => {
|
||||
return tag.tag === targetTagName;
|
||||
});
|
||||
|
||||
return propertyTags.some(([
|
||||
, tag,
|
||||
], index) => {
|
||||
/** @type {import('../iterateJsdoc.js').Integer} */
|
||||
let tagsIndex;
|
||||
const dupeTagInfo = propertyTags.find(([
|
||||
tgsIndex,
|
||||
tg,
|
||||
], idx) => {
|
||||
tagsIndex = Number(tgsIndex);
|
||||
|
||||
return tg.name === tag.name && idx !== index;
|
||||
});
|
||||
if (dupeTagInfo) {
|
||||
utils.reportJSDoc(`Duplicate @${targetTagName} "${tag.name}"`, dupeTagInfo[1], enableFixer ? () => {
|
||||
utils.removeTag(tagsIndex);
|
||||
} : null);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} targetTagName
|
||||
* @param {{
|
||||
* idx: number;
|
||||
* name: string;
|
||||
* type: string;
|
||||
* }[]} jsdocPropertyNames
|
||||
* @param {import('comment-parser').Block} jsdoc
|
||||
* @param {Function} report
|
||||
*/
|
||||
const validatePropertyNamesDeep = (
|
||||
targetTagName,
|
||||
jsdocPropertyNames, jsdoc, report,
|
||||
) => {
|
||||
/** @type {string} */
|
||||
let lastRealProperty;
|
||||
|
||||
return jsdocPropertyNames.some(({
|
||||
name: jsdocPropertyName,
|
||||
idx,
|
||||
}) => {
|
||||
const isPropertyPath = jsdocPropertyName.includes('.');
|
||||
|
||||
if (isPropertyPath) {
|
||||
if (!lastRealProperty) {
|
||||
report(`@${targetTagName} path declaration ("${jsdocPropertyName}") appears before any real property.`, null, jsdoc.tags[idx]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
let pathRootNodeName = jsdocPropertyName.slice(0, jsdocPropertyName.indexOf('.'));
|
||||
|
||||
if (pathRootNodeName.endsWith('[]')) {
|
||||
pathRootNodeName = pathRootNodeName.slice(0, -2);
|
||||
}
|
||||
|
||||
if (pathRootNodeName !== lastRealProperty) {
|
||||
report(
|
||||
`@${targetTagName} path declaration ("${jsdocPropertyName}") root node name ("${pathRootNodeName}") ` +
|
||||
`does not match previous real property name ("${lastRealProperty}").`,
|
||||
null,
|
||||
jsdoc.tags[idx],
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
lastRealProperty = jsdocPropertyName;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
export default iterateJsdoc(({
|
||||
context,
|
||||
jsdoc,
|
||||
report,
|
||||
utils,
|
||||
}) => {
|
||||
const {
|
||||
enableFixer = false,
|
||||
} = context.options[0] || {};
|
||||
const jsdocPropertyNamesDeep = utils.getJsdocTagsDeep('property');
|
||||
if (!jsdocPropertyNamesDeep || !jsdocPropertyNamesDeep.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetTagName = /** @type {string} */ (utils.getPreferredTagName({
|
||||
tagName: 'property',
|
||||
}));
|
||||
const isError = validatePropertyNames(
|
||||
targetTagName,
|
||||
enableFixer,
|
||||
jsdoc,
|
||||
utils,
|
||||
);
|
||||
|
||||
if (isError) {
|
||||
return;
|
||||
}
|
||||
|
||||
validatePropertyNamesDeep(
|
||||
targetTagName, jsdocPropertyNamesDeep, jsdoc, report,
|
||||
);
|
||||
}, {
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Ensures that property names in JSDoc are not duplicated on the same block and that nested properties have defined roots.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/check-property-names.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
enableFixer: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
30
node_modules/eslint-plugin-jsdoc/src/rules/checkSyntax.js
generated
vendored
Normal file
30
node_modules/eslint-plugin-jsdoc/src/rules/checkSyntax.js
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
export default iterateJsdoc(({
|
||||
jsdoc,
|
||||
report,
|
||||
settings,
|
||||
}) => {
|
||||
const {
|
||||
mode,
|
||||
} = settings;
|
||||
|
||||
// Don't check for "permissive" and "closure"
|
||||
if (mode === 'jsdoc' || mode === 'typescript') {
|
||||
for (const tag of jsdoc.tags) {
|
||||
if (tag.type.slice(-1) === '=') {
|
||||
report('Syntax should not be Google Closure Compiler style.', null, tag);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Reports against syntax not valid for the mode (e.g., Google Closure Compiler in non-Closure mode).',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/check-syntax.md#repos-sticky-header',
|
||||
},
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
314
node_modules/eslint-plugin-jsdoc/src/rules/checkTagNames.js
generated
vendored
Normal file
314
node_modules/eslint-plugin-jsdoc/src/rules/checkTagNames.js
generated
vendored
Normal file
@@ -0,0 +1,314 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
import escapeStringRegexp from 'escape-string-regexp';
|
||||
|
||||
// https://babeljs.io/docs/en/babel-plugin-transform-react-jsx/
|
||||
const jsxTagNames = new Set([
|
||||
'jsx',
|
||||
'jsxFrag',
|
||||
'jsxImportSource',
|
||||
'jsxRuntime',
|
||||
]);
|
||||
|
||||
const typedTagsAlwaysUnnecessary = new Set([
|
||||
'augments',
|
||||
'callback',
|
||||
'class',
|
||||
'enum',
|
||||
'implements',
|
||||
'private',
|
||||
'property',
|
||||
'protected',
|
||||
'public',
|
||||
'readonly',
|
||||
'this',
|
||||
'type',
|
||||
'typedef',
|
||||
]);
|
||||
|
||||
const typedTagsNeedingName = new Set([
|
||||
'template',
|
||||
]);
|
||||
|
||||
const typedTagsUnnecessaryOutsideDeclare = new Set([
|
||||
'abstract',
|
||||
'access',
|
||||
'class',
|
||||
'constant',
|
||||
'constructs',
|
||||
'default',
|
||||
'enum',
|
||||
'export',
|
||||
'exports',
|
||||
'function',
|
||||
'global',
|
||||
'inherits',
|
||||
'instance',
|
||||
'interface',
|
||||
'member',
|
||||
'memberof',
|
||||
'memberOf',
|
||||
'method',
|
||||
'mixes',
|
||||
'mixin',
|
||||
'module',
|
||||
'name',
|
||||
'namespace',
|
||||
'override',
|
||||
'property',
|
||||
'requires',
|
||||
'static',
|
||||
'this',
|
||||
]);
|
||||
|
||||
export default iterateJsdoc(({
|
||||
sourceCode,
|
||||
jsdoc,
|
||||
report,
|
||||
utils,
|
||||
context,
|
||||
node,
|
||||
settings,
|
||||
jsdocNode,
|
||||
}) => {
|
||||
const
|
||||
/**
|
||||
* @type {{
|
||||
* definedTags: string[],
|
||||
* enableFixer: boolean,
|
||||
* jsxTags: boolean,
|
||||
* typed: boolean
|
||||
}} */ {
|
||||
definedTags = [],
|
||||
enableFixer = true,
|
||||
jsxTags,
|
||||
typed,
|
||||
} = context.options[0] || {};
|
||||
|
||||
/** @type {(string|undefined)[]} */
|
||||
let definedPreferredTags = [];
|
||||
const {
|
||||
tagNamePreference,
|
||||
structuredTags,
|
||||
} = settings;
|
||||
const definedStructuredTags = Object.keys(structuredTags);
|
||||
const definedNonPreferredTags = Object.keys(tagNamePreference);
|
||||
if (definedNonPreferredTags.length) {
|
||||
definedPreferredTags = Object.values(tagNamePreference).map((preferredTag) => {
|
||||
if (typeof preferredTag === 'string') {
|
||||
// May become an empty string but will be filtered out below
|
||||
return preferredTag;
|
||||
}
|
||||
|
||||
if (!preferredTag) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (typeof preferredTag !== 'object') {
|
||||
utils.reportSettings(
|
||||
'Invalid `settings.jsdoc.tagNamePreference`. Values must be falsy, a string, or an object.',
|
||||
);
|
||||
}
|
||||
|
||||
return preferredTag.replacement;
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('eslint').Rule.Node} subNode
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const isInAmbientContext = (subNode) => {
|
||||
return subNode.type === 'Program' ?
|
||||
context.getFilename().endsWith('.d.ts') :
|
||||
Boolean(
|
||||
/** @type {import('@typescript-eslint/types').TSESTree.VariableDeclaration} */ (
|
||||
subNode
|
||||
).declare,
|
||||
) || isInAmbientContext(subNode.parent);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('comment-parser').Spec} jsdocTag
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const tagIsRedundantWhenTyped = (jsdocTag) => {
|
||||
if (!typedTagsUnnecessaryOutsideDeclare.has(jsdocTag.tag)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (jsdocTag.tag === 'default') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (node === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (context.getFilename().endsWith('.d.ts') && [
|
||||
'Program', null, undefined,
|
||||
].includes(node?.parent?.type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isInAmbientContext(/** @type {import('eslint').Rule.Node} */ (node))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} message
|
||||
* @param {import('comment-parser').Spec} jsdocTag
|
||||
* @param {import('../iterateJsdoc.js').Integer} tagIndex
|
||||
* @param {Partial<import('comment-parser').Tokens>} [additionalTagChanges]
|
||||
* @returns {void}
|
||||
*/
|
||||
const reportWithTagRemovalFixer = (message, jsdocTag, tagIndex, additionalTagChanges) => {
|
||||
utils.reportJSDoc(message, jsdocTag, enableFixer ? () => {
|
||||
if (jsdocTag.description.trim()) {
|
||||
utils.changeTag(jsdocTag, {
|
||||
postType: '',
|
||||
type: '',
|
||||
...additionalTagChanges,
|
||||
});
|
||||
} else {
|
||||
utils.removeTag(tagIndex, {
|
||||
removeEmptyBlock: true,
|
||||
});
|
||||
}
|
||||
} : null, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('comment-parser').Spec} jsdocTag
|
||||
* @param {import('../iterateJsdoc.js').Integer} tagIndex
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const checkTagForTypedValidity = (jsdocTag, tagIndex) => {
|
||||
if (typedTagsAlwaysUnnecessary.has(jsdocTag.tag)) {
|
||||
reportWithTagRemovalFixer(
|
||||
`'@${jsdocTag.tag}' is redundant when using a type system.`,
|
||||
jsdocTag,
|
||||
tagIndex,
|
||||
{
|
||||
postTag: '',
|
||||
tag: '',
|
||||
},
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tagIsRedundantWhenTyped(jsdocTag)) {
|
||||
reportWithTagRemovalFixer(
|
||||
`'@${jsdocTag.tag}' is redundant outside of ambient (\`declare\`/\`.d.ts\`) contexts when using a type system.`,
|
||||
jsdocTag,
|
||||
tagIndex,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typedTagsNeedingName.has(jsdocTag.tag) && !jsdocTag.name) {
|
||||
reportWithTagRemovalFixer(
|
||||
`'@${jsdocTag.tag}' without a name is redundant when using a type system.`,
|
||||
jsdocTag,
|
||||
tagIndex,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
for (let tagIndex = 0; tagIndex < jsdoc.tags.length; tagIndex += 1) {
|
||||
const jsdocTag = jsdoc.tags[tagIndex];
|
||||
const tagName = jsdocTag.tag;
|
||||
if (jsxTags && jsxTagNames.has(tagName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typed && checkTagForTypedValidity(jsdocTag, tagIndex)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const validTags = [
|
||||
...definedTags,
|
||||
...(/** @type {string[]} */ (definedPreferredTags)),
|
||||
...definedNonPreferredTags,
|
||||
...definedStructuredTags,
|
||||
...typed ? typedTagsNeedingName : [],
|
||||
];
|
||||
|
||||
if (utils.isValidTag(tagName, validTags)) {
|
||||
let preferredTagName = utils.getPreferredTagName({
|
||||
allowObjectReturn: true,
|
||||
defaultMessage: `Blacklisted tag found (\`@${tagName}\`)`,
|
||||
tagName,
|
||||
});
|
||||
if (!preferredTagName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let message;
|
||||
if (typeof preferredTagName === 'object') {
|
||||
({
|
||||
message,
|
||||
replacement: preferredTagName,
|
||||
} = /** @type {{message: string; replacement?: string | undefined;}} */ (
|
||||
preferredTagName
|
||||
));
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
message = `Invalid JSDoc tag (preference). Replace "${tagName}" JSDoc tag with "${preferredTagName}".`;
|
||||
}
|
||||
|
||||
if (preferredTagName !== tagName) {
|
||||
report(message, (fixer) => {
|
||||
const replacement = sourceCode.getText(jsdocNode).replace(
|
||||
new RegExp(`@${escapeStringRegexp(tagName)}\\b`, 'u'),
|
||||
`@${preferredTagName}`,
|
||||
);
|
||||
|
||||
return fixer.replaceText(jsdocNode, replacement);
|
||||
}, jsdocTag);
|
||||
}
|
||||
} else {
|
||||
report(`Invalid JSDoc tag name "${tagName}".`, null, jsdocTag);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Reports invalid block tag names.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/check-tag-names.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
definedTags: {
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
enableFixer: {
|
||||
type: 'boolean',
|
||||
},
|
||||
jsxTags: {
|
||||
type: 'boolean',
|
||||
},
|
||||
typed: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
172
node_modules/eslint-plugin-jsdoc/src/rules/checkTemplateNames.js
generated
vendored
Normal file
172
node_modules/eslint-plugin-jsdoc/src/rules/checkTemplateNames.js
generated
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
import {
|
||||
parse as parseType,
|
||||
traverse,
|
||||
tryParse as tryParseType,
|
||||
} from '@es-joy/jsdoccomment';
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
export default iterateJsdoc(({
|
||||
context,
|
||||
utils,
|
||||
node,
|
||||
settings,
|
||||
report,
|
||||
}) => {
|
||||
const {
|
||||
mode
|
||||
} = settings;
|
||||
|
||||
const templateTags = utils.getTags('template');
|
||||
|
||||
const usedNames = new Set();
|
||||
/**
|
||||
* @param {string} potentialType
|
||||
*/
|
||||
const checkForUsedTypes = (potentialType) => {
|
||||
let parsedType;
|
||||
try {
|
||||
parsedType = mode === 'permissive' ?
|
||||
tryParseType(/** @type {string} */ (potentialType)) :
|
||||
parseType(/** @type {string} */ (potentialType), mode);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
traverse(parsedType, (nde) => {
|
||||
const {
|
||||
type,
|
||||
value,
|
||||
} = /** @type {import('jsdoc-type-pratt-parser').NameResult} */ (nde);
|
||||
if (type === 'JsdocTypeName' && (/^[A-Z]$/).test(value)) {
|
||||
usedNames.add(value);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const checkParamsAndReturnsTags = () => {
|
||||
const paramName = /** @type {string} */ (utils.getPreferredTagName({
|
||||
tagName: 'param',
|
||||
}));
|
||||
const paramTags = utils.getTags(paramName);
|
||||
for (const paramTag of paramTags) {
|
||||
checkForUsedTypes(paramTag.type);
|
||||
}
|
||||
|
||||
const returnsName = /** @type {string} */ (utils.getPreferredTagName({
|
||||
tagName: 'returns',
|
||||
}));
|
||||
const returnsTags = utils.getTags(returnsName);
|
||||
for (const returnsTag of returnsTags) {
|
||||
checkForUsedTypes(returnsTag.type);
|
||||
}
|
||||
};
|
||||
|
||||
const checkTemplateTags = () => {
|
||||
for (const tag of templateTags) {
|
||||
const {name} = tag;
|
||||
const names = name.split(/,\s*/);
|
||||
for (const name of names) {
|
||||
if (!usedNames.has(name)) {
|
||||
report(`@template ${name} not in use`, null, tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('@typescript-eslint/types').TSESTree.FunctionDeclaration|
|
||||
* import('@typescript-eslint/types').TSESTree.ClassDeclaration|
|
||||
* import('@typescript-eslint/types').TSESTree.TSInterfaceDeclaration|
|
||||
* import('@typescript-eslint/types').TSESTree.TSTypeAliasDeclaration} aliasDeclaration
|
||||
* @param {boolean} [checkParamsAndReturns]
|
||||
*/
|
||||
const checkParameters = (aliasDeclaration, checkParamsAndReturns) => {
|
||||
/* c8 ignore next -- Guard */
|
||||
const {params} = aliasDeclaration.typeParameters ?? {params: []};
|
||||
for (const {name: {name}} of params) {
|
||||
usedNames.add(name);
|
||||
}
|
||||
if (checkParamsAndReturns) {
|
||||
checkParamsAndReturnsTags();
|
||||
}
|
||||
|
||||
checkTemplateTags();
|
||||
};
|
||||
|
||||
const handleTypeAliases = () => {
|
||||
const nde = /** @type {import('@typescript-eslint/types').TSESTree.Node} */ (
|
||||
node
|
||||
);
|
||||
if (!nde) {
|
||||
return;
|
||||
}
|
||||
switch (nde.type) {
|
||||
case 'ExportDefaultDeclaration':
|
||||
case 'ExportNamedDeclaration':
|
||||
switch (nde.declaration?.type) {
|
||||
case 'FunctionDeclaration':
|
||||
checkParameters(nde.declaration, true);
|
||||
break;
|
||||
case 'ClassDeclaration':
|
||||
case 'TSTypeAliasDeclaration':
|
||||
case 'TSInterfaceDeclaration':
|
||||
checkParameters(nde.declaration);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'FunctionDeclaration':
|
||||
checkParameters(nde, true);
|
||||
break;
|
||||
case 'ClassDeclaration':
|
||||
case 'TSTypeAliasDeclaration':
|
||||
case 'TSInterfaceDeclaration':
|
||||
checkParameters(nde);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const callbackTags = utils.getTags('callback');
|
||||
const functionTags = utils.getTags('function');
|
||||
if (callbackTags.length || functionTags.length) {
|
||||
checkParamsAndReturnsTags();
|
||||
checkTemplateTags();
|
||||
return;
|
||||
}
|
||||
|
||||
const typedefTags = utils.getTags('typedef');
|
||||
if (!typedefTags.length || typedefTags.length >= 2) {
|
||||
handleTypeAliases();
|
||||
return;
|
||||
}
|
||||
|
||||
const potentialTypedefType = typedefTags[0].type;
|
||||
checkForUsedTypes(potentialTypedefType);
|
||||
|
||||
const propertyName = /** @type {string} */ (utils.getPreferredTagName({
|
||||
tagName: 'property',
|
||||
}));
|
||||
const propertyTags = utils.getTags(propertyName);
|
||||
for (const propertyTag of propertyTags) {
|
||||
checkForUsedTypes(propertyTag.type);
|
||||
}
|
||||
|
||||
for (const tag of templateTags) {
|
||||
const {name} = tag;
|
||||
const names = name.split(/,\s*/);
|
||||
for (const name of names) {
|
||||
if (!usedNames.has(name)) {
|
||||
report(`@template ${name} not in use`, null, tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Checks that any `@template` names are actually used in the connected `@typedef` or type alias.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-template.md#repos-sticky-header',
|
||||
},
|
||||
schema: [],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
535
node_modules/eslint-plugin-jsdoc/src/rules/checkTypes.js
generated
vendored
Normal file
535
node_modules/eslint-plugin-jsdoc/src/rules/checkTypes.js
generated
vendored
Normal file
@@ -0,0 +1,535 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
import {
|
||||
parse,
|
||||
stringify,
|
||||
traverse,
|
||||
tryParse,
|
||||
} from '@es-joy/jsdoccomment';
|
||||
|
||||
const strictNativeTypes = [
|
||||
'undefined',
|
||||
'null',
|
||||
'boolean',
|
||||
'number',
|
||||
'bigint',
|
||||
'string',
|
||||
'symbol',
|
||||
'object',
|
||||
'Array',
|
||||
'Function',
|
||||
'Date',
|
||||
'RegExp',
|
||||
];
|
||||
|
||||
/**
|
||||
* 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(/\.(?:<>)?$/u);
|
||||
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(/(?:\.|<>|\.<>|\[\])$/u, '');
|
||||
|
||||
// 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}`';
|
||||
};
|
||||
|
||||
export default iterateJsdoc(({
|
||||
jsdocNode,
|
||||
sourceCode,
|
||||
report,
|
||||
utils,
|
||||
settings,
|
||||
context,
|
||||
}) => {
|
||||
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
|
||||
* }}
|
||||
*/
|
||||
{
|
||||
preferredTypes: preferredTypesOriginal,
|
||||
structuredTags,
|
||||
mode,
|
||||
} = settings;
|
||||
|
||||
const injectObjectPreferredTypes = !('Object' in preferredTypesOriginal ||
|
||||
'object' in preferredTypesOriginal ||
|
||||
'object.<>' in preferredTypesOriginal ||
|
||||
'Object.<>' in preferredTypesOriginal ||
|
||||
'object<>' in preferredTypesOriginal);
|
||||
|
||||
/**
|
||||
* @type {{
|
||||
* message: string,
|
||||
* replacement: false
|
||||
* }}
|
||||
*/
|
||||
const info = {
|
||||
message: getMessage(),
|
||||
replacement: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {{
|
||||
* message: string,
|
||||
* replacement: false
|
||||
* }}
|
||||
*/
|
||||
const infoUC = {
|
||||
message: getMessage(true),
|
||||
replacement: false,
|
||||
};
|
||||
|
||||
/** @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[]
|
||||
* })[]
|
||||
* }}
|
||||
*/ {
|
||||
noDefaults,
|
||||
unifyParentAndChildTypeChecks,
|
||||
exemptTagContexts = [],
|
||||
} = 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 typeName = typeNodeName;
|
||||
|
||||
const isNameOfGeneric = parentNode !== undefined && parentNode.type === 'JsdocTypeGeneric' && property === 'left';
|
||||
if (unifyParentAndChildTypeChecks || isNameOfGeneric) {
|
||||
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) => {
|
||||
if (preferredTypes?.[typeNodeName + checkPostFix] !== undefined) {
|
||||
typeName += 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) => {
|
||||
if (preferredTypes?.[checkPostFix] !== undefined) {
|
||||
typeName = checkPostFix;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const directNameMatch = preferredTypes?.[typeNodeName] !== undefined &&
|
||||
!Object.values(preferredTypes).includes(typeNodeName);
|
||||
const unifiedSyntaxParentMatch = property && directNameMatch && unifyParentAndChildTypeChecks;
|
||||
isGenericMatch = isGenericMatch || Boolean(unifiedSyntaxParentMatch);
|
||||
|
||||
hasMatchingPreferredType = isGenericMatch ||
|
||||
directNameMatch && !property;
|
||||
|
||||
return [
|
||||
hasMatchingPreferredType, typeName, isGenericMatch,
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Iterates strict types to see if any should be added to `invalidTypes` (and
|
||||
* the the relevant strict type returned as the new preferred type).
|
||||
* @param {string} typeNodeName
|
||||
* @param {string|undefined} preferred
|
||||
* @param {import('jsdoc-type-pratt-parser').NonRootResult|undefined} parentNode
|
||||
* @param {(string|false|undefined)[][]} invalidTypes
|
||||
* @returns {string|undefined} The `preferred` type string, optionally changed
|
||||
*/
|
||||
const checkNativeTypes = (typeNodeName, preferred, parentNode, invalidTypes) => {
|
||||
let changedPreferred = preferred;
|
||||
for (const strictNativeType of strictNativeTypes) {
|
||||
if (
|
||||
strictNativeType === 'object' &&
|
||||
(
|
||||
// This is not set to remap with exact type match (e.g.,
|
||||
// `object: 'Object'`), so can ignore (including if circular)
|
||||
!preferredTypes?.[typeNodeName] ||
|
||||
// Although present on `preferredTypes` for remapping, this is a
|
||||
// parent object without a parent match (and not
|
||||
// `unifyParentAndChildTypeChecks`) and we don't want
|
||||
// `object<>` given TypeScript issue https://github.com/microsoft/TypeScript/issues/20555
|
||||
/**
|
||||
* @type {import('jsdoc-type-pratt-parser').GenericResult}
|
||||
*/
|
||||
(
|
||||
parentNode
|
||||
)?.elements?.length && (
|
||||
/**
|
||||
* @type {import('jsdoc-type-pratt-parser').GenericResult}
|
||||
*/
|
||||
(
|
||||
parentNode
|
||||
)?.left?.type === 'JsdocTypeName' &&
|
||||
/**
|
||||
* @type {import('jsdoc-type-pratt-parser').GenericResult}
|
||||
*/
|
||||
(parentNode)?.left?.value === 'Object'
|
||||
)
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strictNativeType !== typeNodeName &&
|
||||
strictNativeType.toLowerCase() === typeNodeName.toLowerCase() &&
|
||||
|
||||
// Don't report if user has own map for a strict native type
|
||||
(!preferredTypes || preferredTypes?.[strictNativeType] === undefined)
|
||||
) {
|
||||
changedPreferred = strictNativeType;
|
||||
invalidTypes.push([
|
||||
typeNodeName, changedPreferred,
|
||||
]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return changedPreferred;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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,
|
||||
typeName,
|
||||
isGenericMatch,
|
||||
] = getPreferredTypeInfo(type, typeNodeName, parentNode, property);
|
||||
|
||||
let preferred;
|
||||
let types;
|
||||
if (hasMatchingPreferredType) {
|
||||
const preferredSetting = preferredTypes[typeName];
|
||||
typeNodeName = typeName === '[]' ? typeName : 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 (!noDefaults && type === 'JsdocTypeName') {
|
||||
preferred = checkNativeTypes(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' ? tryParse(jsdocTag.type) : parse(jsdocTag.type, mode);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
const {
|
||||
tag: tagName,
|
||||
name: nameInTag,
|
||||
} = jsdocTag;
|
||||
|
||||
traverse(typeAst, (node, parentNode, property) => {
|
||||
const {
|
||||
type,
|
||||
value,
|
||||
} =
|
||||
/**
|
||||
* @type {import('jsdoc-type-pratt-parser').NameResult}
|
||||
*/ (node);
|
||||
if (![
|
||||
'JsdocTypeName', 'JsdocTypeAny',
|
||||
].includes(type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
getInvalidTypes(type, value, tagName, nameInTag, idx, property, node, parentNode, invalidTypes);
|
||||
});
|
||||
|
||||
if (invalidTypes.length) {
|
||||
const fixedType = 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: 'Reports invalid types.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/check-types.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
exemptTagContexts: {
|
||||
items: {
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
tag: {
|
||||
type: 'string',
|
||||
},
|
||||
types: {
|
||||
oneOf: [
|
||||
{
|
||||
type: 'boolean',
|
||||
},
|
||||
{
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
noDefaults: {
|
||||
type: 'boolean',
|
||||
},
|
||||
unifyParentAndChildTypeChecks: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
249
node_modules/eslint-plugin-jsdoc/src/rules/checkValues.js
generated
vendored
Normal file
249
node_modules/eslint-plugin-jsdoc/src/rules/checkValues.js
generated
vendored
Normal file
@@ -0,0 +1,249 @@
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { createSyncFn } from 'synckit';
|
||||
import semver from 'semver';
|
||||
import spdxExpressionParse from 'spdx-expression-parse';
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const pathName = join(__dirname, '../import-worker.mjs');
|
||||
|
||||
const allowedKinds = new Set([
|
||||
'class',
|
||||
'constant',
|
||||
'event',
|
||||
'external',
|
||||
'file',
|
||||
'function',
|
||||
'member',
|
||||
'mixin',
|
||||
'module',
|
||||
'namespace',
|
||||
'typedef',
|
||||
]);
|
||||
|
||||
export default iterateJsdoc(({
|
||||
utils,
|
||||
report,
|
||||
context,
|
||||
settings,
|
||||
}) => {
|
||||
const options = context.options[0] || {};
|
||||
const {
|
||||
allowedLicenses = null,
|
||||
allowedAuthors = null,
|
||||
numericOnlyVariation = false,
|
||||
licensePattern = '/([^\n\r]*)/gu',
|
||||
} = 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 {
|
||||
type, name, description
|
||||
} = tag;
|
||||
const typePart = type ? `{${type}} `: '';
|
||||
const imprt = 'import ' + (description
|
||||
? `${typePart}${name} ${description}`
|
||||
: `${typePart}${name}`);
|
||||
|
||||
const getImports = createSyncFn(pathName);
|
||||
if (!getImports(imprt)) {
|
||||
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: {
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
allowedLicenses: {
|
||||
anyOf: [
|
||||
{
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
{
|
||||
type: 'boolean',
|
||||
},
|
||||
],
|
||||
},
|
||||
licensePattern: {
|
||||
type: 'string',
|
||||
},
|
||||
numericOnlyVariation: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
388
node_modules/eslint-plugin-jsdoc/src/rules/convertToJsdocComments.js
generated
vendored
Normal file
388
node_modules/eslint-plugin-jsdoc/src/rules/convertToJsdocComments.js
generated
vendored
Normal file
@@ -0,0 +1,388 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
import {
|
||||
getSettings,
|
||||
} from '../iterateJsdoc.js';
|
||||
import {
|
||||
getIndent,
|
||||
getContextObject,
|
||||
enforcedContexts,
|
||||
} from '../jsdocUtils.js';
|
||||
import {
|
||||
getNonJsdocComment,
|
||||
getDecorator,
|
||||
getReducedASTNode,
|
||||
getFollowingComment,
|
||||
} from '@es-joy/jsdoccomment';
|
||||
|
||||
/** @type {import('eslint').Rule.RuleModule} */
|
||||
export default {
|
||||
create (context) {
|
||||
/**
|
||||
* @typedef {import('eslint').AST.Token | import('estree').Comment | {
|
||||
* type: import('eslint').AST.TokenType|"Line"|"Block"|"Shebang",
|
||||
* range: [number, number],
|
||||
* value: string
|
||||
* }} Token
|
||||
*/
|
||||
|
||||
/**
|
||||
* @callback AddComment
|
||||
* @param {boolean|undefined} inlineCommentBlock
|
||||
* @param {Token} comment
|
||||
* @param {string} indent
|
||||
* @param {number} lines
|
||||
* @param {import('eslint').Rule.RuleFixer} fixer
|
||||
*/
|
||||
|
||||
/* c8 ignore next -- Fallback to deprecated method */
|
||||
const {
|
||||
sourceCode = context.getSourceCode(),
|
||||
} = context;
|
||||
const settings = getSettings(context);
|
||||
if (!settings) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const {
|
||||
contexts = settings.contexts || [],
|
||||
contextsAfter = /** @type {string[]} */ ([]),
|
||||
contextsBeforeAndAfter = [
|
||||
'VariableDeclarator', 'TSPropertySignature', 'PropertyDefinition'
|
||||
],
|
||||
enableFixer = true,
|
||||
enforceJsdocLineStyle = 'multi',
|
||||
lineOrBlockStyle = 'both',
|
||||
allowedPrefixes = ['@ts-', 'istanbul ', 'c8 ', 'v8 ', 'eslint', 'prettier-']
|
||||
} = context.options[0] ?? {};
|
||||
|
||||
let reportingNonJsdoc = false;
|
||||
|
||||
/**
|
||||
* @param {string} messageId
|
||||
* @param {import('estree').Comment|Token} comment
|
||||
* @param {import('eslint').Rule.Node} node
|
||||
* @param {import('eslint').Rule.ReportFixer} fixer
|
||||
*/
|
||||
const report = (messageId, comment, node, fixer) => {
|
||||
const loc = {
|
||||
end: {
|
||||
column: 0,
|
||||
/* c8 ignore next 2 -- Guard */
|
||||
// @ts-expect-error Ok
|
||||
line: (comment.loc?.start?.line ?? 1),
|
||||
},
|
||||
start: {
|
||||
column: 0,
|
||||
/* c8 ignore next 2 -- Guard */
|
||||
// @ts-expect-error Ok
|
||||
line: (comment.loc?.start?.line ?? 1)
|
||||
},
|
||||
};
|
||||
|
||||
context.report({
|
||||
fix: enableFixer ? fixer : null,
|
||||
loc,
|
||||
messageId,
|
||||
node,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('eslint').Rule.Node} node
|
||||
* @param {import('eslint').AST.Token | import('estree').Comment | {
|
||||
* type: import('eslint').AST.TokenType|"Line"|"Block"|"Shebang",
|
||||
* range: [number, number],
|
||||
* value: string
|
||||
* }} comment
|
||||
* @param {AddComment} addComment
|
||||
* @param {import('../iterateJsdoc.js').Context[]} ctxts
|
||||
*/
|
||||
const getFixer = (node, comment, addComment, ctxts) => {
|
||||
return /** @type {import('eslint').Rule.ReportFixer} */ (fixer) => {
|
||||
// Default to one line break if the `minLines`/`maxLines` settings allow
|
||||
const lines = settings.minLines === 0 && settings.maxLines >= 1 ? 1 : settings.minLines;
|
||||
let baseNode =
|
||||
/**
|
||||
* @type {import('@typescript-eslint/types').TSESTree.Node|import('eslint').Rule.Node}
|
||||
*/ (
|
||||
getReducedASTNode(node, sourceCode)
|
||||
);
|
||||
|
||||
const decorator = getDecorator(
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(baseNode)
|
||||
);
|
||||
if (decorator) {
|
||||
baseNode = /** @type {import('@typescript-eslint/types').TSESTree.Decorator} */ (
|
||||
decorator
|
||||
);
|
||||
}
|
||||
|
||||
const indent = getIndent({
|
||||
text: sourceCode.getText(
|
||||
/** @type {import('eslint').Rule.Node} */ (baseNode),
|
||||
/** @type {import('eslint').AST.SourceLocation} */
|
||||
(
|
||||
/** @type {import('eslint').Rule.Node} */ (baseNode).loc
|
||||
).start.column,
|
||||
),
|
||||
});
|
||||
|
||||
const {
|
||||
inlineCommentBlock,
|
||||
} =
|
||||
/**
|
||||
* @type {{
|
||||
* context: string,
|
||||
* inlineCommentBlock: boolean,
|
||||
* minLineCount: import('../iterateJsdoc.js').Integer
|
||||
* }[]}
|
||||
*/ (ctxts).find((contxt) => {
|
||||
if (typeof contxt === 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const {
|
||||
context: ctxt,
|
||||
} = contxt;
|
||||
return ctxt === node.type;
|
||||
}) || {};
|
||||
|
||||
return addComment(inlineCommentBlock, comment, indent, lines, fixer);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('eslint').AST.Token | import('estree').Comment | {
|
||||
* type: import('eslint').AST.TokenType|"Line"|"Block"|"Shebang",
|
||||
* range: [number, number],
|
||||
* value: string
|
||||
* }} comment
|
||||
* @param {import('eslint').Rule.Node} node
|
||||
* @param {AddComment} addComment
|
||||
* @param {import('../iterateJsdoc.js').Context[]} ctxts
|
||||
*/
|
||||
const reportings = (comment, node, addComment, ctxts) => {
|
||||
const fixer = getFixer(node, comment, addComment, ctxts);
|
||||
|
||||
if (comment.type === 'Block') {
|
||||
if (lineOrBlockStyle === 'line') {
|
||||
return;
|
||||
}
|
||||
report('blockCommentsJsdocStyle', comment, node, fixer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (comment.type === 'Line') {
|
||||
if (lineOrBlockStyle === 'block') {
|
||||
return;
|
||||
}
|
||||
report('lineCommentsJsdocStyle', comment, node, fixer);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {import('../iterateJsdoc.js').CheckJsdoc}
|
||||
*/
|
||||
const checkNonJsdoc = (_info, _handler, node) => {
|
||||
const comment = getNonJsdocComment(sourceCode, node, settings);
|
||||
|
||||
if (
|
||||
!comment ||
|
||||
/** @type {string[]} */
|
||||
(allowedPrefixes).some((prefix) => {
|
||||
return comment.value.trimStart().startsWith(prefix);
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
reportingNonJsdoc = true;
|
||||
|
||||
/** @type {AddComment} */
|
||||
const addComment = (inlineCommentBlock, comment, indent, lines, fixer) => {
|
||||
const insertion = (
|
||||
inlineCommentBlock || enforceJsdocLineStyle === 'single'
|
||||
? `/** ${comment.value.trim()} `
|
||||
: `/**\n${indent}*${comment.value.trimEnd()}\n${indent}`
|
||||
) +
|
||||
`*/${'\n'.repeat((lines || 1) - 1)}`;
|
||||
|
||||
return fixer.replaceText(
|
||||
/** @type {import('eslint').AST.Token} */
|
||||
(comment),
|
||||
insertion,
|
||||
);
|
||||
};
|
||||
|
||||
reportings(comment, node, addComment, contexts);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('eslint').Rule.Node} node
|
||||
* @param {import('../iterateJsdoc.js').Context[]} ctxts
|
||||
*/
|
||||
const checkNonJsdocAfter = (node, ctxts) => {
|
||||
const comment = getFollowingComment(sourceCode, node);
|
||||
|
||||
if (
|
||||
!comment ||
|
||||
comment.value.startsWith('*') ||
|
||||
/** @type {string[]} */
|
||||
(allowedPrefixes).some((prefix) => {
|
||||
return comment.value.trimStart().startsWith(prefix);
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @type {AddComment} */
|
||||
const addComment = (inlineCommentBlock, comment, indent, lines, fixer) => {
|
||||
const insertion = (
|
||||
inlineCommentBlock || enforceJsdocLineStyle === 'single'
|
||||
? `/** ${comment.value.trim()} `
|
||||
: `/**\n${indent}*${comment.value.trimEnd()}\n${indent}`
|
||||
) +
|
||||
`*/${'\n'.repeat((lines || 1) - 1)}${lines ? `\n${indent.slice(1)}` : ' '}`;
|
||||
|
||||
return [fixer.remove(
|
||||
/** @type {import('eslint').AST.Token} */
|
||||
(comment)
|
||||
), fixer.insertTextBefore(
|
||||
node.type === 'VariableDeclarator' ? node.parent : node,
|
||||
insertion,
|
||||
)];
|
||||
};
|
||||
|
||||
reportings(comment, node, addComment, ctxts);
|
||||
};
|
||||
|
||||
// Todo: add contexts to check after (and handle if want both before and after)
|
||||
return {
|
||||
...getContextObject(
|
||||
enforcedContexts(context, true, settings),
|
||||
checkNonJsdoc,
|
||||
),
|
||||
...getContextObject(
|
||||
contextsAfter,
|
||||
(_info, _handler, node) => {
|
||||
checkNonJsdocAfter(node, contextsAfter);
|
||||
},
|
||||
),
|
||||
...getContextObject(
|
||||
contextsBeforeAndAfter,
|
||||
(_info, _handler, node) => {
|
||||
checkNonJsdoc({}, null, node);
|
||||
if (!reportingNonJsdoc) {
|
||||
checkNonJsdocAfter(node, contextsBeforeAndAfter);
|
||||
}
|
||||
}
|
||||
)
|
||||
};
|
||||
},
|
||||
meta: {
|
||||
fixable: 'code',
|
||||
|
||||
messages: {
|
||||
blockCommentsJsdocStyle: 'Block comments should be JSDoc-style.',
|
||||
lineCommentsJsdocStyle: 'Line comments should be JSDoc-style.',
|
||||
},
|
||||
|
||||
docs: {
|
||||
description: 'Converts non-JSDoc comments preceding or following nodes into JSDoc ones',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/convert-to-jsdoc-comments.md#repos-sticky-header',
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
allowedPrefixes: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string'
|
||||
}
|
||||
},
|
||||
contexts: {
|
||||
items: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
context: {
|
||||
type: 'string',
|
||||
},
|
||||
inlineCommentBlock: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
contextsAfter: {
|
||||
items: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
context: {
|
||||
type: 'string',
|
||||
},
|
||||
inlineCommentBlock: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
contextsBeforeAndAfter: {
|
||||
items: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
context: {
|
||||
type: 'string',
|
||||
},
|
||||
inlineCommentBlock: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
enableFixer: {
|
||||
type: 'boolean'
|
||||
},
|
||||
enforceJsdocLineStyle: {
|
||||
type: 'string',
|
||||
enum: ['multi', 'single']
|
||||
},
|
||||
lineOrBlockStyle: {
|
||||
type: 'string',
|
||||
enum: ['block', 'line', 'both']
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
};
|
||||
88
node_modules/eslint-plugin-jsdoc/src/rules/emptyTags.js
generated
vendored
Normal file
88
node_modules/eslint-plugin-jsdoc/src/rules/emptyTags.js
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
const defaultEmptyTags = new Set([
|
||||
'abstract', 'async', 'generator', 'global', 'hideconstructor',
|
||||
'ignore', 'inner', 'instance', 'override', 'readonly',
|
||||
|
||||
// jsdoc doesn't use this form in its docs, but allow for compatibility with
|
||||
// TypeScript which allows and Closure which requires
|
||||
'inheritDoc',
|
||||
|
||||
// jsdoc doesn't use but allow for TypeScript
|
||||
'internal',
|
||||
'overload',
|
||||
]);
|
||||
|
||||
const emptyIfNotClosure = new Set([
|
||||
'package', 'private', 'protected', 'public', 'static',
|
||||
|
||||
// Closure doesn't allow with this casing
|
||||
'inheritdoc',
|
||||
]);
|
||||
|
||||
const emptyIfClosure = new Set([
|
||||
'interface',
|
||||
]);
|
||||
|
||||
export default iterateJsdoc(({
|
||||
settings,
|
||||
jsdoc,
|
||||
utils,
|
||||
}) => {
|
||||
const emptyTags = utils.filterTags(({
|
||||
tag: tagName,
|
||||
}) => {
|
||||
return defaultEmptyTags.has(tagName) ||
|
||||
utils.hasOptionTag(tagName) && jsdoc.tags.some(({
|
||||
tag,
|
||||
}) => {
|
||||
return tag === tagName;
|
||||
}) ||
|
||||
settings.mode === 'closure' && emptyIfClosure.has(tagName) ||
|
||||
settings.mode !== 'closure' && emptyIfNotClosure.has(tagName);
|
||||
});
|
||||
|
||||
for (const tag of emptyTags) {
|
||||
const content = tag.name || tag.description || tag.type;
|
||||
if (content.trim()) {
|
||||
const fix = () => {
|
||||
// By time of call in fixer, `tag` will have `line` added
|
||||
utils.setTag(
|
||||
/**
|
||||
* @type {import('comment-parser').Spec & {
|
||||
* line: import('../iterateJsdoc.js').Integer
|
||||
* }}
|
||||
*/ (tag),
|
||||
);
|
||||
};
|
||||
|
||||
utils.reportJSDoc(`@${tag.tag} should be empty.`, tag, fix, true);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
checkInternal: true,
|
||||
checkPrivate: true,
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Expects specific tags to be empty of any content.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/empty-tags.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
tags: {
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
64
node_modules/eslint-plugin-jsdoc/src/rules/implementsOnClasses.js
generated
vendored
Normal file
64
node_modules/eslint-plugin-jsdoc/src/rules/implementsOnClasses.js
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
export default iterateJsdoc(({
|
||||
report,
|
||||
utils,
|
||||
}) => {
|
||||
const iteratingFunction = utils.isIteratingFunction();
|
||||
|
||||
if (iteratingFunction) {
|
||||
if (utils.hasATag([
|
||||
'class',
|
||||
'constructor',
|
||||
]) ||
|
||||
utils.isConstructor()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
} else if (!utils.isVirtualFunction()) {
|
||||
return;
|
||||
}
|
||||
|
||||
utils.forEachPreferredTag('implements', (tag) => {
|
||||
report('@implements used on a non-constructor function', null, tag);
|
||||
});
|
||||
}, {
|
||||
contextDefaults: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Reports an issue with any non-constructor function using `@implements`.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/implements-on-classes.md#repos-sticky-header',
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
contexts: {
|
||||
items: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
comment: {
|
||||
type: 'string',
|
||||
},
|
||||
context: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
131
node_modules/eslint-plugin-jsdoc/src/rules/importsAsDependencies.js
generated
vendored
Normal file
131
node_modules/eslint-plugin-jsdoc/src/rules/importsAsDependencies.js
generated
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
import {
|
||||
parse,
|
||||
traverse,
|
||||
tryParse,
|
||||
} from '@es-joy/jsdoccomment';
|
||||
import {
|
||||
readFileSync,
|
||||
} from 'fs';
|
||||
import {isBuiltin as isBuiltinModule} from 'node:module';
|
||||
import {
|
||||
join,
|
||||
} from 'path';
|
||||
|
||||
/**
|
||||
* @type {Set<string>|null}
|
||||
*/
|
||||
let deps;
|
||||
|
||||
const setDeps = function () {
|
||||
try {
|
||||
const pkg = JSON.parse(
|
||||
// @ts-expect-error It's ok
|
||||
readFileSync(join(process.cwd(), './package.json')),
|
||||
);
|
||||
deps = new Set([
|
||||
...(pkg.dependencies ?
|
||||
/* c8 ignore next 2 */
|
||||
Object.keys(pkg.dependencies) :
|
||||
[]),
|
||||
...(pkg.devDependencies ?
|
||||
/* c8 ignore next 2 */
|
||||
Object.keys(pkg.devDependencies) :
|
||||
[]),
|
||||
]);
|
||||
/* c8 ignore next -- our package.json exists */
|
||||
} catch (error) {
|
||||
/* c8 ignore next -- our package.json exists */
|
||||
deps = null;
|
||||
/* c8 ignore next 4 -- our package.json exists */
|
||||
/* eslint-disable no-console -- Inform user */
|
||||
console.log(error);
|
||||
/* eslint-enable no-console -- Inform user */
|
||||
}
|
||||
};
|
||||
|
||||
const moduleCheck = new Map();
|
||||
|
||||
export default iterateJsdoc(({
|
||||
jsdoc,
|
||||
settings,
|
||||
utils,
|
||||
}) => {
|
||||
if (deps === undefined) {
|
||||
setDeps();
|
||||
}
|
||||
|
||||
/* c8 ignore next 3 -- our package.json exists */
|
||||
if (deps === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
mode,
|
||||
} = settings;
|
||||
|
||||
for (const tag of jsdoc.tags) {
|
||||
let typeAst;
|
||||
try {
|
||||
typeAst = mode === 'permissive' ? tryParse(tag.type) : parse(tag.type, mode);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-loop-func -- Safe
|
||||
traverse(typeAst, (nde) => {
|
||||
/* c8 ignore next 3 -- TS guard */
|
||||
if (deps === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (nde.type === 'JsdocTypeImport') {
|
||||
let mod = nde.element.value.replace(
|
||||
/^(@[^/]+\/[^/]+|[^/]+).*$/u, '$1',
|
||||
);
|
||||
|
||||
if ((/^[./]/u).test(mod)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isBuiltinModule(mod)) {
|
||||
// mod = '@types/node';
|
||||
// moduleCheck.set(mod, !deps.has(mod));
|
||||
return;
|
||||
} else if (!moduleCheck.has(mod)) {
|
||||
let pkg;
|
||||
try {
|
||||
pkg = JSON.parse(
|
||||
// @ts-expect-error It's ok
|
||||
readFileSync(join(process.cwd(), 'node_modules', mod, './package.json')),
|
||||
);
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
if (!pkg || (!pkg.types && !pkg.typings)) {
|
||||
mod = `@types/${mod}`;
|
||||
}
|
||||
|
||||
moduleCheck.set(mod, !deps.has(mod));
|
||||
}
|
||||
|
||||
if (moduleCheck.get(mod)) {
|
||||
utils.reportJSDoc(
|
||||
'import points to package which is not found in dependencies',
|
||||
tag,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Reports if JSDoc `import()` statements point to a package which is not listed in `dependencies` or `devDependencies`',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/imports-as-dependencies.md#repos-sticky-header',
|
||||
},
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
189
node_modules/eslint-plugin-jsdoc/src/rules/informativeDocs.js
generated
vendored
Normal file
189
node_modules/eslint-plugin-jsdoc/src/rules/informativeDocs.js
generated
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
import {
|
||||
areDocsInformative,
|
||||
} from 'are-docs-informative';
|
||||
|
||||
const defaultAliases = {
|
||||
a: [
|
||||
'an', 'our',
|
||||
],
|
||||
};
|
||||
|
||||
const defaultUselessWords = [
|
||||
'a', 'an', 'i', 'in', 'of', 's', 'the',
|
||||
];
|
||||
|
||||
/* eslint-disable complexity -- Temporary */
|
||||
|
||||
/**
|
||||
* @param {import('eslint').Rule.Node|import('@typescript-eslint/types').TSESTree.Node|null|undefined} node
|
||||
* @returns {string[]}
|
||||
*/
|
||||
const getNamesFromNode = (node) => {
|
||||
switch (node?.type) {
|
||||
case 'AccessorProperty':
|
||||
case 'MethodDefinition':
|
||||
case 'PropertyDefinition':
|
||||
case 'TSAbstractAccessorProperty':
|
||||
case 'TSAbstractMethodDefinition':
|
||||
case 'TSAbstractPropertyDefinition':
|
||||
return [
|
||||
...getNamesFromNode(
|
||||
/** @type {import('@typescript-eslint/types').TSESTree.Node} */ (
|
||||
node.parent
|
||||
).parent,
|
||||
),
|
||||
...getNamesFromNode(
|
||||
/** @type {import('@typescript-eslint/types').TSESTree.Node} */
|
||||
(node.key),
|
||||
),
|
||||
];
|
||||
|
||||
case 'ExportDefaultDeclaration':
|
||||
case 'ExportNamedDeclaration':
|
||||
return getNamesFromNode(
|
||||
/** @type {import('@typescript-eslint/types').TSESTree.ExportNamedDeclaration} */
|
||||
(node).declaration
|
||||
);
|
||||
case 'ClassDeclaration':
|
||||
case 'ClassExpression':
|
||||
case 'FunctionDeclaration':
|
||||
case 'FunctionExpression':
|
||||
case 'TSModuleDeclaration':
|
||||
case 'TSMethodSignature':
|
||||
case 'TSDeclareFunction':
|
||||
case 'TSEnumDeclaration':
|
||||
case 'TSEnumMember':
|
||||
case 'TSInterfaceDeclaration':
|
||||
case 'TSTypeAliasDeclaration':
|
||||
return getNamesFromNode(
|
||||
/** @type {import('@typescript-eslint/types').TSESTree.ClassDeclaration} */
|
||||
(node).id,
|
||||
);
|
||||
case 'Identifier':
|
||||
return [
|
||||
node.name,
|
||||
];
|
||||
case 'Property':
|
||||
return getNamesFromNode(
|
||||
/** @type {import('@typescript-eslint/types').TSESTree.Node} */
|
||||
(node.key),
|
||||
);
|
||||
case 'VariableDeclaration':
|
||||
return getNamesFromNode(
|
||||
/** @type {import('@typescript-eslint/types').TSESTree.Node} */
|
||||
(node.declarations[0]),
|
||||
);
|
||||
case 'VariableDeclarator':
|
||||
return [
|
||||
...getNamesFromNode(
|
||||
/** @type {import('@typescript-eslint/types').TSESTree.Node} */
|
||||
(node.id),
|
||||
),
|
||||
...getNamesFromNode(
|
||||
/** @type {import('@typescript-eslint/types').TSESTree.Node} */
|
||||
(node.init),
|
||||
),
|
||||
].filter(Boolean);
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
};
|
||||
/* eslint-enable complexity -- Temporary */
|
||||
|
||||
export default iterateJsdoc(({
|
||||
context,
|
||||
jsdoc,
|
||||
node,
|
||||
report,
|
||||
utils,
|
||||
}) => {
|
||||
const /** @type {{aliases: {[key: string]: string[]}, excludedTags: string[], uselessWords: string[]}} */ {
|
||||
aliases = defaultAliases,
|
||||
excludedTags = [],
|
||||
uselessWords = defaultUselessWords,
|
||||
} = context.options[0] || {};
|
||||
const nodeNames = getNamesFromNode(node);
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
* @param {string} extraName
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const descriptionIsRedundant = (text, extraName = '') => {
|
||||
const textTrimmed = text.trim();
|
||||
return Boolean(textTrimmed) && !areDocsInformative(textTrimmed, [
|
||||
extraName, nodeNames,
|
||||
].filter(Boolean).join(' '), {
|
||||
aliases,
|
||||
uselessWords,
|
||||
});
|
||||
};
|
||||
|
||||
const {
|
||||
description,
|
||||
lastDescriptionLine,
|
||||
} = utils.getDescription();
|
||||
let descriptionReported = false;
|
||||
|
||||
for (const tag of jsdoc.tags) {
|
||||
if (excludedTags.includes(tag.tag)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (descriptionIsRedundant(tag.description, tag.name)) {
|
||||
utils.reportJSDoc(
|
||||
'This tag description only repeats the name it describes.',
|
||||
tag,
|
||||
);
|
||||
}
|
||||
|
||||
descriptionReported ||= tag.description === description &&
|
||||
/** @type {import('comment-parser').Spec & {line: import('../iterateJsdoc.js').Integer}} */
|
||||
(tag).line === lastDescriptionLine;
|
||||
}
|
||||
|
||||
if (!descriptionReported && descriptionIsRedundant(description)) {
|
||||
report('This description only repeats the name it describes.');
|
||||
}
|
||||
}, {
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description:
|
||||
'This rule reports doc comments that only restate their attached name.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/informative-docs.md#repos-sticky-header',
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
aliases: {
|
||||
patternProperties: {
|
||||
'.*': {
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
},
|
||||
excludedTags: {
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
uselessWords: {
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
286
node_modules/eslint-plugin-jsdoc/src/rules/matchDescription.js
generated
vendored
Normal file
286
node_modules/eslint-plugin-jsdoc/src/rules/matchDescription.js
generated
vendored
Normal file
@@ -0,0 +1,286 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
// If supporting Node >= 10, we could loosen the default to this for the
|
||||
// initial letter: \\p{Upper}
|
||||
const matchDescriptionDefault = '^\n?([A-Z`\\d_][\\s\\S]*[.?!`]\\s*)?$';
|
||||
|
||||
/**
|
||||
* @param {string} value
|
||||
* @param {string} userDefault
|
||||
* @returns {string}
|
||||
*/
|
||||
const stringOrDefault = (value, userDefault) => {
|
||||
return typeof value === 'string' ?
|
||||
value :
|
||||
userDefault || matchDescriptionDefault;
|
||||
};
|
||||
|
||||
export default iterateJsdoc(({
|
||||
jsdoc,
|
||||
report,
|
||||
context,
|
||||
utils,
|
||||
}) => {
|
||||
const {
|
||||
mainDescription,
|
||||
matchDescription,
|
||||
message,
|
||||
nonemptyTags = true,
|
||||
tags = {},
|
||||
} = context.options[0] || {};
|
||||
|
||||
/**
|
||||
* @param {string} desc
|
||||
* @param {import('comment-parser').Spec} [tag]
|
||||
* @returns {void}
|
||||
*/
|
||||
const validateDescription = (desc, tag) => {
|
||||
let mainDescriptionMatch = mainDescription;
|
||||
let errorMessage = message;
|
||||
if (typeof mainDescription === 'object') {
|
||||
mainDescriptionMatch = mainDescription.match;
|
||||
errorMessage = mainDescription.message;
|
||||
}
|
||||
|
||||
if (mainDescriptionMatch === false && (
|
||||
!tag || !Object.prototype.hasOwnProperty.call(tags, tag.tag))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tagValue = mainDescriptionMatch;
|
||||
if (tag) {
|
||||
const tagName = tag.tag;
|
||||
if (typeof tags[tagName] === 'object') {
|
||||
tagValue = tags[tagName].match;
|
||||
errorMessage = tags[tagName].message;
|
||||
} else {
|
||||
tagValue = tags[tagName];
|
||||
}
|
||||
}
|
||||
|
||||
const regex = utils.getRegexFromString(
|
||||
stringOrDefault(tagValue, matchDescription),
|
||||
);
|
||||
|
||||
if (!regex.test(desc)) {
|
||||
report(
|
||||
errorMessage || 'JSDoc description does not satisfy the regex pattern.',
|
||||
null,
|
||||
tag || {
|
||||
// Add one as description would typically be into block
|
||||
line: jsdoc.source[0].number + 1,
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const {
|
||||
description,
|
||||
} = utils.getDescription();
|
||||
if (description) {
|
||||
validateDescription(description);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} tagName
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const hasNoTag = (tagName) => {
|
||||
return !tags[tagName];
|
||||
};
|
||||
|
||||
for (const tag of [
|
||||
'description',
|
||||
'summary',
|
||||
'file',
|
||||
'classdesc',
|
||||
]) {
|
||||
utils.forEachPreferredTag(tag, (matchingJsdocTag, targetTagName) => {
|
||||
const desc = (matchingJsdocTag.name + ' ' + utils.getTagDescription(matchingJsdocTag)).trim();
|
||||
if (hasNoTag(targetTagName)) {
|
||||
validateDescription(desc, matchingJsdocTag);
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
if (nonemptyTags) {
|
||||
for (const tag of [
|
||||
'copyright',
|
||||
'example',
|
||||
'see',
|
||||
'todo',
|
||||
]) {
|
||||
utils.forEachPreferredTag(tag, (matchingJsdocTag, targetTagName) => {
|
||||
const desc = (matchingJsdocTag.name + ' ' + utils.getTagDescription(matchingJsdocTag)).trim();
|
||||
|
||||
if (hasNoTag(targetTagName) && !(/.+/u).test(desc)) {
|
||||
report(
|
||||
'JSDoc description must not be empty.',
|
||||
null,
|
||||
matchingJsdocTag,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!Object.keys(tags).length) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} tagName
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const hasOptionTag = (tagName) => {
|
||||
return Boolean(tags[tagName]);
|
||||
};
|
||||
|
||||
const whitelistedTags = utils.filterTags(({
|
||||
tag: tagName,
|
||||
}) => {
|
||||
return hasOptionTag(tagName);
|
||||
});
|
||||
const {
|
||||
tagsWithNames,
|
||||
tagsWithoutNames,
|
||||
} = utils.getTagsByType(whitelistedTags);
|
||||
|
||||
tagsWithNames.some((tag) => {
|
||||
const desc = /** @type {string} */ (
|
||||
utils.getTagDescription(tag)
|
||||
).replace(/^[- ]*/u, '')
|
||||
.trim();
|
||||
|
||||
return validateDescription(desc, tag);
|
||||
});
|
||||
|
||||
tagsWithoutNames.some((tag) => {
|
||||
const desc = (tag.name + ' ' + utils.getTagDescription(tag)).trim();
|
||||
|
||||
return validateDescription(desc, tag);
|
||||
});
|
||||
}, {
|
||||
contextDefaults: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Enforces a regular expression pattern on descriptions.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/match-description.md#repos-sticky-header',
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
contexts: {
|
||||
items: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
comment: {
|
||||
type: 'string',
|
||||
},
|
||||
context: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
mainDescription: {
|
||||
oneOf: [
|
||||
{
|
||||
format: 'regex',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
type: 'boolean',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
match: {
|
||||
oneOf: [
|
||||
{
|
||||
format: 'regex',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
type: 'boolean',
|
||||
},
|
||||
],
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
},
|
||||
matchDescription: {
|
||||
format: 'regex',
|
||||
type: 'string',
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
},
|
||||
nonemptyTags: {
|
||||
type: 'boolean',
|
||||
},
|
||||
tags: {
|
||||
patternProperties: {
|
||||
'.*': {
|
||||
oneOf: [
|
||||
{
|
||||
format: 'regex',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
enum: [
|
||||
true,
|
||||
],
|
||||
type: 'boolean',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
match: {
|
||||
oneOf: [
|
||||
{
|
||||
format: 'regex',
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
enum: [
|
||||
true,
|
||||
],
|
||||
type: 'boolean',
|
||||
},
|
||||
],
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
147
node_modules/eslint-plugin-jsdoc/src/rules/matchName.js
generated
vendored
Normal file
147
node_modules/eslint-plugin-jsdoc/src/rules/matchName.js
generated
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
export default iterateJsdoc(({
|
||||
context,
|
||||
jsdoc,
|
||||
report,
|
||||
info: {
|
||||
lastIndex,
|
||||
},
|
||||
utils,
|
||||
}) => {
|
||||
const {
|
||||
match,
|
||||
} = context.options[0] || {};
|
||||
if (!match) {
|
||||
report('Rule `no-restricted-syntax` is missing a `match` option.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
allowName,
|
||||
disallowName,
|
||||
replacement,
|
||||
tags = [
|
||||
'*',
|
||||
],
|
||||
} = match[/** @type {import('../iterateJsdoc.js').Integer} */ (lastIndex)];
|
||||
|
||||
const allowNameRegex = allowName && utils.getRegexFromString(allowName);
|
||||
const disallowNameRegex = disallowName && utils.getRegexFromString(disallowName);
|
||||
|
||||
let applicableTags = jsdoc.tags;
|
||||
if (!tags.includes('*')) {
|
||||
applicableTags = utils.getPresentTags(tags);
|
||||
}
|
||||
|
||||
let reported = false;
|
||||
for (const tag of applicableTags) {
|
||||
const allowed = !allowNameRegex || allowNameRegex.test(tag.name);
|
||||
const disallowed = disallowNameRegex && disallowNameRegex.test(tag.name);
|
||||
const hasRegex = allowNameRegex || disallowNameRegex;
|
||||
if (hasRegex && allowed && !disallowed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!hasRegex && reported) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const fixer = () => {
|
||||
for (const src of tag.source) {
|
||||
if (src.tokens.name) {
|
||||
src.tokens.name = src.tokens.name.replace(
|
||||
disallowNameRegex, replacement,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let {
|
||||
message,
|
||||
} = match[/** @type {import('../iterateJsdoc.js').Integer} */ (lastIndex)];
|
||||
if (!message) {
|
||||
if (hasRegex) {
|
||||
message = disallowed ?
|
||||
`Only allowing names not matching \`${disallowNameRegex}\` but found "${tag.name}".` :
|
||||
`Only allowing names matching \`${allowNameRegex}\` but found "${tag.name}".`;
|
||||
} else {
|
||||
message = `Prohibited context for "${tag.name}".`;
|
||||
}
|
||||
}
|
||||
|
||||
utils.reportJSDoc(
|
||||
message,
|
||||
hasRegex ? tag : null,
|
||||
|
||||
// We could match up
|
||||
disallowNameRegex && replacement !== undefined ?
|
||||
fixer :
|
||||
null,
|
||||
false,
|
||||
{
|
||||
// Could also supply `context`, `comment`, `tags`
|
||||
allowName,
|
||||
disallowName,
|
||||
name: tag.name,
|
||||
},
|
||||
);
|
||||
if (!hasRegex) {
|
||||
reported = true;
|
||||
}
|
||||
}
|
||||
}, {
|
||||
matchContext: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Reports the name portion of a JSDoc tag if matching or not matching a given regular expression.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/match-name.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
match: {
|
||||
additionalProperties: false,
|
||||
items: {
|
||||
properties: {
|
||||
allowName: {
|
||||
type: 'string',
|
||||
},
|
||||
comment: {
|
||||
type: 'string',
|
||||
},
|
||||
context: {
|
||||
type: 'string',
|
||||
},
|
||||
disallowName: {
|
||||
type: 'string',
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
},
|
||||
tags: {
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
required: [
|
||||
'match',
|
||||
],
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
333
node_modules/eslint-plugin-jsdoc/src/rules/multilineBlocks.js
generated
vendored
Normal file
333
node_modules/eslint-plugin-jsdoc/src/rules/multilineBlocks.js
generated
vendored
Normal file
@@ -0,0 +1,333 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
export default iterateJsdoc(({
|
||||
context,
|
||||
jsdoc,
|
||||
utils,
|
||||
}) => {
|
||||
const {
|
||||
allowMultipleTags = true,
|
||||
noFinalLineText = true,
|
||||
noZeroLineText = true,
|
||||
noSingleLineBlocks = false,
|
||||
singleLineTags = [
|
||||
'lends', 'type',
|
||||
],
|
||||
noMultilineBlocks = false,
|
||||
minimumLengthForMultiline = Number.POSITIVE_INFINITY,
|
||||
multilineTags = [
|
||||
'*',
|
||||
],
|
||||
} = context.options[0] || {};
|
||||
|
||||
const {
|
||||
source: [
|
||||
{
|
||||
tokens,
|
||||
},
|
||||
],
|
||||
} = jsdoc;
|
||||
const {
|
||||
description,
|
||||
tag,
|
||||
} = tokens;
|
||||
const sourceLength = jsdoc.source.length;
|
||||
|
||||
/**
|
||||
* @param {string} tagName
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const isInvalidSingleLine = (tagName) => {
|
||||
return noSingleLineBlocks &&
|
||||
(!tagName ||
|
||||
!singleLineTags.includes(tagName) && !singleLineTags.includes('*'));
|
||||
};
|
||||
|
||||
if (sourceLength === 1) {
|
||||
if (!isInvalidSingleLine(tag.slice(1))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fixer = () => {
|
||||
utils.makeMultiline();
|
||||
};
|
||||
|
||||
utils.reportJSDoc(
|
||||
'Single line blocks are not permitted by your configuration.',
|
||||
null,
|
||||
fixer,
|
||||
true,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const lineChecks = () => {
|
||||
if (
|
||||
noZeroLineText &&
|
||||
(tag || description)
|
||||
) {
|
||||
const fixer = () => {
|
||||
const line = {
|
||||
...tokens,
|
||||
};
|
||||
utils.emptyTokens(tokens);
|
||||
const {
|
||||
tokens: {
|
||||
delimiter,
|
||||
start,
|
||||
},
|
||||
} = jsdoc.source[1];
|
||||
utils.addLine(1, {
|
||||
...line,
|
||||
delimiter,
|
||||
start,
|
||||
});
|
||||
};
|
||||
|
||||
utils.reportJSDoc(
|
||||
'Should have no text on the "0th" line (after the `/**`).',
|
||||
null,
|
||||
fixer,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const finalLine = jsdoc.source[jsdoc.source.length - 1];
|
||||
const finalLineTokens = finalLine.tokens;
|
||||
if (
|
||||
noFinalLineText &&
|
||||
finalLineTokens.description.trim()
|
||||
) {
|
||||
const fixer = () => {
|
||||
const line = {
|
||||
...finalLineTokens,
|
||||
};
|
||||
line.description = line.description.trimEnd();
|
||||
|
||||
const {
|
||||
delimiter,
|
||||
} = line;
|
||||
|
||||
for (const prop of [
|
||||
'delimiter',
|
||||
'postDelimiter',
|
||||
'tag',
|
||||
'type',
|
||||
'lineEnd',
|
||||
'postType',
|
||||
'postTag',
|
||||
'name',
|
||||
'postName',
|
||||
'description',
|
||||
]) {
|
||||
finalLineTokens[
|
||||
/**
|
||||
* @type {"delimiter"|"postDelimiter"|"tag"|"type"|
|
||||
* "lineEnd"|"postType"|"postTag"|"name"|
|
||||
* "postName"|"description"}
|
||||
*/ (
|
||||
prop
|
||||
)
|
||||
] = '';
|
||||
}
|
||||
|
||||
utils.addLine(jsdoc.source.length - 1, {
|
||||
...line,
|
||||
delimiter,
|
||||
end: '',
|
||||
});
|
||||
};
|
||||
|
||||
utils.reportJSDoc(
|
||||
'Should have no text on the final line (before the `*/`).',
|
||||
null,
|
||||
fixer,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if (noMultilineBlocks) {
|
||||
if (
|
||||
jsdoc.tags.length &&
|
||||
(multilineTags.includes('*') || utils.hasATag(multilineTags))
|
||||
) {
|
||||
lineChecks();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (jsdoc.description.length >= minimumLengthForMultiline) {
|
||||
lineChecks();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
noSingleLineBlocks &&
|
||||
(!jsdoc.tags.length ||
|
||||
!utils.filterTags(({
|
||||
tag: tg,
|
||||
}) => {
|
||||
return !isInvalidSingleLine(tg);
|
||||
}).length)
|
||||
) {
|
||||
utils.reportJSDoc(
|
||||
'Multiline jsdoc blocks are prohibited by ' +
|
||||
'your configuration but fixing would result in a single ' +
|
||||
'line block which you have prohibited with `noSingleLineBlocks`.',
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (jsdoc.tags.length > 1) {
|
||||
if (!allowMultipleTags) {
|
||||
utils.reportJSDoc(
|
||||
'Multiline jsdoc blocks are prohibited by ' +
|
||||
'your configuration but the block has multiple tags.',
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
} else if (jsdoc.tags.length === 1 && jsdoc.description.trim()) {
|
||||
if (!allowMultipleTags) {
|
||||
utils.reportJSDoc(
|
||||
'Multiline jsdoc blocks are prohibited by ' +
|
||||
'your configuration but the block has a description with a tag.',
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const fixer = () => {
|
||||
jsdoc.source = [
|
||||
{
|
||||
number: 1,
|
||||
source: '',
|
||||
tokens: jsdoc.source.reduce((obj, {
|
||||
tokens: {
|
||||
description: desc,
|
||||
tag: tg,
|
||||
type: typ,
|
||||
name: nme,
|
||||
lineEnd,
|
||||
postType,
|
||||
postName,
|
||||
postTag,
|
||||
},
|
||||
}) => {
|
||||
if (typ) {
|
||||
obj.type = typ;
|
||||
}
|
||||
|
||||
if (tg && typ && nme) {
|
||||
obj.postType = postType;
|
||||
}
|
||||
|
||||
if (nme) {
|
||||
obj.name += nme;
|
||||
}
|
||||
|
||||
if (nme && desc) {
|
||||
obj.postName = postName;
|
||||
}
|
||||
|
||||
obj.description += desc;
|
||||
|
||||
const nameOrDescription = obj.description || obj.name;
|
||||
if (
|
||||
nameOrDescription && nameOrDescription.slice(-1) !== ' '
|
||||
) {
|
||||
obj.description += ' ';
|
||||
}
|
||||
|
||||
obj.lineEnd = lineEnd;
|
||||
|
||||
// Already filtered for multiple tags
|
||||
obj.tag += tg;
|
||||
if (tg) {
|
||||
obj.postTag = postTag || ' ';
|
||||
}
|
||||
|
||||
return obj;
|
||||
}, utils.seedTokens({
|
||||
delimiter: '/**',
|
||||
end: '*/',
|
||||
postDelimiter: ' ',
|
||||
})),
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
utils.reportJSDoc(
|
||||
'Multiline jsdoc blocks are prohibited by ' +
|
||||
'your configuration.',
|
||||
null,
|
||||
fixer,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
lineChecks();
|
||||
}, {
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Controls how and whether jsdoc blocks can be expressed as single or multiple line blocks.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/multiline-blocks.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
allowMultipleTags: {
|
||||
type: 'boolean',
|
||||
},
|
||||
minimumLengthForMultiline: {
|
||||
type: 'integer',
|
||||
},
|
||||
multilineTags: {
|
||||
anyOf: [
|
||||
{
|
||||
enum: [
|
||||
'*',
|
||||
],
|
||||
type: 'string',
|
||||
}, {
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
],
|
||||
},
|
||||
noFinalLineText: {
|
||||
type: 'boolean',
|
||||
},
|
||||
noMultilineBlocks: {
|
||||
type: 'boolean',
|
||||
},
|
||||
noSingleLineBlocks: {
|
||||
type: 'boolean',
|
||||
},
|
||||
noZeroLineText: {
|
||||
type: 'boolean',
|
||||
},
|
||||
singleLineTags: {
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
109
node_modules/eslint-plugin-jsdoc/src/rules/noBadBlocks.js
generated
vendored
Normal file
109
node_modules/eslint-plugin-jsdoc/src/rules/noBadBlocks.js
generated
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
import {
|
||||
parse as commentParser,
|
||||
} from 'comment-parser';
|
||||
|
||||
// Neither a single nor 3+ asterisks are valid jsdoc per
|
||||
// https://jsdoc.app/about-getting-started.html#adding-documentation-comments-to-your-code
|
||||
const commentRegexp = /^\/\*(?!\*)/u;
|
||||
const extraAsteriskCommentRegexp = /^\/\*{3,}/u;
|
||||
|
||||
export default iterateJsdoc(({
|
||||
context,
|
||||
sourceCode,
|
||||
allComments,
|
||||
makeReport,
|
||||
}) => {
|
||||
const [
|
||||
{
|
||||
ignore = [
|
||||
'ts-check',
|
||||
'ts-expect-error',
|
||||
'ts-ignore',
|
||||
'ts-nocheck',
|
||||
],
|
||||
preventAllMultiAsteriskBlocks = false,
|
||||
} = {},
|
||||
] = context.options;
|
||||
|
||||
let extraAsterisks = false;
|
||||
const nonJsdocNodes = /** @type {import('estree').Node[]} */ (
|
||||
allComments
|
||||
).filter((comment) => {
|
||||
const commentText = sourceCode.getText(comment);
|
||||
let sliceIndex = 2;
|
||||
if (!commentRegexp.test(commentText)) {
|
||||
const multiline = extraAsteriskCommentRegexp.exec(commentText)?.[0];
|
||||
if (!multiline) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sliceIndex = multiline.length;
|
||||
extraAsterisks = true;
|
||||
if (preventAllMultiAsteriskBlocks) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const tags = (commentParser(
|
||||
`${commentText.slice(0, 2)}*${commentText.slice(sliceIndex)}`,
|
||||
)[0] || {}).tags ?? [];
|
||||
|
||||
return tags.length && !tags.some(({
|
||||
tag,
|
||||
}) => {
|
||||
return ignore.includes(tag);
|
||||
});
|
||||
});
|
||||
|
||||
if (!nonJsdocNodes.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const node of nonJsdocNodes) {
|
||||
const report = /** @type {import('../iterateJsdoc.js').MakeReport} */ (
|
||||
makeReport
|
||||
)(context, node);
|
||||
|
||||
// eslint-disable-next-line no-loop-func
|
||||
const fix = /** @type {import('eslint').Rule.ReportFixer} */ (fixer) => {
|
||||
const text = sourceCode.getText(node);
|
||||
|
||||
return fixer.replaceText(
|
||||
node,
|
||||
extraAsterisks ?
|
||||
text.replace(extraAsteriskCommentRegexp, '/**') :
|
||||
text.replace('/*', '/**'),
|
||||
);
|
||||
};
|
||||
|
||||
report('Expected JSDoc-like comment to begin with two asterisks.', fix);
|
||||
}
|
||||
}, {
|
||||
checkFile: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'This rule checks for multi-line-style comments which fail to meet the criteria of a jsdoc block.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/no-bad-blocks.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
ignore: {
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
preventAllMultiAsteriskBlocks: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'layout',
|
||||
},
|
||||
});
|
||||
69
node_modules/eslint-plugin-jsdoc/src/rules/noBlankBlockDescriptions.js
generated
vendored
Normal file
69
node_modules/eslint-plugin-jsdoc/src/rules/noBlankBlockDescriptions.js
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
const anyWhitespaceLines = /^\s*$/u;
|
||||
const atLeastTwoLinesWhitespace = /^[ \t]*\n[ \t]*\n\s*$/u;
|
||||
|
||||
export default iterateJsdoc(({
|
||||
jsdoc,
|
||||
utils,
|
||||
}) => {
|
||||
const {
|
||||
description,
|
||||
descriptions,
|
||||
lastDescriptionLine,
|
||||
} = utils.getDescription();
|
||||
|
||||
const regex = jsdoc.tags.length ?
|
||||
anyWhitespaceLines :
|
||||
atLeastTwoLinesWhitespace;
|
||||
|
||||
if (descriptions.length && regex.test(description)) {
|
||||
if (jsdoc.tags.length) {
|
||||
utils.reportJSDoc(
|
||||
'There should be no blank lines in block descriptions followed by tags.',
|
||||
{
|
||||
line: lastDescriptionLine,
|
||||
},
|
||||
() => {
|
||||
utils.setBlockDescription(() => {
|
||||
// Remove all lines
|
||||
return [];
|
||||
});
|
||||
},
|
||||
);
|
||||
} else {
|
||||
utils.reportJSDoc(
|
||||
'There should be no extra blank lines in block descriptions not followed by tags.',
|
||||
{
|
||||
line: lastDescriptionLine,
|
||||
},
|
||||
() => {
|
||||
utils.setBlockDescription((info, seedTokens) => {
|
||||
return [
|
||||
// Keep the starting line
|
||||
{
|
||||
number: 0,
|
||||
source: '',
|
||||
tokens: seedTokens({
|
||||
...info,
|
||||
description: '',
|
||||
}),
|
||||
},
|
||||
];
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Detects and removes extra lines of a blank block description',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/no-blank-block-descriptions.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'whitespace',
|
||||
schema: [],
|
||||
type: 'layout',
|
||||
},
|
||||
});
|
||||
53
node_modules/eslint-plugin-jsdoc/src/rules/noBlankBlocks.js
generated
vendored
Normal file
53
node_modules/eslint-plugin-jsdoc/src/rules/noBlankBlocks.js
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
export default iterateJsdoc(({
|
||||
context,
|
||||
jsdoc,
|
||||
utils,
|
||||
}) => {
|
||||
if (jsdoc.tags.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
description,
|
||||
lastDescriptionLine,
|
||||
} = utils.getDescription();
|
||||
if (description.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
enableFixer,
|
||||
} = context.options[0] || {};
|
||||
|
||||
utils.reportJSDoc(
|
||||
'No empty blocks',
|
||||
{
|
||||
line: lastDescriptionLine,
|
||||
},
|
||||
enableFixer ? () => {
|
||||
jsdoc.source.splice(0, jsdoc.source.length);
|
||||
} : null,
|
||||
);
|
||||
}, {
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Removes empty blocks with nothing but possibly line breaks',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/no-blank-blocks.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
enableFixer: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
85
node_modules/eslint-plugin-jsdoc/src/rules/noDefaults.js
generated
vendored
Normal file
85
node_modules/eslint-plugin-jsdoc/src/rules/noDefaults.js
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
export default iterateJsdoc(({
|
||||
context,
|
||||
utils,
|
||||
}) => {
|
||||
const {
|
||||
noOptionalParamNames,
|
||||
} = context.options[0] || {};
|
||||
const paramTags = utils.getPresentTags([
|
||||
'param', 'arg', 'argument',
|
||||
]);
|
||||
for (const tag of paramTags) {
|
||||
if (noOptionalParamNames && tag.optional) {
|
||||
utils.reportJSDoc(`Optional param names are not permitted on @${tag.tag}.`, tag, () => {
|
||||
utils.changeTag(tag, {
|
||||
name: tag.name.replace(/([^=]*)(=.+)?/u, '$1'),
|
||||
});
|
||||
});
|
||||
} else if (tag.default) {
|
||||
utils.reportJSDoc(`Defaults are not permitted on @${tag.tag}.`, tag, () => {
|
||||
utils.changeTag(tag, {
|
||||
name: tag.name.replace(/([^=]*)(=.+)?/u, '[$1]'),
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const defaultTags = utils.getPresentTags([
|
||||
'default', 'defaultvalue',
|
||||
]);
|
||||
for (const tag of defaultTags) {
|
||||
if (tag.description.trim()) {
|
||||
utils.reportJSDoc(`Default values are not permitted on @${tag.tag}.`, tag, () => {
|
||||
utils.changeTag(tag, {
|
||||
description: '',
|
||||
postTag: '',
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}, {
|
||||
contextDefaults: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'This rule reports defaults being used on the relevant portion of `@param` or `@default`.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/no-defaults.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
contexts: {
|
||||
items: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
comment: {
|
||||
type: 'string',
|
||||
},
|
||||
context: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
noOptionalParamNames: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
195
node_modules/eslint-plugin-jsdoc/src/rules/noMissingSyntax.js
generated
vendored
Normal file
195
node_modules/eslint-plugin-jsdoc/src/rules/noMissingSyntax.js
generated
vendored
Normal file
@@ -0,0 +1,195 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* comment: string,
|
||||
* context: string,
|
||||
* message: string,
|
||||
* minimum: import('../iterateJsdoc.js').Integer
|
||||
* }} ContextObject
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {string|ContextObject} Context
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {import('../iterateJsdoc.js').StateObject} state
|
||||
* @returns {void}
|
||||
*/
|
||||
const setDefaults = (state) => {
|
||||
if (!state.selectorMap) {
|
||||
state.selectorMap = {};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('../iterateJsdoc.js').StateObject} state
|
||||
* @param {string} selector
|
||||
* @param {string} comment
|
||||
* @returns {void}
|
||||
*/
|
||||
const incrementSelector = (state, selector, comment) => {
|
||||
if (!state.selectorMap[selector]) {
|
||||
state.selectorMap[selector] = {};
|
||||
}
|
||||
|
||||
if (!state.selectorMap[selector][comment]) {
|
||||
state.selectorMap[selector][comment] = 0;
|
||||
}
|
||||
|
||||
state.selectorMap[selector][comment]++;
|
||||
};
|
||||
|
||||
export default iterateJsdoc(({
|
||||
context,
|
||||
info: {
|
||||
comment,
|
||||
},
|
||||
state,
|
||||
utils,
|
||||
}) => {
|
||||
if (!context.options[0]) {
|
||||
// Handle error later
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Context[]}
|
||||
*/
|
||||
const contexts = context.options[0].contexts;
|
||||
|
||||
const {
|
||||
contextStr,
|
||||
} = utils.findContext(contexts, comment);
|
||||
|
||||
setDefaults(state);
|
||||
|
||||
incrementSelector(state, contextStr, String(comment));
|
||||
}, {
|
||||
contextSelected: true,
|
||||
exit ({
|
||||
context,
|
||||
settings,
|
||||
state,
|
||||
}) {
|
||||
if (!context.options.length && !settings.contexts) {
|
||||
context.report({
|
||||
loc: {
|
||||
end: {
|
||||
column: 1,
|
||||
line: 1,
|
||||
},
|
||||
start: {
|
||||
column: 1,
|
||||
line: 1,
|
||||
},
|
||||
},
|
||||
message: 'Rule `no-missing-syntax` is missing a `contexts` option.',
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setDefaults(state);
|
||||
|
||||
/**
|
||||
* @type {Context[]}
|
||||
*/
|
||||
const contexts = (context.options[0] ?? {}).contexts ?? settings?.contexts;
|
||||
|
||||
// Report when MISSING
|
||||
contexts.some((cntxt) => {
|
||||
const contextStr = typeof cntxt === 'object' ? cntxt.context ?? 'any' : cntxt;
|
||||
const comment = typeof cntxt === 'string' ? '' : cntxt?.comment;
|
||||
|
||||
const contextKey = contextStr === 'any' ? 'undefined' : contextStr;
|
||||
|
||||
if (
|
||||
(!state.selectorMap[contextKey] ||
|
||||
!state.selectorMap[contextKey][comment] ||
|
||||
state.selectorMap[contextKey][comment] < (
|
||||
// @ts-expect-error comment would need an object, not string
|
||||
cntxt?.minimum ?? 1
|
||||
)) &&
|
||||
(contextStr !== 'any' || Object.values(state.selectorMap).every((cmmnt) => {
|
||||
return !cmmnt[comment] || cmmnt[comment] < (
|
||||
// @ts-expect-error comment would need an object, not string
|
||||
cntxt?.minimum ?? 1
|
||||
);
|
||||
}))
|
||||
) {
|
||||
const message = typeof cntxt === 'string' ?
|
||||
'Syntax is required: {{context}}' :
|
||||
cntxt?.message ?? ('Syntax is required: {{context}}' +
|
||||
(comment ? ' with {{comment}}' : ''));
|
||||
context.report({
|
||||
data: {
|
||||
comment,
|
||||
context: contextStr,
|
||||
},
|
||||
loc: {
|
||||
end: {
|
||||
column: 1,
|
||||
line: 1,
|
||||
},
|
||||
start: {
|
||||
column: 1,
|
||||
line: 1,
|
||||
},
|
||||
},
|
||||
message,
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
},
|
||||
matchContext: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Reports when certain comment structures are always expected.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/no-missing-syntax.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
contexts: {
|
||||
items: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
comment: {
|
||||
type: 'string',
|
||||
},
|
||||
context: {
|
||||
type: 'string',
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
},
|
||||
minimum: {
|
||||
type: 'integer',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
134
node_modules/eslint-plugin-jsdoc/src/rules/noMultiAsterisks.js
generated
vendored
Normal file
134
node_modules/eslint-plugin-jsdoc/src/rules/noMultiAsterisks.js
generated
vendored
Normal file
@@ -0,0 +1,134 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
const middleAsterisksBlockWS = /^([\t ]|\*(?!\*))+/u;
|
||||
const middleAsterisksNoBlockWS = /^\*+/u;
|
||||
|
||||
const endAsterisksSingleLineBlockWS = /\*((?:\*|(?: |\t))*)\*$/u;
|
||||
const endAsterisksMultipleLineBlockWS = /((?:\*|(?: |\t))*)\*$/u;
|
||||
|
||||
const endAsterisksSingleLineNoBlockWS = /\*(\**)\*$/u;
|
||||
const endAsterisksMultipleLineNoBlockWS = /(\**)\*$/u;
|
||||
|
||||
export default iterateJsdoc(({
|
||||
context,
|
||||
jsdoc,
|
||||
utils,
|
||||
}) => {
|
||||
const {
|
||||
allowWhitespace = false,
|
||||
preventAtEnd = true,
|
||||
preventAtMiddleLines = true,
|
||||
} = context.options[0] || {};
|
||||
|
||||
const middleAsterisks = allowWhitespace ? middleAsterisksNoBlockWS : middleAsterisksBlockWS;
|
||||
|
||||
// eslint-disable-next-line complexity -- Todo
|
||||
jsdoc.source.some(({
|
||||
tokens,
|
||||
number,
|
||||
}) => {
|
||||
const {
|
||||
delimiter,
|
||||
tag,
|
||||
name,
|
||||
type,
|
||||
description,
|
||||
end,
|
||||
postDelimiter,
|
||||
} = tokens;
|
||||
|
||||
if (
|
||||
preventAtMiddleLines &&
|
||||
!end && !tag && !type && !name &&
|
||||
(
|
||||
!allowWhitespace && middleAsterisks.test(description) ||
|
||||
allowWhitespace && middleAsterisks.test(postDelimiter + description)
|
||||
)
|
||||
) {
|
||||
// console.log('description', JSON.stringify(description));
|
||||
const fix = () => {
|
||||
tokens.description = description.replace(middleAsterisks, '');
|
||||
};
|
||||
|
||||
utils.reportJSDoc(
|
||||
'Should be no multiple asterisks on middle lines.',
|
||||
{
|
||||
line: number,
|
||||
},
|
||||
fix,
|
||||
true,
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!preventAtEnd || !end) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isSingleLineBlock = delimiter === '/**';
|
||||
const delim = isSingleLineBlock ? '*' : delimiter;
|
||||
const endAsterisks = allowWhitespace ?
|
||||
(isSingleLineBlock ? endAsterisksSingleLineNoBlockWS : endAsterisksMultipleLineNoBlockWS) :
|
||||
(isSingleLineBlock ? endAsterisksSingleLineBlockWS : endAsterisksMultipleLineBlockWS);
|
||||
|
||||
const endingAsterisksAndSpaces = (
|
||||
allowWhitespace ? postDelimiter + description + delim : description + delim
|
||||
).match(
|
||||
endAsterisks,
|
||||
);
|
||||
|
||||
if (
|
||||
!endingAsterisksAndSpaces ||
|
||||
!isSingleLineBlock && endingAsterisksAndSpaces[1] && !endingAsterisksAndSpaces[1].trim()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const endFix = () => {
|
||||
if (!isSingleLineBlock) {
|
||||
tokens.delimiter = '';
|
||||
}
|
||||
|
||||
tokens.description = (description + delim).replace(endAsterisks, '');
|
||||
};
|
||||
|
||||
utils.reportJSDoc(
|
||||
'Should be no multiple asterisks on end lines.',
|
||||
{
|
||||
line: number,
|
||||
},
|
||||
endFix,
|
||||
true,
|
||||
);
|
||||
|
||||
return true;
|
||||
});
|
||||
}, {
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: '',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/no-multi-asterisks.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
allowWhitespace: {
|
||||
type: 'boolean',
|
||||
},
|
||||
preventAtEnd: {
|
||||
type: 'boolean',
|
||||
},
|
||||
preventAtMiddleLines: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
91
node_modules/eslint-plugin-jsdoc/src/rules/noRestrictedSyntax.js
generated
vendored
Normal file
91
node_modules/eslint-plugin-jsdoc/src/rules/noRestrictedSyntax.js
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
export default iterateJsdoc(({
|
||||
context,
|
||||
info: {
|
||||
comment,
|
||||
},
|
||||
report,
|
||||
utils,
|
||||
}) => {
|
||||
if (!context.options.length) {
|
||||
report('Rule `no-restricted-syntax` is missing a `contexts` option.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
contexts,
|
||||
} = context.options[0];
|
||||
|
||||
const {
|
||||
foundContext,
|
||||
contextStr,
|
||||
} = utils.findContext(contexts, comment);
|
||||
|
||||
// We are not on the *particular* matching context/comment, so don't assume
|
||||
// we need reporting
|
||||
if (!foundContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message = /** @type {import('../iterateJsdoc.js').ContextObject} */ (
|
||||
foundContext
|
||||
)?.message ??
|
||||
'Syntax is restricted: {{context}}' +
|
||||
(comment ? ' with {{comment}}' : '');
|
||||
|
||||
report(message, null, null, comment ? {
|
||||
comment,
|
||||
context: contextStr,
|
||||
} : {
|
||||
context: contextStr,
|
||||
});
|
||||
}, {
|
||||
contextSelected: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Reports when certain comment structures are present.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/no-restricted-syntax.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
contexts: {
|
||||
items: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
comment: {
|
||||
type: 'string',
|
||||
},
|
||||
context: {
|
||||
type: 'string',
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
required: [
|
||||
'contexts',
|
||||
],
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
nonGlobalSettings: true,
|
||||
});
|
||||
77
node_modules/eslint-plugin-jsdoc/src/rules/noTypes.js
generated
vendored
Normal file
77
node_modules/eslint-plugin-jsdoc/src/rules/noTypes.js
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
/**
|
||||
* @param {import('comment-parser').Line} line
|
||||
*/
|
||||
const removeType = ({
|
||||
tokens,
|
||||
}) => {
|
||||
tokens.postTag = '';
|
||||
tokens.type = '';
|
||||
};
|
||||
|
||||
export default iterateJsdoc(({
|
||||
utils,
|
||||
}) => {
|
||||
if (!utils.isIteratingFunction() && !utils.isVirtualFunction()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tags = utils.getPresentTags([
|
||||
'param', 'arg', 'argument', 'returns', 'return',
|
||||
]);
|
||||
|
||||
for (const tag of tags) {
|
||||
if (tag.type) {
|
||||
utils.reportJSDoc(`Types are not permitted on @${tag.tag}.`, tag, () => {
|
||||
for (const source of tag.source) {
|
||||
removeType(source);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}, {
|
||||
contextDefaults: [
|
||||
'ArrowFunctionExpression', 'FunctionDeclaration', 'FunctionExpression', 'TSDeclareFunction',
|
||||
// Add this to above defaults
|
||||
'TSMethodSignature'
|
||||
],
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'This rule reports types being used on `@param` or `@returns`.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/no-types.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
contexts: {
|
||||
items: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
comment: {
|
||||
type: 'string',
|
||||
},
|
||||
context: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
380
node_modules/eslint-plugin-jsdoc/src/rules/noUndefinedTypes.js
generated
vendored
Normal file
380
node_modules/eslint-plugin-jsdoc/src/rules/noUndefinedTypes.js
generated
vendored
Normal file
@@ -0,0 +1,380 @@
|
||||
import { dirname, join } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
import { createSyncFn } from 'synckit';
|
||||
import {
|
||||
getJSDocComment,
|
||||
parse as parseType,
|
||||
traverse,
|
||||
tryParse as tryParseType,
|
||||
} from '@es-joy/jsdoccomment';
|
||||
import iterateJsdoc, {
|
||||
parseComment,
|
||||
} from '../iterateJsdoc.js';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const pathName = join(__dirname, '../import-worker.mjs');
|
||||
|
||||
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',
|
||||
];
|
||||
|
||||
const typescriptGlobals = [
|
||||
// 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',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param {string|false|undefined} [str]
|
||||
* @returns {undefined|string|false}
|
||||
*/
|
||||
const stripPseudoTypes = (str) => {
|
||||
return str && str.replace(/(?:\.|<>|\.<>|\[\])$/u, '');
|
||||
};
|
||||
|
||||
export default iterateJsdoc(({
|
||||
context,
|
||||
node,
|
||||
report,
|
||||
settings,
|
||||
sourceCode,
|
||||
utils,
|
||||
}) => {
|
||||
const {
|
||||
scopeManager,
|
||||
} = sourceCode;
|
||||
|
||||
// When is this ever `null`?
|
||||
const globalScope = /** @type {import('eslint').Scope.Scope} */ (
|
||||
scopeManager.globalScope
|
||||
);
|
||||
|
||||
const
|
||||
/**
|
||||
* @type {{
|
||||
* definedTypes: string[],
|
||||
* disableReporting: boolean,
|
||||
* markVariablesAsUsed: boolean
|
||||
* }}
|
||||
*/ {
|
||||
definedTypes = [],
|
||||
disableReporting = false,
|
||||
markVariablesAsUsed = true,
|
||||
} = context.options[0] || {};
|
||||
|
||||
/** @type {(string|undefined)[]} */
|
||||
let definedPreferredTypes = [];
|
||||
const {
|
||||
preferredTypes,
|
||||
structuredTags,
|
||||
mode,
|
||||
} = 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 comments = sourceCode.getAllComments()
|
||||
.filter((comment) => {
|
||||
return (/^\*\s/u).test(comment.value);
|
||||
})
|
||||
.map((commentNode) => {
|
||||
return parseComment(commentNode, '');
|
||||
});
|
||||
|
||||
const typedefDeclarations = comments
|
||||
.flatMap((doc) => {
|
||||
return doc.tags.filter(({
|
||||
tag,
|
||||
}) => {
|
||||
return utils.isNamepathDefiningTag(tag);
|
||||
});
|
||||
})
|
||||
.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 {
|
||||
type, name, description
|
||||
} = tag;
|
||||
const typePart = type ? `{${type}} `: '';
|
||||
const imprt = 'import ' + (description
|
||||
? `${typePart}${name} ${description}`
|
||||
: `${typePart}${name}`);
|
||||
|
||||
const getImports = createSyncFn(pathName);
|
||||
const imports = /** @type {import('parse-imports').Import[]} */ (getImports(imprt));
|
||||
if (!imports) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return imports.flatMap(({importClause}) => {
|
||||
/* c8 ignore next */
|
||||
const {default: dflt, named, namespace} = importClause || {};
|
||||
const types = [];
|
||||
if (dflt) {
|
||||
types.push(dflt);
|
||||
}
|
||||
if (namespace) {
|
||||
types.push(namespace);
|
||||
}
|
||||
if (named) {
|
||||
for (const {binding} of named) {
|
||||
types.push(binding);
|
||||
}
|
||||
}
|
||||
|
||||
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 jsdoc = parseComment(commentNode, '');
|
||||
|
||||
return jsdoc.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);
|
||||
}) :
|
||||
utils.getPresentTags([
|
||||
'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';
|
||||
|
||||
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;
|
||||
}).map(({
|
||||
name,
|
||||
}) => {
|
||||
return name;
|
||||
/* c8 ignore next */
|
||||
}) : [],
|
||||
)
|
||||
.concat(extraTypes)
|
||||
.concat(typedefDeclarations)
|
||||
.concat(importTags)
|
||||
.concat(definedTypes)
|
||||
.concat(/** @type {string[]} */ (definedPreferredTypes))
|
||||
.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 tagsWithTypes = /** @type {TypeAndTagInfo[]} */ ([
|
||||
...typeTags,
|
||||
...namepathReferencingTags,
|
||||
...namepathOrUrlReferencingTags,
|
||||
// Remove types which failed to parse
|
||||
].filter(Boolean));
|
||||
|
||||
for (const {
|
||||
tag,
|
||||
parsedType,
|
||||
} of tagsWithTypes) {
|
||||
traverse(parsedType, (nde) => {
|
||||
const {
|
||||
type,
|
||||
value,
|
||||
} = /** @type {import('jsdoc-type-pratt-parser').NameResult} */ (nde);
|
||||
|
||||
if (type === 'JsdocTypeName') {
|
||||
const structuredTypes = structuredTags[tag.tag]?.type;
|
||||
if (!allDefinedTypes.has(value) &&
|
||||
(!Array.isArray(structuredTypes) || !structuredTypes.includes(value))
|
||||
) {
|
||||
if (!disableReporting) {
|
||||
report(`The type '${value}' is undefined.`, null, tag);
|
||||
}
|
||||
} else if (markVariablesAsUsed && !extraTypes.includes(value)) {
|
||||
if (sourceCode.markVariableAsUsed) {
|
||||
sourceCode.markVariableAsUsed(value);
|
||||
/* c8 ignore next 3 */
|
||||
} else {
|
||||
context.markVariableAsUsed(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Checks that types in jsdoc comments are defined.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/no-undefined-types.md#repos-sticky-header',
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
definedTypes: {
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
disableReporting: {
|
||||
type: 'boolean',
|
||||
},
|
||||
markVariablesAsUsed: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
189
node_modules/eslint-plugin-jsdoc/src/rules/requireAsteriskPrefix.js
generated
vendored
Normal file
189
node_modules/eslint-plugin-jsdoc/src/rules/requireAsteriskPrefix.js
generated
vendored
Normal file
@@ -0,0 +1,189 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
export default iterateJsdoc(({
|
||||
context,
|
||||
jsdoc,
|
||||
utils,
|
||||
indent,
|
||||
}) => {
|
||||
const [
|
||||
defaultRequireValue = 'always',
|
||||
{
|
||||
tags: tagMap = {},
|
||||
} = {},
|
||||
] = context.options;
|
||||
|
||||
const {
|
||||
source,
|
||||
} = jsdoc;
|
||||
|
||||
const always = defaultRequireValue === 'always';
|
||||
const never = defaultRequireValue === 'never';
|
||||
|
||||
/** @type {string} */
|
||||
let currentTag;
|
||||
source.some(({
|
||||
number,
|
||||
tokens,
|
||||
}) => {
|
||||
const {
|
||||
delimiter,
|
||||
tag,
|
||||
end,
|
||||
description,
|
||||
} = tokens;
|
||||
|
||||
/**
|
||||
* @returns {void}
|
||||
*/
|
||||
const neverFix = () => {
|
||||
tokens.delimiter = '';
|
||||
tokens.postDelimiter = '';
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} checkValue
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const checkNever = (checkValue) => {
|
||||
if (delimiter && delimiter !== '/**' && (
|
||||
never && !tagMap.always?.includes(checkValue) ||
|
||||
tagMap.never?.includes(checkValue)
|
||||
)) {
|
||||
utils.reportJSDoc('Expected JSDoc line to have no prefix.', {
|
||||
column: 0,
|
||||
line: number,
|
||||
}, neverFix);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {void}
|
||||
*/
|
||||
const alwaysFix = () => {
|
||||
if (!tokens.start) {
|
||||
tokens.start = indent + ' ';
|
||||
}
|
||||
|
||||
tokens.delimiter = '*';
|
||||
tokens.postDelimiter = tag || description ? ' ' : '';
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} checkValue
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const checkAlways = (checkValue) => {
|
||||
if (
|
||||
!delimiter && (
|
||||
always && !tagMap.never?.includes(checkValue) ||
|
||||
tagMap.always?.includes(checkValue)
|
||||
)
|
||||
) {
|
||||
utils.reportJSDoc('Expected JSDoc line to have the prefix.', {
|
||||
column: 0,
|
||||
line: number,
|
||||
}, alwaysFix);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
if (tag) {
|
||||
// Remove at sign
|
||||
currentTag = tag.slice(1);
|
||||
}
|
||||
|
||||
if (
|
||||
// If this is the end but has a tag, the delimiter will also be
|
||||
// populated and will be safely ignored later
|
||||
end && !tag
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!currentTag) {
|
||||
if (tagMap.any?.includes('*description')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (checkNever('*description')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (checkAlways('*description')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tagMap.any?.includes(currentTag)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (checkNever(currentTag)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (checkAlways(currentTag)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}, {
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description:
|
||||
'Requires that each JSDoc line starts with an `*`.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-asterisk-prefix.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [
|
||||
{
|
||||
enum: [
|
||||
'always', 'never', 'any',
|
||||
],
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
tags: {
|
||||
properties: {
|
||||
always: {
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
any: {
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
never: {
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'layout',
|
||||
},
|
||||
});
|
||||
161
node_modules/eslint-plugin-jsdoc/src/rules/requireDescription.js
generated
vendored
Normal file
161
node_modules/eslint-plugin-jsdoc/src/rules/requireDescription.js
generated
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
/**
|
||||
* @param {string} description
|
||||
* @returns {import('../iterateJsdoc.js').Integer}
|
||||
*/
|
||||
const checkDescription = (description) => {
|
||||
return description
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter(Boolean)
|
||||
.length;
|
||||
};
|
||||
|
||||
export default iterateJsdoc(({
|
||||
jsdoc,
|
||||
report,
|
||||
utils,
|
||||
context,
|
||||
}) => {
|
||||
if (utils.avoidDocs()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
descriptionStyle = 'body',
|
||||
} = context.options[0] || {};
|
||||
|
||||
let targetTagName = utils.getPreferredTagName({
|
||||
// We skip reporting except when `@description` is essential to the rule,
|
||||
// so user can block the tag and still meaningfully use this rule
|
||||
// even if the tag is present (and `check-tag-names` is the one to
|
||||
// normally report the fact that it is blocked but present)
|
||||
skipReportingBlockedTag: descriptionStyle !== 'tag',
|
||||
tagName: 'description',
|
||||
});
|
||||
if (!targetTagName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isBlocked = typeof targetTagName === 'object' && 'blocked' in targetTagName && targetTagName.blocked;
|
||||
if (isBlocked) {
|
||||
targetTagName = /** @type {{blocked: true; tagName: string;}} */ (
|
||||
targetTagName
|
||||
).tagName;
|
||||
}
|
||||
|
||||
if (descriptionStyle !== 'tag') {
|
||||
const {
|
||||
description,
|
||||
} = utils.getDescription();
|
||||
if (checkDescription(description || '')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (descriptionStyle === 'body') {
|
||||
const descTags = utils.getPresentTags([
|
||||
'desc', 'description',
|
||||
]);
|
||||
if (descTags.length) {
|
||||
const [
|
||||
{
|
||||
tag: tagName,
|
||||
},
|
||||
] = descTags;
|
||||
report(`Remove the @${tagName} tag to leave a plain block description or add additional description text above the @${tagName} line.`);
|
||||
} else {
|
||||
report('Missing JSDoc block description.');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const functionExamples = isBlocked ?
|
||||
[] :
|
||||
jsdoc.tags.filter(({
|
||||
tag,
|
||||
}) => {
|
||||
return tag === targetTagName;
|
||||
});
|
||||
|
||||
if (!functionExamples.length) {
|
||||
report(
|
||||
descriptionStyle === 'any' ?
|
||||
`Missing JSDoc block description or @${targetTagName} declaration.` :
|
||||
`Missing JSDoc @${targetTagName} declaration.`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (const example of functionExamples) {
|
||||
if (!checkDescription(`${example.name} ${utils.getTagDescription(example)}`)) {
|
||||
report(`Missing JSDoc @${targetTagName} description.`, null, example);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
contextDefaults: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Requires that all functions have a description.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-description.md#repos-sticky-header',
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
checkConstructors: {
|
||||
default: true,
|
||||
type: 'boolean',
|
||||
},
|
||||
checkGetters: {
|
||||
default: true,
|
||||
type: 'boolean',
|
||||
},
|
||||
checkSetters: {
|
||||
default: true,
|
||||
type: 'boolean',
|
||||
},
|
||||
contexts: {
|
||||
items: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
comment: {
|
||||
type: 'string',
|
||||
},
|
||||
context: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
descriptionStyle: {
|
||||
enum: [
|
||||
'body', 'tag', 'any',
|
||||
],
|
||||
type: 'string',
|
||||
},
|
||||
exemptedBy: {
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
335
node_modules/eslint-plugin-jsdoc/src/rules/requireDescriptionCompleteSentence.js
generated
vendored
Normal file
335
node_modules/eslint-plugin-jsdoc/src/rules/requireDescriptionCompleteSentence.js
generated
vendored
Normal file
@@ -0,0 +1,335 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
import escapeStringRegexp from 'escape-string-regexp';
|
||||
|
||||
const otherDescriptiveTags = new Set([
|
||||
// 'copyright' and 'see' might be good addition, but as the former may be
|
||||
// sensitive text, and the latter may have just a link, they are not
|
||||
// included by default
|
||||
'summary', 'file', 'fileoverview', 'overview', 'classdesc', 'todo',
|
||||
'deprecated', 'throws', 'exception', 'yields', 'yield',
|
||||
]);
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
* @returns {string[]}
|
||||
*/
|
||||
const extractParagraphs = (text) => {
|
||||
return text.split(/(?<![;:])\n\n+/u);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
* @param {string|RegExp} abbreviationsRegex
|
||||
* @returns {string[]}
|
||||
*/
|
||||
const extractSentences = (text, abbreviationsRegex) => {
|
||||
const txt = text
|
||||
// Remove all {} tags.
|
||||
.replaceAll(/(?<!^)\{[\s\S]*?\}\s*/gu, '')
|
||||
|
||||
// Remove custom abbreviations
|
||||
.replace(abbreviationsRegex, '');
|
||||
|
||||
const sentenceEndGrouping = /([.?!])(?:\s+|$)/ug;
|
||||
|
||||
const puncts = [
|
||||
...txt.matchAll(sentenceEndGrouping),
|
||||
].map((sentEnd) => {
|
||||
return sentEnd[0];
|
||||
});
|
||||
|
||||
return txt
|
||||
.split(/[.?!](?:\s+|$)/u)
|
||||
|
||||
// Re-add the dot.
|
||||
.map((sentence, idx) => {
|
||||
return !puncts[idx] && /^\s*$/u.test(sentence) ? sentence : `${sentence}${puncts[idx] || ''}`;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const isNewLinePrecededByAPeriod = (text) => {
|
||||
/** @type {boolean} */
|
||||
let lastLineEndsSentence;
|
||||
|
||||
const lines = text.split('\n');
|
||||
|
||||
return !lines.some((line) => {
|
||||
if (lastLineEndsSentence === false && /^[A-Z][a-z]/u.test(line)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
lastLineEndsSentence = /[.:?!|]$/u.test(line);
|
||||
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const isCapitalized = (str) => {
|
||||
return str[0] === str[0].toUpperCase();
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const isTable = (str) => {
|
||||
return str.charAt(0) === '|';
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @returns {string}
|
||||
*/
|
||||
const capitalize = (str) => {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} description
|
||||
* @param {import('../iterateJsdoc.js').Report} reportOrig
|
||||
* @param {import('eslint').Rule.Node} jsdocNode
|
||||
* @param {string|RegExp} abbreviationsRegex
|
||||
* @param {import('eslint').SourceCode} sourceCode
|
||||
* @param {import('comment-parser').Spec|{
|
||||
* line: import('../iterateJsdoc.js').Integer
|
||||
* }} tag
|
||||
* @param {boolean} newlineBeforeCapsAssumesBadSentenceEnd
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const validateDescription = (
|
||||
description, reportOrig, jsdocNode, abbreviationsRegex,
|
||||
sourceCode, tag, newlineBeforeCapsAssumesBadSentenceEnd,
|
||||
) => {
|
||||
if (!description || (/^\n+$/u).test(description)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const descriptionNoHeadings = description.replaceAll(/^\s*#[^\n]*(\n|$)/gm, '');
|
||||
|
||||
const paragraphs = extractParagraphs(descriptionNoHeadings).filter(Boolean);
|
||||
|
||||
return paragraphs.some((paragraph, parIdx) => {
|
||||
const sentences = extractSentences(paragraph, abbreviationsRegex);
|
||||
|
||||
const fix = /** @type {import('eslint').Rule.ReportFixer} */ (fixer) => {
|
||||
let text = sourceCode.getText(jsdocNode);
|
||||
|
||||
if (!/[.:?!]$/u.test(paragraph)) {
|
||||
const line = paragraph.split('\n').filter(Boolean).pop();
|
||||
text = text.replace(new RegExp(`${escapeStringRegexp(
|
||||
/** @type {string} */
|
||||
(line),
|
||||
)}$`, 'mu'), `${line}.`);
|
||||
}
|
||||
|
||||
for (const sentence of sentences.filter((sentence_) => {
|
||||
return !(/^\s*$/u).test(sentence_) && !isCapitalized(sentence_) &&
|
||||
!isTable(sentence_);
|
||||
})) {
|
||||
const beginning = sentence.split('\n')[0];
|
||||
|
||||
if ('tag' in tag && tag.tag) {
|
||||
const reg = new RegExp(`(@${escapeStringRegexp(tag.tag)}.*)${escapeStringRegexp(beginning)}`, 'u');
|
||||
|
||||
text = text.replace(reg, (_$0, $1) => {
|
||||
return $1 + capitalize(beginning);
|
||||
});
|
||||
} else {
|
||||
text = text.replace(new RegExp('((?:[.?!]|\\*|\\})\\s*)' + escapeStringRegexp(beginning), 'u'), '$1' + capitalize(beginning));
|
||||
}
|
||||
}
|
||||
|
||||
return fixer.replaceText(jsdocNode, text);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} msg
|
||||
* @param {import('eslint').Rule.ReportFixer | null | undefined} fixer
|
||||
* @param {{
|
||||
* line?: number | undefined;
|
||||
* column?: number | undefined;
|
||||
* } | (import('comment-parser').Spec & {
|
||||
* line?: number | undefined;
|
||||
* column?: number | undefined;
|
||||
* })} tagObj
|
||||
* @returns {void}
|
||||
*/
|
||||
const report = (msg, fixer, tagObj) => {
|
||||
if ('line' in tagObj) {
|
||||
/**
|
||||
* @type {{
|
||||
* line: number;
|
||||
* }}
|
||||
*/ (tagObj).line += parIdx * 2;
|
||||
} else {
|
||||
/** @type {import('comment-parser').Spec} */ (
|
||||
tagObj
|
||||
).source[0].number += parIdx * 2;
|
||||
}
|
||||
|
||||
// Avoid errors if old column doesn't exist here
|
||||
tagObj.column = 0;
|
||||
reportOrig(msg, fixer, tagObj);
|
||||
};
|
||||
|
||||
if (sentences.some((sentence) => {
|
||||
return (/^[.?!]$/u).test(sentence);
|
||||
})) {
|
||||
report('Sentences must be more than punctuation.', null, tag);
|
||||
}
|
||||
|
||||
if (sentences.some((sentence) => {
|
||||
return !(/^\s*$/u).test(sentence) && !isCapitalized(sentence) && !isTable(sentence);
|
||||
})) {
|
||||
report('Sentences should start with an uppercase character.', fix, tag);
|
||||
}
|
||||
|
||||
const paragraphNoAbbreviations = paragraph.replace(abbreviationsRegex, '');
|
||||
|
||||
if (!/(?:[.?!|]|```)\s*$/u.test(paragraphNoAbbreviations)) {
|
||||
report('Sentences must end with a period.', fix, tag);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (newlineBeforeCapsAssumesBadSentenceEnd && !isNewLinePrecededByAPeriod(paragraphNoAbbreviations)) {
|
||||
report('A line of text is started with an uppercase character, but the preceding line does not end the sentence.', null, tag);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
export default iterateJsdoc(({
|
||||
sourceCode,
|
||||
context,
|
||||
jsdoc,
|
||||
report,
|
||||
jsdocNode,
|
||||
utils,
|
||||
}) => {
|
||||
const /** @type {{abbreviations: string[], newlineBeforeCapsAssumesBadSentenceEnd: boolean}} */ {
|
||||
abbreviations = [],
|
||||
newlineBeforeCapsAssumesBadSentenceEnd = false,
|
||||
} = context.options[0] || {};
|
||||
|
||||
const abbreviationsRegex = abbreviations.length ?
|
||||
new RegExp('\\b' + abbreviations.map((abbreviation) => {
|
||||
return escapeStringRegexp(abbreviation.replaceAll(/\.$/ug, '') + '.');
|
||||
}).join('|') + '(?:$|\\s)', 'gu') :
|
||||
'';
|
||||
|
||||
let {
|
||||
description,
|
||||
} = utils.getDescription();
|
||||
|
||||
const indices = [
|
||||
...description.matchAll(/```[\s\S]*```/gu),
|
||||
].map((match) => {
|
||||
const {
|
||||
index,
|
||||
} = match;
|
||||
const [
|
||||
{
|
||||
length,
|
||||
},
|
||||
] = match;
|
||||
return {
|
||||
index,
|
||||
length,
|
||||
};
|
||||
}).reverse();
|
||||
|
||||
for (const {
|
||||
index,
|
||||
length,
|
||||
} of indices) {
|
||||
description = description.slice(0, index) +
|
||||
description.slice(/** @type {import('../iterateJsdoc.js').Integer} */ (
|
||||
index
|
||||
) + length);
|
||||
}
|
||||
|
||||
if (validateDescription(description, report, jsdocNode, abbreviationsRegex, sourceCode, {
|
||||
line: jsdoc.source[0].number + 1,
|
||||
}, newlineBeforeCapsAssumesBadSentenceEnd)) {
|
||||
return;
|
||||
}
|
||||
|
||||
utils.forEachPreferredTag('description', (matchingJsdocTag) => {
|
||||
const desc = `${matchingJsdocTag.name} ${utils.getTagDescription(matchingJsdocTag)}`.trim();
|
||||
validateDescription(desc, report, jsdocNode, abbreviationsRegex, sourceCode, matchingJsdocTag, newlineBeforeCapsAssumesBadSentenceEnd);
|
||||
}, true);
|
||||
|
||||
const {
|
||||
tagsWithNames,
|
||||
} = utils.getTagsByType(jsdoc.tags);
|
||||
const tagsWithoutNames = utils.filterTags(({
|
||||
tag: tagName,
|
||||
}) => {
|
||||
return otherDescriptiveTags.has(tagName) ||
|
||||
utils.hasOptionTag(tagName) && !tagsWithNames.some(({
|
||||
tag,
|
||||
}) => {
|
||||
// If user accidentally adds tags with names (or like `returns`
|
||||
// get parsed as having names), do not add to this list
|
||||
return tag === tagName;
|
||||
});
|
||||
});
|
||||
|
||||
tagsWithNames.some((tag) => {
|
||||
const desc = /** @type {string} */ (
|
||||
utils.getTagDescription(tag)
|
||||
).replace(/^- /u, '').trimEnd();
|
||||
|
||||
return validateDescription(desc, report, jsdocNode, abbreviationsRegex, sourceCode, tag, newlineBeforeCapsAssumesBadSentenceEnd);
|
||||
});
|
||||
|
||||
tagsWithoutNames.some((tag) => {
|
||||
const desc = `${tag.name} ${utils.getTagDescription(tag)}`.trim();
|
||||
|
||||
return validateDescription(desc, report, jsdocNode, abbreviationsRegex, sourceCode, tag, newlineBeforeCapsAssumesBadSentenceEnd);
|
||||
});
|
||||
}, {
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Requires that block description, explicit `@description`, and `@param`/`@returns` tag descriptions are written in complete sentences.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-description-complete-sentence.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
abbreviations: {
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
newlineBeforeCapsAssumesBadSentenceEnd: {
|
||||
type: 'boolean',
|
||||
},
|
||||
tags: {
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
118
node_modules/eslint-plugin-jsdoc/src/rules/requireExample.js
generated
vendored
Normal file
118
node_modules/eslint-plugin-jsdoc/src/rules/requireExample.js
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
export default iterateJsdoc(({
|
||||
context,
|
||||
jsdoc,
|
||||
report,
|
||||
utils,
|
||||
}) => {
|
||||
if (utils.avoidDocs()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
enableFixer = true,
|
||||
exemptNoArguments = false,
|
||||
} = context.options[0] || {};
|
||||
|
||||
const targetTagName = 'example';
|
||||
|
||||
const functionExamples = jsdoc.tags.filter(({
|
||||
tag,
|
||||
}) => {
|
||||
return tag === targetTagName;
|
||||
});
|
||||
|
||||
if (!functionExamples.length) {
|
||||
if (exemptNoArguments && utils.isIteratingFunction() &&
|
||||
!utils.hasParams()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
utils.reportJSDoc(`Missing JSDoc @${targetTagName} declaration.`, null, () => {
|
||||
if (enableFixer) {
|
||||
utils.addTag(targetTagName);
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (const example of functionExamples) {
|
||||
const exampleContent = `${example.name} ${utils.getTagDescription(example)}`
|
||||
.trim()
|
||||
.split('\n')
|
||||
.filter(Boolean);
|
||||
|
||||
if (!exampleContent.length) {
|
||||
report(`Missing JSDoc @${targetTagName} description.`, null, example);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
contextDefaults: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Requires that all functions have examples.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-example.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
checkConstructors: {
|
||||
default: true,
|
||||
type: 'boolean',
|
||||
},
|
||||
checkGetters: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
checkSetters: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
contexts: {
|
||||
items: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
comment: {
|
||||
type: 'string',
|
||||
},
|
||||
context: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
enableFixer: {
|
||||
default: true,
|
||||
type: 'boolean',
|
||||
},
|
||||
exemptedBy: {
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
exemptNoArguments: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
154
node_modules/eslint-plugin-jsdoc/src/rules/requireFileOverview.js
generated
vendored
Normal file
154
node_modules/eslint-plugin-jsdoc/src/rules/requireFileOverview.js
generated
vendored
Normal file
@@ -0,0 +1,154 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
const defaultTags = {
|
||||
file: {
|
||||
initialCommentsOnly: true,
|
||||
mustExist: true,
|
||||
preventDuplicates: true,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('../iterateJsdoc.js').StateObject} state
|
||||
* @returns {void}
|
||||
*/
|
||||
const setDefaults = (state) => {
|
||||
// First iteration
|
||||
if (!state.globalTags) {
|
||||
state.globalTags = {};
|
||||
state.hasDuplicates = {};
|
||||
state.hasTag = {};
|
||||
state.hasNonCommentBeforeTag = {};
|
||||
}
|
||||
};
|
||||
|
||||
export default iterateJsdoc(({
|
||||
jsdocNode,
|
||||
state,
|
||||
utils,
|
||||
context,
|
||||
}) => {
|
||||
const {
|
||||
tags = defaultTags,
|
||||
} = context.options[0] || {};
|
||||
|
||||
setDefaults(state);
|
||||
|
||||
for (const tagName of Object.keys(tags)) {
|
||||
const targetTagName = /** @type {string} */ (utils.getPreferredTagName({
|
||||
tagName,
|
||||
}));
|
||||
|
||||
const hasTag = Boolean(targetTagName && utils.hasTag(targetTagName));
|
||||
|
||||
state.hasTag[tagName] = hasTag || state.hasTag[tagName];
|
||||
|
||||
const hasDuplicate = state.hasDuplicates[tagName];
|
||||
|
||||
if (hasDuplicate === false) {
|
||||
// Was marked before, so if a tag now, is a dupe
|
||||
state.hasDuplicates[tagName] = hasTag;
|
||||
} else if (!hasDuplicate && hasTag) {
|
||||
// No dupes set before, but has first tag, so change state
|
||||
// from `undefined` to `false` so can detect next time
|
||||
state.hasDuplicates[tagName] = false;
|
||||
state.hasNonCommentBeforeTag[tagName] = state.hasNonComment &&
|
||||
state.hasNonComment < jsdocNode.range[0];
|
||||
}
|
||||
}
|
||||
}, {
|
||||
exit ({
|
||||
context,
|
||||
state,
|
||||
utils,
|
||||
}) {
|
||||
setDefaults(state);
|
||||
const {
|
||||
tags = defaultTags,
|
||||
} = context.options[0] || {};
|
||||
|
||||
for (const [
|
||||
tagName,
|
||||
{
|
||||
mustExist = false,
|
||||
preventDuplicates = false,
|
||||
initialCommentsOnly = false,
|
||||
},
|
||||
] of Object.entries(tags)) {
|
||||
const obj = utils.getPreferredTagNameObject({
|
||||
tagName,
|
||||
});
|
||||
if (obj && typeof obj === 'object' && 'blocked' in obj) {
|
||||
utils.reportSettings(
|
||||
`\`settings.jsdoc.tagNamePreference\` cannot block @${obj.tagName} ` +
|
||||
'for the `require-file-overview` rule',
|
||||
);
|
||||
} else {
|
||||
const targetTagName = (
|
||||
obj && typeof obj === 'object' && obj.replacement
|
||||
) || obj;
|
||||
if (mustExist && !state.hasTag[tagName]) {
|
||||
utils.reportSettings(`Missing @${targetTagName}`);
|
||||
}
|
||||
|
||||
if (preventDuplicates && state.hasDuplicates[tagName]) {
|
||||
utils.reportSettings(
|
||||
`Duplicate @${targetTagName}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (initialCommentsOnly &&
|
||||
state.hasNonCommentBeforeTag[tagName]
|
||||
) {
|
||||
utils.reportSettings(
|
||||
`@${targetTagName} should be at the beginning of the file`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Checks that all files have one `@file`, `@fileoverview`, or `@overview` tag at the beginning of the file.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-file-overview.md#repos-sticky-header',
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
tags: {
|
||||
patternProperties: {
|
||||
'.*': {
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
initialCommentsOnly: {
|
||||
type: 'boolean',
|
||||
},
|
||||
mustExist: {
|
||||
type: 'boolean',
|
||||
},
|
||||
preventDuplicates: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
nonComment ({
|
||||
state,
|
||||
node,
|
||||
}) {
|
||||
if (!state.hasNonComment) {
|
||||
state.hasNonComment = node.range[0];
|
||||
}
|
||||
},
|
||||
});
|
||||
178
node_modules/eslint-plugin-jsdoc/src/rules/requireHyphenBeforeParamDescription.js
generated
vendored
Normal file
178
node_modules/eslint-plugin-jsdoc/src/rules/requireHyphenBeforeParamDescription.js
generated
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
export default iterateJsdoc(({
|
||||
sourceCode,
|
||||
utils,
|
||||
report,
|
||||
context,
|
||||
jsdoc,
|
||||
jsdocNode,
|
||||
}) => {
|
||||
const [
|
||||
mainCircumstance,
|
||||
{
|
||||
tags = null,
|
||||
} = {},
|
||||
] = context.options;
|
||||
|
||||
const tgs = /**
|
||||
* @type {null|"any"|{[key: string]: "always"|"never"}}
|
||||
*/ (tags);
|
||||
|
||||
/**
|
||||
* @param {import('@es-joy/jsdoccomment').JsdocTagWithInline} jsdocTag
|
||||
* @param {string} targetTagName
|
||||
* @param {"always"|"never"} [circumstance]
|
||||
* @returns {void}
|
||||
*/
|
||||
const checkHyphens = (jsdocTag, targetTagName, circumstance = mainCircumstance) => {
|
||||
const always = !circumstance || circumstance === 'always';
|
||||
const desc = /** @type {string} */ (utils.getTagDescription(jsdocTag));
|
||||
if (!desc.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const startsWithHyphen = (/^\s*-/u).test(desc);
|
||||
if (always) {
|
||||
if (!startsWithHyphen) {
|
||||
report(`There must be a hyphen before @${targetTagName} description.`, (fixer) => {
|
||||
const lineIndex = /** @type {import('../iterateJsdoc.js').Integer} */ (
|
||||
jsdocTag.line
|
||||
);
|
||||
const sourceLines = sourceCode.getText(jsdocNode).split('\n');
|
||||
|
||||
// Get start index of description, accounting for multi-line descriptions
|
||||
const description = desc.split('\n')[0];
|
||||
const descriptionIndex = sourceLines[lineIndex].lastIndexOf(description);
|
||||
|
||||
const replacementLine = sourceLines[lineIndex]
|
||||
.slice(0, descriptionIndex) + '- ' + description;
|
||||
sourceLines.splice(lineIndex, 1, replacementLine);
|
||||
const replacement = sourceLines.join('\n');
|
||||
|
||||
return fixer.replaceText(jsdocNode, replacement);
|
||||
}, jsdocTag);
|
||||
}
|
||||
} else if (startsWithHyphen) {
|
||||
let lines = 0;
|
||||
for (const {
|
||||
tokens,
|
||||
} of jsdocTag.source) {
|
||||
if (tokens.description) {
|
||||
break;
|
||||
}
|
||||
|
||||
lines++;
|
||||
}
|
||||
|
||||
utils.reportJSDoc(
|
||||
`There must be no hyphen before @${targetTagName} description.`,
|
||||
{
|
||||
line: jsdocTag.source[0].number + lines,
|
||||
},
|
||||
() => {
|
||||
for (const {
|
||||
tokens,
|
||||
} of jsdocTag.source) {
|
||||
if (tokens.description) {
|
||||
tokens.description = tokens.description.replace(
|
||||
/^\s*-\s*/u, '',
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
true,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
utils.forEachPreferredTag('param', checkHyphens);
|
||||
if (tgs) {
|
||||
const tagEntries = Object.entries(tgs);
|
||||
for (const [
|
||||
tagName,
|
||||
circumstance,
|
||||
] of tagEntries) {
|
||||
if (tagName === '*') {
|
||||
const preferredParamTag = utils.getPreferredTagName({
|
||||
tagName: 'param',
|
||||
});
|
||||
for (const {
|
||||
tag,
|
||||
} of jsdoc.tags) {
|
||||
if (tag === preferredParamTag || tagEntries.some(([
|
||||
tagNme,
|
||||
]) => {
|
||||
return tagNme !== '*' && tagNme === tag;
|
||||
})) {
|
||||
continue;
|
||||
}
|
||||
|
||||
utils.forEachPreferredTag(tag, (jsdocTag, targetTagName) => {
|
||||
checkHyphens(
|
||||
jsdocTag,
|
||||
targetTagName,
|
||||
/** @type {"always"|"never"} */ (circumstance),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
utils.forEachPreferredTag(tagName, (jsdocTag, targetTagName) => {
|
||||
checkHyphens(
|
||||
jsdocTag,
|
||||
targetTagName,
|
||||
/** @type {"always"|"never"} */ (circumstance),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}, {
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Requires a hyphen before the `@param` description.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-hyphen-before-param-description.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [
|
||||
{
|
||||
enum: [
|
||||
'always', 'never',
|
||||
],
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
tags: {
|
||||
anyOf: [
|
||||
{
|
||||
patternProperties: {
|
||||
'.*': {
|
||||
enum: [
|
||||
'always', 'never',
|
||||
],
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
{
|
||||
enum: [
|
||||
'any',
|
||||
],
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'layout',
|
||||
},
|
||||
});
|
||||
645
node_modules/eslint-plugin-jsdoc/src/rules/requireJsdoc.js
generated
vendored
Normal file
645
node_modules/eslint-plugin-jsdoc/src/rules/requireJsdoc.js
generated
vendored
Normal file
@@ -0,0 +1,645 @@
|
||||
import exportParser from '../exportParser.js';
|
||||
import {
|
||||
getSettings,
|
||||
} from '../iterateJsdoc.js';
|
||||
import {
|
||||
exemptSpeciaMethods,
|
||||
isConstructor,
|
||||
getFunctionParameterNames,
|
||||
hasReturnValue,
|
||||
getIndent,
|
||||
getContextObject,
|
||||
enforcedContexts,
|
||||
} from '../jsdocUtils.js';
|
||||
import {
|
||||
getDecorator,
|
||||
getJSDocComment,
|
||||
getReducedASTNode,
|
||||
} from '@es-joy/jsdoccomment';
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* ancestorsOnly: boolean,
|
||||
* esm: boolean,
|
||||
* initModuleExports: boolean,
|
||||
* initWindow: boolean
|
||||
* }} RequireJsdocOpts
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('eslint').Rule.Node|
|
||||
* import('@typescript-eslint/types').TSESTree.Node} ESLintOrTSNode
|
||||
*/
|
||||
|
||||
/** @type {import('json-schema').JSONSchema4} */
|
||||
const OPTIONS_SCHEMA = {
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
checkConstructors: {
|
||||
default: true,
|
||||
type: 'boolean',
|
||||
},
|
||||
checkGetters: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'boolean',
|
||||
},
|
||||
{
|
||||
enum: [
|
||||
'no-setter',
|
||||
],
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
default: true,
|
||||
},
|
||||
checkSetters: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'boolean',
|
||||
},
|
||||
{
|
||||
enum: [
|
||||
'no-getter',
|
||||
],
|
||||
type: 'string',
|
||||
},
|
||||
],
|
||||
default: true,
|
||||
},
|
||||
contexts: {
|
||||
items: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
context: {
|
||||
type: 'string',
|
||||
},
|
||||
inlineCommentBlock: {
|
||||
type: 'boolean',
|
||||
},
|
||||
minLineCount: {
|
||||
type: 'integer',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
enableFixer: {
|
||||
default: true,
|
||||
type: 'boolean',
|
||||
},
|
||||
exemptEmptyConstructors: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
exemptEmptyFunctions: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
fixerMessage: {
|
||||
default: '',
|
||||
type: 'string',
|
||||
},
|
||||
minLineCount: {
|
||||
type: 'integer',
|
||||
},
|
||||
publicOnly: {
|
||||
oneOf: [
|
||||
{
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
default: {},
|
||||
properties: {
|
||||
ancestorsOnly: {
|
||||
type: 'boolean',
|
||||
},
|
||||
cjs: {
|
||||
type: 'boolean',
|
||||
},
|
||||
esm: {
|
||||
type: 'boolean',
|
||||
},
|
||||
window: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
},
|
||||
require: {
|
||||
additionalProperties: false,
|
||||
default: {},
|
||||
properties: {
|
||||
ArrowFunctionExpression: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
ClassDeclaration: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
ClassExpression: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
FunctionDeclaration: {
|
||||
default: true,
|
||||
type: 'boolean',
|
||||
},
|
||||
FunctionExpression: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
MethodDefinition: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('eslint').Rule.RuleContext} context
|
||||
* @param {import('json-schema').JSONSchema4Object} baseObject
|
||||
* @param {string} option
|
||||
* @param {string} key
|
||||
* @returns {boolean|undefined}
|
||||
*/
|
||||
const getOption = (context, baseObject, option, key) => {
|
||||
if (context.options[0] && option in context.options[0] &&
|
||||
// Todo: boolean shouldn't be returning property, but
|
||||
// tests currently require
|
||||
(typeof context.options[0][option] === 'boolean' ||
|
||||
key in context.options[0][option])
|
||||
) {
|
||||
return context.options[0][option][key];
|
||||
}
|
||||
|
||||
return /** @type {{[key: string]: {default?: boolean|undefined}}} */ (
|
||||
baseObject.properties
|
||||
)[key].default;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('eslint').Rule.RuleContext} context
|
||||
* @param {import('../iterateJsdoc.js').Settings} settings
|
||||
* @returns {{
|
||||
* contexts: (string|{
|
||||
* context: string,
|
||||
* inlineCommentBlock: boolean,
|
||||
* minLineCount: import('../iterateJsdoc.js').Integer
|
||||
* })[],
|
||||
* enableFixer: boolean,
|
||||
* exemptEmptyConstructors: boolean,
|
||||
* exemptEmptyFunctions: boolean,
|
||||
* fixerMessage: string,
|
||||
* minLineCount: undefined|import('../iterateJsdoc.js').Integer,
|
||||
* publicOnly: boolean|{[key: string]: boolean|undefined}
|
||||
* require: {[key: string]: boolean|undefined}
|
||||
* }}
|
||||
*/
|
||||
const getOptions = (context, settings) => {
|
||||
const {
|
||||
publicOnly,
|
||||
contexts = settings.contexts || [],
|
||||
exemptEmptyConstructors = true,
|
||||
exemptEmptyFunctions = false,
|
||||
enableFixer = true,
|
||||
fixerMessage = '',
|
||||
minLineCount = undefined,
|
||||
} = context.options[0] || {};
|
||||
|
||||
return {
|
||||
contexts,
|
||||
enableFixer,
|
||||
exemptEmptyConstructors,
|
||||
exemptEmptyFunctions,
|
||||
fixerMessage,
|
||||
minLineCount,
|
||||
publicOnly: ((baseObj) => {
|
||||
if (!publicOnly) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @type {{[key: string]: boolean|undefined}} */
|
||||
const properties = {};
|
||||
for (const prop of Object.keys(
|
||||
/** @type {import('json-schema').JSONSchema4Object} */ (
|
||||
/** @type {import('json-schema').JSONSchema4Object} */ (
|
||||
baseObj
|
||||
).properties),
|
||||
)) {
|
||||
const opt = getOption(
|
||||
context,
|
||||
/** @type {import('json-schema').JSONSchema4Object} */ (baseObj),
|
||||
'publicOnly',
|
||||
prop,
|
||||
);
|
||||
|
||||
properties[prop] = opt;
|
||||
}
|
||||
|
||||
return properties;
|
||||
})(
|
||||
/** @type {import('json-schema').JSONSchema4Object} */
|
||||
(
|
||||
/** @type {import('json-schema').JSONSchema4Object} */
|
||||
(
|
||||
/** @type {import('json-schema').JSONSchema4Object} */
|
||||
(
|
||||
OPTIONS_SCHEMA.properties
|
||||
).publicOnly
|
||||
).oneOf
|
||||
)[1],
|
||||
),
|
||||
require: ((baseObj) => {
|
||||
/** @type {{[key: string]: boolean|undefined}} */
|
||||
const properties = {};
|
||||
for (const prop of Object.keys(
|
||||
/** @type {import('json-schema').JSONSchema4Object} */ (
|
||||
/** @type {import('json-schema').JSONSchema4Object} */ (
|
||||
baseObj
|
||||
).properties),
|
||||
)) {
|
||||
const opt = getOption(
|
||||
context,
|
||||
/** @type {import('json-schema').JSONSchema4Object} */
|
||||
(baseObj),
|
||||
'require',
|
||||
prop,
|
||||
);
|
||||
properties[prop] = opt;
|
||||
}
|
||||
|
||||
return properties;
|
||||
})(
|
||||
/** @type {import('json-schema').JSONSchema4Object} */
|
||||
(OPTIONS_SCHEMA.properties).require,
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
/** @type {import('eslint').Rule.RuleModule} */
|
||||
export default {
|
||||
create (context) {
|
||||
/* c8 ignore next -- Fallback to deprecated method */
|
||||
const {
|
||||
sourceCode = context.getSourceCode(),
|
||||
} = context;
|
||||
const settings = getSettings(context);
|
||||
if (!settings) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const opts = getOptions(context, settings);
|
||||
|
||||
const {
|
||||
require: requireOption,
|
||||
contexts,
|
||||
exemptEmptyFunctions,
|
||||
exemptEmptyConstructors,
|
||||
enableFixer,
|
||||
fixerMessage,
|
||||
minLineCount,
|
||||
} = opts;
|
||||
|
||||
const publicOnly =
|
||||
|
||||
/**
|
||||
* @type {{
|
||||
* [key: string]: boolean | undefined;
|
||||
* }}
|
||||
*/ (
|
||||
opts.publicOnly
|
||||
);
|
||||
|
||||
/**
|
||||
* @type {import('../iterateJsdoc.js').CheckJsdoc}
|
||||
*/
|
||||
const checkJsDoc = (info, _handler, node) => {
|
||||
if (
|
||||
// Optimize
|
||||
minLineCount !== undefined || contexts.some((ctxt) => {
|
||||
if (typeof ctxt === 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const {
|
||||
minLineCount: count,
|
||||
} = ctxt;
|
||||
return count !== undefined;
|
||||
})
|
||||
) {
|
||||
/**
|
||||
* @param {undefined|import('../iterateJsdoc.js').Integer} count
|
||||
*/
|
||||
const underMinLine = (count) => {
|
||||
return count !== undefined && count >
|
||||
(sourceCode.getText(node).match(/\n/gu)?.length ?? 0) + 1;
|
||||
};
|
||||
|
||||
if (underMinLine(minLineCount)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
minLineCount: contextMinLineCount,
|
||||
} =
|
||||
/**
|
||||
* @type {{
|
||||
* context: string;
|
||||
* inlineCommentBlock: boolean;
|
||||
* minLineCount: number;
|
||||
* }}
|
||||
*/ (contexts.find((ctxt) => {
|
||||
if (typeof ctxt === 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const {
|
||||
context: ctx,
|
||||
} = ctxt;
|
||||
return ctx === (info.selector || node.type);
|
||||
})) || {};
|
||||
if (underMinLine(contextMinLineCount)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const jsDocNode = getJSDocComment(sourceCode, node, settings);
|
||||
|
||||
if (jsDocNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For those who have options configured against ANY constructors (or
|
||||
// setters or getters) being reported
|
||||
if (exemptSpeciaMethods(
|
||||
{
|
||||
description: '',
|
||||
inlineTags: [],
|
||||
problems: [],
|
||||
source: [],
|
||||
tags: [],
|
||||
},
|
||||
node,
|
||||
context,
|
||||
[
|
||||
OPTIONS_SCHEMA,
|
||||
],
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
// Avoid reporting param-less, return-less functions (when
|
||||
// `exemptEmptyFunctions` option is set)
|
||||
exemptEmptyFunctions && info.isFunctionContext ||
|
||||
|
||||
// Avoid reporting param-less, return-less constructor methods (when
|
||||
// `exemptEmptyConstructors` option is set)
|
||||
exemptEmptyConstructors && isConstructor(node)
|
||||
) {
|
||||
const functionParameterNames = getFunctionParameterNames(node);
|
||||
if (!functionParameterNames.length && !hasReturnValue(node)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const fix = /** @type {import('eslint').Rule.ReportFixer} */ (fixer) => {
|
||||
// Default to one line break if the `minLines`/`maxLines` settings allow
|
||||
const lines = settings.minLines === 0 && settings.maxLines >= 1 ? 1 : settings.minLines;
|
||||
/** @type {ESLintOrTSNode|import('@typescript-eslint/types').TSESTree.Decorator} */
|
||||
let baseNode = getReducedASTNode(node, sourceCode);
|
||||
|
||||
const decorator = getDecorator(
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(baseNode)
|
||||
);
|
||||
if (decorator) {
|
||||
baseNode = decorator;
|
||||
}
|
||||
|
||||
const indent = getIndent({
|
||||
text: sourceCode.getText(
|
||||
/** @type {import('eslint').Rule.Node} */ (baseNode),
|
||||
/** @type {import('eslint').AST.SourceLocation} */
|
||||
(
|
||||
/** @type {import('eslint').Rule.Node} */ (baseNode).loc
|
||||
).start.column,
|
||||
),
|
||||
});
|
||||
|
||||
const {
|
||||
inlineCommentBlock,
|
||||
} =
|
||||
/**
|
||||
* @type {{
|
||||
* context: string,
|
||||
* inlineCommentBlock: boolean,
|
||||
* minLineCount: import('../iterateJsdoc.js').Integer
|
||||
* }}
|
||||
*/ (contexts.find((contxt) => {
|
||||
if (typeof contxt === 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const {
|
||||
context: ctxt,
|
||||
} = contxt;
|
||||
return ctxt === node.type;
|
||||
})) || {};
|
||||
const insertion = (inlineCommentBlock ?
|
||||
`/** ${fixerMessage}` :
|
||||
`/**\n${indent}*${fixerMessage}\n${indent}`) +
|
||||
`*/${'\n'.repeat(lines)}${indent.slice(0, -1)}`;
|
||||
|
||||
return fixer.insertTextBefore(
|
||||
/** @type {import('eslint').Rule.Node} */
|
||||
(baseNode),
|
||||
insertion,
|
||||
);
|
||||
};
|
||||
|
||||
const report = () => {
|
||||
const {
|
||||
start,
|
||||
} = /** @type {import('eslint').AST.SourceLocation} */ (node.loc);
|
||||
const loc = {
|
||||
end: {
|
||||
column: 0,
|
||||
line: start.line + 1,
|
||||
},
|
||||
start,
|
||||
};
|
||||
context.report({
|
||||
fix: enableFixer ? fix : null,
|
||||
loc,
|
||||
messageId: 'missingJsDoc',
|
||||
node,
|
||||
});
|
||||
};
|
||||
|
||||
if (publicOnly) {
|
||||
/** @type {RequireJsdocOpts} */
|
||||
const opt = {
|
||||
ancestorsOnly: Boolean(publicOnly?.ancestorsOnly ?? false),
|
||||
esm: Boolean(publicOnly?.esm ?? true),
|
||||
initModuleExports: Boolean(publicOnly?.cjs ?? true),
|
||||
initWindow: Boolean(publicOnly?.window ?? false),
|
||||
};
|
||||
const exported = exportParser.isUncommentedExport(node, sourceCode, opt, settings);
|
||||
|
||||
if (exported) {
|
||||
report();
|
||||
}
|
||||
} else {
|
||||
report();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} prop
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const hasOption = (prop) => {
|
||||
return requireOption[prop] || contexts.some((ctxt) => {
|
||||
return typeof ctxt === 'object' ? ctxt.context === prop : ctxt === prop;
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
...getContextObject(
|
||||
enforcedContexts(context, [], settings),
|
||||
checkJsDoc,
|
||||
),
|
||||
ArrowFunctionExpression (node) {
|
||||
if (!hasOption('ArrowFunctionExpression')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
[
|
||||
'VariableDeclarator', 'AssignmentExpression', 'ExportDefaultDeclaration',
|
||||
].includes(node.parent.type) ||
|
||||
[
|
||||
'Property', 'ObjectProperty', 'ClassProperty', 'PropertyDefinition',
|
||||
].includes(node.parent.type) &&
|
||||
node ===
|
||||
/**
|
||||
* @type {import('@typescript-eslint/types').TSESTree.Property|
|
||||
* import('@typescript-eslint/types').TSESTree.PropertyDefinition
|
||||
* }
|
||||
*/
|
||||
(node.parent).value
|
||||
) {
|
||||
checkJsDoc({
|
||||
isFunctionContext: true,
|
||||
}, null, node);
|
||||
}
|
||||
},
|
||||
|
||||
ClassDeclaration (node) {
|
||||
if (!hasOption('ClassDeclaration')) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkJsDoc({
|
||||
isFunctionContext: false,
|
||||
}, null, node);
|
||||
},
|
||||
|
||||
ClassExpression (node) {
|
||||
if (!hasOption('ClassExpression')) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkJsDoc({
|
||||
isFunctionContext: false,
|
||||
}, null, node);
|
||||
},
|
||||
|
||||
FunctionDeclaration (node) {
|
||||
if (!hasOption('FunctionDeclaration')) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkJsDoc({
|
||||
isFunctionContext: true,
|
||||
}, null, node);
|
||||
},
|
||||
|
||||
FunctionExpression (node) {
|
||||
if (!hasOption('FunctionExpression')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
[
|
||||
'VariableDeclarator', 'AssignmentExpression', 'ExportDefaultDeclaration',
|
||||
].includes(node.parent.type) ||
|
||||
[
|
||||
'Property', 'ObjectProperty', 'ClassProperty', 'PropertyDefinition',
|
||||
].includes(node.parent.type) &&
|
||||
node ===
|
||||
/**
|
||||
* @type {import('@typescript-eslint/types').TSESTree.Property|
|
||||
* import('@typescript-eslint/types').TSESTree.PropertyDefinition
|
||||
* }
|
||||
*/
|
||||
(node.parent).value
|
||||
) {
|
||||
checkJsDoc({
|
||||
isFunctionContext: true,
|
||||
}, null, node);
|
||||
}
|
||||
},
|
||||
|
||||
MethodDefinition (node) {
|
||||
if (!hasOption('MethodDefinition')) {
|
||||
return;
|
||||
}
|
||||
|
||||
checkJsDoc({
|
||||
isFunctionContext: true,
|
||||
selector: 'MethodDefinition',
|
||||
}, null, /** @type {import('eslint').Rule.Node} */ (node.value));
|
||||
},
|
||||
};
|
||||
},
|
||||
meta: {
|
||||
docs: {
|
||||
category: 'Stylistic Issues',
|
||||
description: 'Require JSDoc comments',
|
||||
recommended: true,
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-jsdoc.md#repos-sticky-header',
|
||||
},
|
||||
|
||||
fixable: 'code',
|
||||
|
||||
messages: {
|
||||
missingJsDoc: 'Missing JSDoc comment.',
|
||||
},
|
||||
|
||||
schema: [
|
||||
OPTIONS_SCHEMA,
|
||||
],
|
||||
|
||||
type: 'suggestion',
|
||||
},
|
||||
};
|
||||
594
node_modules/eslint-plugin-jsdoc/src/rules/requireParam.js
generated
vendored
Normal file
594
node_modules/eslint-plugin-jsdoc/src/rules/requireParam.js
generated
vendored
Normal file
@@ -0,0 +1,594 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
/**
|
||||
* @typedef {[string, boolean, () => RootNamerReturn]} RootNamerReturn
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {string[]} desiredRoots
|
||||
* @param {number} currentIndex
|
||||
* @returns {RootNamerReturn}
|
||||
*/
|
||||
const rootNamer = (desiredRoots, currentIndex) => {
|
||||
/** @type {string} */
|
||||
let name;
|
||||
let idx = currentIndex;
|
||||
const incremented = desiredRoots.length <= 1;
|
||||
if (incremented) {
|
||||
const base = desiredRoots[0];
|
||||
const suffix = idx++;
|
||||
name = `${base}${suffix}`;
|
||||
} else {
|
||||
name = /** @type {string} */ (desiredRoots.shift());
|
||||
}
|
||||
|
||||
return [
|
||||
name,
|
||||
incremented,
|
||||
() => {
|
||||
return rootNamer(desiredRoots, idx);
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
/* eslint-disable complexity -- Temporary */
|
||||
export default iterateJsdoc(({
|
||||
jsdoc,
|
||||
utils,
|
||||
context,
|
||||
}) => {
|
||||
/* eslint-enable complexity -- Temporary */
|
||||
if (utils.avoidDocs()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Param type is specified by type in @type
|
||||
if (utils.hasTag('type')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
autoIncrementBase = 0,
|
||||
checkRestProperty = false,
|
||||
checkDestructured = true,
|
||||
checkDestructuredRoots = true,
|
||||
checkTypesPattern = '/^(?:[oO]bject|[aA]rray|PlainObject|Generic(?:Object|Array))$/',
|
||||
enableFixer = true,
|
||||
enableRootFixer = true,
|
||||
enableRestElementFixer = true,
|
||||
unnamedRootBase = [
|
||||
'root',
|
||||
],
|
||||
useDefaultObjectProperties = false,
|
||||
} = context.options[0] || {};
|
||||
|
||||
const preferredTagName = /** @type {string} */ (utils.getPreferredTagName({
|
||||
tagName: 'param',
|
||||
}));
|
||||
if (!preferredTagName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const functionParameterNames = utils.getFunctionParameterNames(useDefaultObjectProperties);
|
||||
if (!functionParameterNames.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const jsdocParameterNames =
|
||||
/**
|
||||
* @type {{
|
||||
* idx: import('../iterateJsdoc.js').Integer;
|
||||
* name: string;
|
||||
* type: string;
|
||||
* }[]}
|
||||
*/ (utils.getJsdocTagsDeep(preferredTagName));
|
||||
|
||||
const shallowJsdocParameterNames = jsdocParameterNames.filter((tag) => {
|
||||
return !tag.name.includes('.');
|
||||
}).map((tag, idx) => {
|
||||
return {
|
||||
...tag,
|
||||
idx,
|
||||
};
|
||||
});
|
||||
|
||||
const checkTypesRegex = utils.getRegexFromString(checkTypesPattern);
|
||||
|
||||
/**
|
||||
* @type {{
|
||||
* functionParameterIdx: import('../iterateJsdoc.js').Integer,
|
||||
* functionParameterName: string,
|
||||
* inc: boolean|undefined,
|
||||
* remove?: true,
|
||||
* type?: string|undefined
|
||||
* }[]}
|
||||
*/
|
||||
const missingTags = [];
|
||||
const flattenedRoots = utils.flattenRoots(functionParameterNames).names;
|
||||
|
||||
/**
|
||||
* @type {{
|
||||
* [key: string]: import('../iterateJsdoc.js').Integer
|
||||
* }}
|
||||
*/
|
||||
const paramIndex = {};
|
||||
|
||||
/**
|
||||
* @param {string} cur
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const hasParamIndex = (cur) => {
|
||||
return utils.dropPathSegmentQuotes(String(cur)) in paramIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string|number|undefined} cur
|
||||
* @returns {import('../iterateJsdoc.js').Integer}
|
||||
*/
|
||||
const getParamIndex = (cur) => {
|
||||
return paramIndex[utils.dropPathSegmentQuotes(String(cur))];
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} cur
|
||||
* @param {import('../iterateJsdoc.js').Integer} idx
|
||||
* @returns {void}
|
||||
*/
|
||||
const setParamIndex = (cur, idx) => {
|
||||
paramIndex[utils.dropPathSegmentQuotes(String(cur))] = idx;
|
||||
};
|
||||
|
||||
for (const [
|
||||
idx,
|
||||
cur,
|
||||
] of flattenedRoots.entries()) {
|
||||
setParamIndex(cur, idx);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {(import('@es-joy/jsdoccomment').JsdocTagWithInline & {
|
||||
* newAdd?: boolean
|
||||
* })[]} jsdocTags
|
||||
* @param {import('../iterateJsdoc.js').Integer} indexAtFunctionParams
|
||||
* @returns {import('../iterateJsdoc.js').Integer}
|
||||
*/
|
||||
const findExpectedIndex = (jsdocTags, indexAtFunctionParams) => {
|
||||
const remainingRoots = functionParameterNames.slice(indexAtFunctionParams || 0);
|
||||
const foundIndex = jsdocTags.findIndex(({
|
||||
name,
|
||||
newAdd,
|
||||
}) => {
|
||||
return !newAdd && remainingRoots.some((remainingRoot) => {
|
||||
if (Array.isArray(remainingRoot)) {
|
||||
return (
|
||||
/**
|
||||
* @type {import('../jsdocUtils.js').FlattendRootInfo & {
|
||||
* annotationParamName?: string|undefined;
|
||||
* }}
|
||||
*/ (remainingRoot[1]).names.includes(name)
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof remainingRoot === 'object') {
|
||||
return name === remainingRoot.name;
|
||||
}
|
||||
|
||||
return name === remainingRoot;
|
||||
});
|
||||
});
|
||||
|
||||
const tags = foundIndex > -1 ?
|
||||
jsdocTags.slice(0, foundIndex) :
|
||||
jsdocTags.filter(({
|
||||
tag,
|
||||
}) => {
|
||||
return tag === preferredTagName;
|
||||
});
|
||||
|
||||
let tagLineCount = 0;
|
||||
for (const {
|
||||
source,
|
||||
} of tags) {
|
||||
for (const {
|
||||
tokens: {
|
||||
end,
|
||||
},
|
||||
} of source) {
|
||||
if (!end) {
|
||||
tagLineCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tagLineCount;
|
||||
};
|
||||
|
||||
let [
|
||||
nextRootName,
|
||||
incremented,
|
||||
namer,
|
||||
] = rootNamer([
|
||||
...unnamedRootBase,
|
||||
], autoIncrementBase);
|
||||
|
||||
const thisOffset = functionParameterNames[0] === 'this' ? 1 : 0;
|
||||
|
||||
for (const [
|
||||
functionParameterIdx,
|
||||
functionParameterName,
|
||||
] of functionParameterNames.entries()) {
|
||||
|
||||
let inc;
|
||||
if (Array.isArray(functionParameterName)) {
|
||||
const matchedJsdoc = shallowJsdocParameterNames[functionParameterIdx - thisOffset];
|
||||
|
||||
/** @type {string} */
|
||||
let rootName;
|
||||
if (functionParameterName[0]) {
|
||||
rootName = functionParameterName[0];
|
||||
} else if (matchedJsdoc && matchedJsdoc.name) {
|
||||
rootName = matchedJsdoc.name;
|
||||
if (matchedJsdoc.type && matchedJsdoc.type.search(checkTypesRegex) === -1) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
rootName = nextRootName;
|
||||
inc = incremented;
|
||||
}
|
||||
[
|
||||
nextRootName,
|
||||
incremented,
|
||||
namer,
|
||||
] = namer();
|
||||
|
||||
const {
|
||||
hasRestElement,
|
||||
hasPropertyRest,
|
||||
rests,
|
||||
names,
|
||||
} = /**
|
||||
* @type {import('../jsdocUtils.js').FlattendRootInfo & {
|
||||
* annotationParamName?: string | undefined;
|
||||
* }}
|
||||
*/ (functionParameterName[1]);
|
||||
const notCheckingNames = [];
|
||||
if (!enableRestElementFixer && hasRestElement) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!checkDestructuredRoots) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [
|
||||
idx,
|
||||
paramName,
|
||||
] of names.entries()) {
|
||||
// Add root if the root name is not in the docs (and is not already
|
||||
// in the tags to be fixed)
|
||||
if (!jsdocParameterNames.find(({
|
||||
name,
|
||||
}) => {
|
||||
return name === rootName;
|
||||
}) && !missingTags.find(({
|
||||
functionParameterName: fpn,
|
||||
}) => {
|
||||
return fpn === rootName;
|
||||
})) {
|
||||
const emptyParamIdx = jsdocParameterNames.findIndex(({
|
||||
name,
|
||||
}) => {
|
||||
return !name;
|
||||
});
|
||||
|
||||
if (emptyParamIdx > -1) {
|
||||
missingTags.push({
|
||||
functionParameterIdx: emptyParamIdx,
|
||||
functionParameterName: rootName,
|
||||
inc,
|
||||
remove: true,
|
||||
});
|
||||
} else {
|
||||
missingTags.push({
|
||||
functionParameterIdx: hasParamIndex(rootName) ?
|
||||
getParamIndex(rootName) :
|
||||
getParamIndex(paramName),
|
||||
functionParameterName: rootName,
|
||||
inc,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!checkDestructured) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!checkRestProperty && rests[idx]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const fullParamName = `${rootName}.${paramName}`;
|
||||
|
||||
const notCheckingName = jsdocParameterNames.find(({
|
||||
name,
|
||||
type: paramType,
|
||||
}) => {
|
||||
return utils.comparePaths(name)(fullParamName) && paramType.search(checkTypesRegex) === -1 && paramType !== '';
|
||||
});
|
||||
|
||||
if (notCheckingName !== undefined) {
|
||||
notCheckingNames.push(notCheckingName.name);
|
||||
}
|
||||
|
||||
if (notCheckingNames.find((name) => {
|
||||
return fullParamName.startsWith(name);
|
||||
})) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (jsdocParameterNames && !jsdocParameterNames.find(({
|
||||
name,
|
||||
}) => {
|
||||
return utils.comparePaths(name)(fullParamName);
|
||||
})) {
|
||||
missingTags.push({
|
||||
functionParameterIdx: getParamIndex(
|
||||
functionParameterName[0] ? fullParamName : paramName,
|
||||
),
|
||||
functionParameterName: fullParamName,
|
||||
inc,
|
||||
type: hasRestElement && !hasPropertyRest ? '{...any}' : undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @type {string} */
|
||||
let funcParamName;
|
||||
let type;
|
||||
if (typeof functionParameterName === 'object') {
|
||||
if (!enableRestElementFixer && functionParameterName.restElement) {
|
||||
continue;
|
||||
}
|
||||
|
||||
funcParamName = /** @type {string} */ (functionParameterName.name);
|
||||
type = '{...any}';
|
||||
} else {
|
||||
funcParamName = /** @type {string} */ (functionParameterName);
|
||||
}
|
||||
|
||||
if (jsdocParameterNames && !jsdocParameterNames.find(({
|
||||
name,
|
||||
}) => {
|
||||
return name === funcParamName;
|
||||
}) && funcParamName !== 'this') {
|
||||
missingTags.push({
|
||||
functionParameterIdx: getParamIndex(funcParamName),
|
||||
functionParameterName: funcParamName,
|
||||
inc,
|
||||
type,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {{
|
||||
* functionParameterIdx: import('../iterateJsdoc.js').Integer,
|
||||
* functionParameterName: string,
|
||||
* remove?: true,
|
||||
* inc?: boolean,
|
||||
* type?: string
|
||||
* }} cfg
|
||||
*/
|
||||
const fix = ({
|
||||
functionParameterIdx,
|
||||
functionParameterName,
|
||||
remove,
|
||||
inc,
|
||||
type,
|
||||
}) => {
|
||||
if (inc && !enableRootFixer) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('../iterateJsdoc.js').Integer} tagIndex
|
||||
* @param {import('../iterateJsdoc.js').Integer} sourceIndex
|
||||
* @param {import('../iterateJsdoc.js').Integer} spliceCount
|
||||
* @returns {void}
|
||||
*/
|
||||
const createTokens = (tagIndex, sourceIndex, spliceCount) => {
|
||||
// console.log(sourceIndex, tagIndex, jsdoc.tags, jsdoc.source);
|
||||
const tokens = {
|
||||
number: sourceIndex + 1,
|
||||
source: '',
|
||||
tokens: {
|
||||
delimiter: '*',
|
||||
description: '',
|
||||
end: '',
|
||||
lineEnd: '',
|
||||
name: functionParameterName,
|
||||
newAdd: true,
|
||||
postDelimiter: ' ',
|
||||
postName: '',
|
||||
postTag: ' ',
|
||||
postType: type ? ' ' : '',
|
||||
start: jsdoc.source[sourceIndex].tokens.start,
|
||||
tag: `@${preferredTagName}`,
|
||||
type: type ?? '',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {(import('@es-joy/jsdoccomment').JsdocTagWithInline & {
|
||||
* newAdd?: true
|
||||
* })[]}
|
||||
*/ (jsdoc.tags).splice(tagIndex, spliceCount, {
|
||||
description: '',
|
||||
inlineTags: [],
|
||||
name: functionParameterName,
|
||||
newAdd: true,
|
||||
optional: false,
|
||||
problems: [],
|
||||
source: [
|
||||
tokens,
|
||||
],
|
||||
tag: preferredTagName,
|
||||
type: type ?? '',
|
||||
});
|
||||
const firstNumber = jsdoc.source[0].number;
|
||||
jsdoc.source.splice(sourceIndex, spliceCount, tokens);
|
||||
for (const [
|
||||
idx,
|
||||
src,
|
||||
] of jsdoc.source.slice(sourceIndex).entries()) {
|
||||
src.number = firstNumber + sourceIndex + idx;
|
||||
}
|
||||
};
|
||||
|
||||
const offset = jsdoc.source.findIndex(({
|
||||
tokens: {
|
||||
tag,
|
||||
end,
|
||||
},
|
||||
}) => {
|
||||
return tag || end;
|
||||
});
|
||||
if (remove) {
|
||||
createTokens(functionParameterIdx, offset + functionParameterIdx, 1);
|
||||
} else {
|
||||
const expectedIdx = findExpectedIndex(jsdoc.tags, functionParameterIdx);
|
||||
createTokens(expectedIdx, offset + expectedIdx, 0);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {void}
|
||||
*/
|
||||
const fixer = () => {
|
||||
for (const missingTag of missingTags) {
|
||||
fix(missingTag);
|
||||
}
|
||||
};
|
||||
|
||||
if (missingTags.length && jsdoc.source.length === 1) {
|
||||
utils.makeMultiline();
|
||||
}
|
||||
|
||||
for (const {
|
||||
functionParameterName,
|
||||
} of missingTags) {
|
||||
utils.reportJSDoc(
|
||||
`Missing JSDoc @${preferredTagName} "${functionParameterName}" declaration.`,
|
||||
null,
|
||||
enableFixer ? fixer : null,
|
||||
);
|
||||
}
|
||||
}, {
|
||||
contextDefaults: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Requires that all function parameters are documented.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-param.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
autoIncrementBase: {
|
||||
default: 0,
|
||||
type: 'integer',
|
||||
},
|
||||
checkConstructors: {
|
||||
default: true,
|
||||
type: 'boolean',
|
||||
},
|
||||
checkDestructured: {
|
||||
default: true,
|
||||
type: 'boolean',
|
||||
},
|
||||
checkDestructuredRoots: {
|
||||
default: true,
|
||||
type: 'boolean',
|
||||
},
|
||||
checkGetters: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
checkRestProperty: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
checkSetters: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
checkTypesPattern: {
|
||||
type: 'string',
|
||||
},
|
||||
contexts: {
|
||||
items: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
comment: {
|
||||
type: 'string',
|
||||
},
|
||||
context: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
enableFixer: {
|
||||
type: 'boolean',
|
||||
},
|
||||
enableRestElementFixer: {
|
||||
type: 'boolean',
|
||||
},
|
||||
enableRootFixer: {
|
||||
type: 'boolean',
|
||||
},
|
||||
exemptedBy: {
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
unnamedRootBase: {
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
useDefaultObjectProperties: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
|
||||
// We cannot cache comment nodes as the contexts may recur with the
|
||||
// same comment node but a different JS node, and we may need the different
|
||||
// JS node to ensure we iterate its context
|
||||
noTracking: true,
|
||||
});
|
||||
89
node_modules/eslint-plugin-jsdoc/src/rules/requireParamDescription.js
generated
vendored
Normal file
89
node_modules/eslint-plugin-jsdoc/src/rules/requireParamDescription.js
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
export default iterateJsdoc(({
|
||||
context,
|
||||
report,
|
||||
settings,
|
||||
utils,
|
||||
}) => {
|
||||
const {
|
||||
defaultDestructuredRootDescription = 'The root object',
|
||||
setDefaultDestructuredRootDescription = false,
|
||||
} = context.options[0] || {};
|
||||
|
||||
const functionParameterNames = utils.getFunctionParameterNames();
|
||||
|
||||
let rootCount = -1;
|
||||
utils.forEachPreferredTag('param', (jsdocParameter, targetTagName) => {
|
||||
rootCount += jsdocParameter.name.includes('.') ? 0 : 1;
|
||||
if (!jsdocParameter.description.trim()) {
|
||||
if (Array.isArray(functionParameterNames[rootCount])) {
|
||||
if (settings.exemptDestructuredRootsFromChecks) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (setDefaultDestructuredRootDescription) {
|
||||
utils.reportJSDoc(`Missing root description for @${targetTagName}.`, jsdocParameter, () => {
|
||||
utils.changeTag(jsdocParameter, {
|
||||
description: defaultDestructuredRootDescription,
|
||||
postName: ' ',
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
report(
|
||||
`Missing JSDoc @${targetTagName} "${jsdocParameter.name}" description.`,
|
||||
null,
|
||||
jsdocParameter,
|
||||
);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
contextDefaults: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Requires that each `@param` tag has a `description` value.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-param-description.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
contexts: {
|
||||
items: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
comment: {
|
||||
type: 'string',
|
||||
},
|
||||
context: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
defaultDestructuredRootDescription: {
|
||||
type: 'string',
|
||||
},
|
||||
setDefaultDestructuredRootDescription: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
55
node_modules/eslint-plugin-jsdoc/src/rules/requireParamName.js
generated
vendored
Normal file
55
node_modules/eslint-plugin-jsdoc/src/rules/requireParamName.js
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
export default iterateJsdoc(({
|
||||
report,
|
||||
utils,
|
||||
}) => {
|
||||
utils.forEachPreferredTag('param', (jsdocParameter, targetTagName) => {
|
||||
if (jsdocParameter.tag && jsdocParameter.name === '') {
|
||||
report(
|
||||
`There must be an identifier after @${targetTagName} ${jsdocParameter.type === '' ? 'type' : 'tag'}.`,
|
||||
null,
|
||||
jsdocParameter,
|
||||
);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
contextDefaults: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Requires that all function parameters have names.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-param-name.md#repos-sticky-header',
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
contexts: {
|
||||
items: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
comment: {
|
||||
type: 'string',
|
||||
},
|
||||
context: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
89
node_modules/eslint-plugin-jsdoc/src/rules/requireParamType.js
generated
vendored
Normal file
89
node_modules/eslint-plugin-jsdoc/src/rules/requireParamType.js
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
export default iterateJsdoc(({
|
||||
context,
|
||||
report,
|
||||
settings,
|
||||
utils,
|
||||
}) => {
|
||||
const {
|
||||
defaultDestructuredRootType = 'object',
|
||||
setDefaultDestructuredRootType = false,
|
||||
} = context.options[0] || {};
|
||||
|
||||
const functionParameterNames = utils.getFunctionParameterNames();
|
||||
|
||||
let rootCount = -1;
|
||||
utils.forEachPreferredTag('param', (jsdocParameter, targetTagName) => {
|
||||
rootCount += jsdocParameter.name.includes('.') ? 0 : 1;
|
||||
if (!jsdocParameter.type) {
|
||||
if (Array.isArray(functionParameterNames[rootCount])) {
|
||||
if (settings.exemptDestructuredRootsFromChecks) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (setDefaultDestructuredRootType) {
|
||||
utils.reportJSDoc(`Missing root type for @${targetTagName}.`, jsdocParameter, () => {
|
||||
utils.changeTag(jsdocParameter, {
|
||||
postType: ' ',
|
||||
type: `{${defaultDestructuredRootType}}`,
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
report(
|
||||
`Missing JSDoc @${targetTagName} "${jsdocParameter.name}" type.`,
|
||||
null,
|
||||
jsdocParameter,
|
||||
);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
contextDefaults: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Requires that each `@param` tag has a `type` value.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-param-type.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
contexts: {
|
||||
items: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
comment: {
|
||||
type: 'string',
|
||||
},
|
||||
context: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
defaultDestructuredRootType: {
|
||||
type: 'string',
|
||||
},
|
||||
setDefaultDestructuredRootType: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
48
node_modules/eslint-plugin-jsdoc/src/rules/requireProperty.js
generated
vendored
Normal file
48
node_modules/eslint-plugin-jsdoc/src/rules/requireProperty.js
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
export default iterateJsdoc(({
|
||||
utils,
|
||||
}) => {
|
||||
const propertyAssociatedTags = utils.filterTags(({
|
||||
tag,
|
||||
}) => {
|
||||
return [
|
||||
'typedef', 'namespace',
|
||||
].includes(tag);
|
||||
});
|
||||
if (!propertyAssociatedTags.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetTagName = /** @type {string} */ (utils.getPreferredTagName({
|
||||
tagName: 'property',
|
||||
}));
|
||||
|
||||
if (utils.hasATag([
|
||||
targetTagName,
|
||||
])) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const propertyAssociatedTag of propertyAssociatedTags) {
|
||||
if (![
|
||||
'object', 'Object', 'PlainObject',
|
||||
].includes(propertyAssociatedTag.type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
utils.reportJSDoc(`Missing JSDoc @${targetTagName}.`, null, () => {
|
||||
utils.addTag(targetTagName);
|
||||
});
|
||||
}
|
||||
}, {
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Requires that all `@typedef` and `@namespace` tags have `@property` when their type is a plain `object`, `Object`, or `PlainObject`.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-property.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'code',
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
25
node_modules/eslint-plugin-jsdoc/src/rules/requirePropertyDescription.js
generated
vendored
Normal file
25
node_modules/eslint-plugin-jsdoc/src/rules/requirePropertyDescription.js
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
export default iterateJsdoc(({
|
||||
report,
|
||||
utils,
|
||||
}) => {
|
||||
utils.forEachPreferredTag('property', (jsdoc, targetTagName) => {
|
||||
if (!jsdoc.description.trim()) {
|
||||
report(
|
||||
`Missing JSDoc @${targetTagName} "${jsdoc.name}" description.`,
|
||||
null,
|
||||
jsdoc,
|
||||
);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Requires that each `@property` tag has a `description` value.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-property-description.md#repos-sticky-header',
|
||||
},
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
25
node_modules/eslint-plugin-jsdoc/src/rules/requirePropertyName.js
generated
vendored
Normal file
25
node_modules/eslint-plugin-jsdoc/src/rules/requirePropertyName.js
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
export default iterateJsdoc(({
|
||||
report,
|
||||
utils,
|
||||
}) => {
|
||||
utils.forEachPreferredTag('property', (jsdoc, targetTagName) => {
|
||||
if (jsdoc.tag && jsdoc.name === '') {
|
||||
report(
|
||||
`There must be an identifier after @${targetTagName} ${jsdoc.type === '' ? 'type' : 'tag'}.`,
|
||||
null,
|
||||
jsdoc,
|
||||
);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Requires that all function `@property` tags have names.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-property-name.md#repos-sticky-header',
|
||||
},
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
25
node_modules/eslint-plugin-jsdoc/src/rules/requirePropertyType.js
generated
vendored
Normal file
25
node_modules/eslint-plugin-jsdoc/src/rules/requirePropertyType.js
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
export default iterateJsdoc(({
|
||||
report,
|
||||
utils,
|
||||
}) => {
|
||||
utils.forEachPreferredTag('property', (jsdoc, targetTagName) => {
|
||||
if (!jsdoc.type) {
|
||||
report(
|
||||
`Missing JSDoc @${targetTagName} "${jsdoc.name}" type.`,
|
||||
null,
|
||||
jsdoc,
|
||||
);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Requires that each `@property` tag has a `type` value.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-property-type.md#repos-sticky-header',
|
||||
},
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
238
node_modules/eslint-plugin-jsdoc/src/rules/requireReturns.js
generated
vendored
Normal file
238
node_modules/eslint-plugin-jsdoc/src/rules/requireReturns.js
generated
vendored
Normal file
@@ -0,0 +1,238 @@
|
||||
import exportParser from '../exportParser.js';
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
/**
|
||||
* We can skip checking for a return value, in case the documentation is inherited
|
||||
* or the method is either a constructor or an abstract method.
|
||||
*
|
||||
* In either of these cases the return value is optional or not defined.
|
||||
* @param {import('../iterateJsdoc.js').Utils} utils
|
||||
* a reference to the utils which are used to probe if a tag is present or not.
|
||||
* @returns {boolean}
|
||||
* true in case deep checking can be skipped; otherwise false.
|
||||
*/
|
||||
const canSkip = (utils) => {
|
||||
return utils.hasATag([
|
||||
// inheritdoc implies that all documentation is inherited
|
||||
// see https://jsdoc.app/tags-inheritdoc.html
|
||||
//
|
||||
// Abstract methods are by definition incomplete,
|
||||
// so it is not an error if it declares a return value but does not implement it.
|
||||
'abstract',
|
||||
'virtual',
|
||||
|
||||
// Constructors do not have a return value by definition (https://jsdoc.app/tags-class.html)
|
||||
// So we can bail out here, too.
|
||||
'class',
|
||||
'constructor',
|
||||
|
||||
// Return type is specified by type in @type
|
||||
'type',
|
||||
|
||||
// This seems to imply a class as well
|
||||
'interface',
|
||||
]) ||
|
||||
utils.avoidDocs();
|
||||
};
|
||||
|
||||
export default iterateJsdoc(({
|
||||
info: {
|
||||
comment,
|
||||
},
|
||||
node,
|
||||
report,
|
||||
settings,
|
||||
utils,
|
||||
context,
|
||||
}) => {
|
||||
const {
|
||||
contexts,
|
||||
enableFixer = false,
|
||||
forceRequireReturn = false,
|
||||
forceReturnsWithAsync = false,
|
||||
publicOnly = false,
|
||||
} = context.options[0] || {};
|
||||
|
||||
// A preflight check. We do not need to run a deep check
|
||||
// in case the @returns comment is optional or undefined.
|
||||
if (canSkip(utils)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @type {boolean|undefined} */
|
||||
let forceRequireReturnContext;
|
||||
if (contexts) {
|
||||
const {
|
||||
foundContext,
|
||||
} = utils.findContext(contexts, comment);
|
||||
if (typeof foundContext === 'object') {
|
||||
forceRequireReturnContext = foundContext.forceRequireReturn;
|
||||
}
|
||||
}
|
||||
|
||||
const tagName = /** @type {string} */ (utils.getPreferredTagName({
|
||||
tagName: 'returns',
|
||||
}));
|
||||
if (!tagName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tags = utils.getTags(tagName);
|
||||
|
||||
if (tags.length > 1) {
|
||||
report(`Found more than one @${tagName} declaration.`);
|
||||
}
|
||||
|
||||
const iteratingFunction = utils.isIteratingFunction();
|
||||
|
||||
// In case the code returns something, we expect a return value in JSDoc.
|
||||
const [
|
||||
tag,
|
||||
] = tags;
|
||||
const missingReturnTag = typeof tag === 'undefined' || tag === null;
|
||||
|
||||
const shouldReport = () => {
|
||||
if (!missingReturnTag) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (publicOnly) {
|
||||
/** @type {import('./requireJsdoc.js').RequireJsdocOpts} */
|
||||
const opt = {
|
||||
ancestorsOnly: Boolean(publicOnly?.ancestorsOnly ?? false),
|
||||
esm: Boolean(publicOnly?.esm ?? true),
|
||||
initModuleExports: Boolean(publicOnly?.cjs ?? true),
|
||||
initWindow: Boolean(publicOnly?.window ?? false),
|
||||
};
|
||||
/* c8 ignore next -- Fallback to deprecated method */
|
||||
const {
|
||||
sourceCode = context.getSourceCode(),
|
||||
} = context;
|
||||
const exported = exportParser.isUncommentedExport(
|
||||
/** @type {import('eslint').Rule.Node} */ (node), sourceCode, opt, settings,
|
||||
);
|
||||
|
||||
if (!exported) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ((forceRequireReturn || forceRequireReturnContext) && (
|
||||
iteratingFunction || utils.isVirtualFunction()
|
||||
)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const isAsync = !iteratingFunction && utils.hasTag('async') ||
|
||||
iteratingFunction && utils.isAsync();
|
||||
|
||||
if (forceReturnsWithAsync && isAsync) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return iteratingFunction && utils.hasValueOrExecutorHasNonEmptyResolveValue(
|
||||
forceReturnsWithAsync,
|
||||
);
|
||||
};
|
||||
|
||||
if (shouldReport()) {
|
||||
utils.reportJSDoc(`Missing JSDoc @${tagName} declaration.`, null, enableFixer ? () => {
|
||||
utils.addTag(tagName);
|
||||
} : null);
|
||||
}
|
||||
}, {
|
||||
contextDefaults: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Requires that returns are documented.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-returns.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
checkConstructors: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
checkGetters: {
|
||||
default: true,
|
||||
type: 'boolean',
|
||||
},
|
||||
contexts: {
|
||||
items: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
comment: {
|
||||
type: 'string',
|
||||
},
|
||||
context: {
|
||||
type: 'string',
|
||||
},
|
||||
forceRequireReturn: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
enableFixer: {
|
||||
type: 'boolean',
|
||||
},
|
||||
exemptedBy: {
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
forceRequireReturn: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
forceReturnsWithAsync: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
publicOnly: {
|
||||
oneOf: [
|
||||
{
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
default: {},
|
||||
properties: {
|
||||
ancestorsOnly: {
|
||||
type: 'boolean',
|
||||
},
|
||||
cjs: {
|
||||
type: 'boolean',
|
||||
},
|
||||
esm: {
|
||||
type: 'boolean',
|
||||
},
|
||||
window: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
145
node_modules/eslint-plugin-jsdoc/src/rules/requireReturnsCheck.js
generated
vendored
Executable file
145
node_modules/eslint-plugin-jsdoc/src/rules/requireReturnsCheck.js
generated
vendored
Executable file
@@ -0,0 +1,145 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
/**
|
||||
* @param {import('../iterateJsdoc.js').Utils} utils
|
||||
* @param {import('../iterateJsdoc.js').Settings} settings
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const canSkip = (utils, settings) => {
|
||||
const voidingTags = [
|
||||
// An abstract function is by definition incomplete
|
||||
// so it is perfectly fine if a return is documented but
|
||||
// not present within the function.
|
||||
// A subclass may inherit the doc and implement the
|
||||
// missing return.
|
||||
'abstract',
|
||||
'virtual',
|
||||
|
||||
// A constructor function returns `this` by default, so may be `@returns`
|
||||
// tag indicating this but no explicit return
|
||||
'class',
|
||||
'constructor',
|
||||
'interface',
|
||||
];
|
||||
|
||||
if (settings.mode === 'closure') {
|
||||
// Structural Interface in GCC terms, equivalent to @interface tag as far as this rule is concerned
|
||||
voidingTags.push('record');
|
||||
}
|
||||
|
||||
return utils.hasATag(voidingTags) ||
|
||||
utils.isConstructor() ||
|
||||
utils.classHasTag('interface') ||
|
||||
settings.mode === 'closure' && utils.classHasTag('record');
|
||||
};
|
||||
|
||||
// eslint-disable-next-line complexity -- Temporary
|
||||
export default iterateJsdoc(({
|
||||
context,
|
||||
node,
|
||||
report,
|
||||
settings,
|
||||
utils,
|
||||
}) => {
|
||||
const {
|
||||
exemptAsync = true,
|
||||
exemptGenerators = settings.mode === 'typescript',
|
||||
reportMissingReturnForUndefinedTypes = false,
|
||||
} = context.options[0] || {};
|
||||
|
||||
if (canSkip(utils, settings)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (exemptAsync && utils.isAsync()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tagName = /** @type {string} */ (utils.getPreferredTagName({
|
||||
tagName: 'returns',
|
||||
}));
|
||||
if (!tagName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tags = utils.getTags(tagName);
|
||||
|
||||
if (tags.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tags.length > 1) {
|
||||
report(`Found more than one @${tagName} declaration.`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const [
|
||||
tag,
|
||||
] = tags;
|
||||
|
||||
const type = tag.type.trim();
|
||||
|
||||
// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#assertion-functions
|
||||
if (/asserts\s/u.test(type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const returnNever = type === 'never';
|
||||
|
||||
if (returnNever && utils.hasValueOrExecutorHasNonEmptyResolveValue(false)) {
|
||||
report(`JSDoc @${tagName} declaration set with "never" but return expression is present in function.`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// In case a return value is declared in JSDoc, we also expect one in the code.
|
||||
if (
|
||||
!returnNever &&
|
||||
(
|
||||
reportMissingReturnForUndefinedTypes ||
|
||||
!utils.mayBeUndefinedTypeTag(tag)
|
||||
) &&
|
||||
(tag.type === '' && !utils.hasValueOrExecutorHasNonEmptyResolveValue(
|
||||
exemptAsync,
|
||||
) ||
|
||||
tag.type !== '' && !utils.hasValueOrExecutorHasNonEmptyResolveValue(
|
||||
exemptAsync,
|
||||
true,
|
||||
)) &&
|
||||
Boolean(
|
||||
!exemptGenerators || !node ||
|
||||
!('generator' in /** @type {import('../iterateJsdoc.js').Node} */ (node)) ||
|
||||
!(/** @type {import('@typescript-eslint/types').TSESTree.FunctionDeclaration} */ (node)).generator
|
||||
)
|
||||
) {
|
||||
report(`JSDoc @${tagName} declaration present but return expression not available in function.`);
|
||||
}
|
||||
}, {
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Requires a return statement in function body if a `@returns` tag is specified in jsdoc comment.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-returns-check.md#repos-sticky-header',
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
exemptAsync: {
|
||||
default: true,
|
||||
type: 'boolean',
|
||||
},
|
||||
exemptGenerators: {
|
||||
type: 'boolean',
|
||||
},
|
||||
reportMissingReturnForUndefinedTypes: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
59
node_modules/eslint-plugin-jsdoc/src/rules/requireReturnsDescription.js
generated
vendored
Normal file
59
node_modules/eslint-plugin-jsdoc/src/rules/requireReturnsDescription.js
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
export default iterateJsdoc(({
|
||||
report,
|
||||
utils,
|
||||
}) => {
|
||||
utils.forEachPreferredTag('returns', (jsdocTag, targetTagName) => {
|
||||
const type = jsdocTag.type && jsdocTag.type.trim();
|
||||
|
||||
if ([
|
||||
'void', 'undefined', 'Promise<void>', 'Promise<undefined>',
|
||||
].includes(type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!jsdocTag.description.trim()) {
|
||||
report(`Missing JSDoc @${targetTagName} description.`, null, jsdocTag);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
contextDefaults: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Requires that the `@returns` tag has a `description` value.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-returns-description.md#repos-sticky-header',
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
contexts: {
|
||||
items: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
comment: {
|
||||
type: 'string',
|
||||
},
|
||||
context: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
51
node_modules/eslint-plugin-jsdoc/src/rules/requireReturnsType.js
generated
vendored
Normal file
51
node_modules/eslint-plugin-jsdoc/src/rules/requireReturnsType.js
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
export default iterateJsdoc(({
|
||||
report,
|
||||
utils,
|
||||
}) => {
|
||||
utils.forEachPreferredTag('returns', (jsdocTag, targetTagName) => {
|
||||
if (!jsdocTag.type) {
|
||||
report(`Missing JSDoc @${targetTagName} type.`, null, jsdocTag);
|
||||
}
|
||||
});
|
||||
}, {
|
||||
contextDefaults: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Requires that `@returns` tag has `type` value.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-returns-type.md#repos-sticky-header',
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
contexts: {
|
||||
items: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
comment: {
|
||||
type: 'string',
|
||||
},
|
||||
context: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
180
node_modules/eslint-plugin-jsdoc/src/rules/requireTemplate.js
generated
vendored
Normal file
180
node_modules/eslint-plugin-jsdoc/src/rules/requireTemplate.js
generated
vendored
Normal file
@@ -0,0 +1,180 @@
|
||||
import {
|
||||
parse as parseType,
|
||||
traverse,
|
||||
tryParse as tryParseType,
|
||||
} from '@es-joy/jsdoccomment';
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
export default iterateJsdoc(({
|
||||
context,
|
||||
utils,
|
||||
node,
|
||||
settings,
|
||||
report,
|
||||
}) => {
|
||||
const {
|
||||
requireSeparateTemplates = false,
|
||||
} = context.options[0] || {};
|
||||
|
||||
const {
|
||||
mode
|
||||
} = settings;
|
||||
|
||||
const usedNames = new Set();
|
||||
const templateTags = utils.getTags('template');
|
||||
const templateNames = templateTags.flatMap(({name}) => {
|
||||
return name.split(/,\s*/);
|
||||
});
|
||||
|
||||
for (const tag of templateTags) {
|
||||
const {name} = tag;
|
||||
const names = name.split(/,\s*/);
|
||||
if (requireSeparateTemplates && names.length > 1) {
|
||||
report(`Missing separate @template for ${names[1]}`, null, tag);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('@typescript-eslint/types').TSESTree.FunctionDeclaration|
|
||||
* import('@typescript-eslint/types').TSESTree.ClassDeclaration|
|
||||
* import('@typescript-eslint/types').TSESTree.TSInterfaceDeclaration|
|
||||
* import('@typescript-eslint/types').TSESTree.TSTypeAliasDeclaration} aliasDeclaration
|
||||
*/
|
||||
const checkTypeParams = (aliasDeclaration) => {
|
||||
/* c8 ignore next -- Guard */
|
||||
const {params} = aliasDeclaration.typeParameters ?? {params: []};
|
||||
for (const {name: {name}} of params) {
|
||||
usedNames.add(name);
|
||||
}
|
||||
for (const usedName of usedNames) {
|
||||
if (!templateNames.includes(usedName)) {
|
||||
report(`Missing @template ${usedName}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleTypes = () => {
|
||||
const nde = /** @type {import('@typescript-eslint/types').TSESTree.Node} */ (
|
||||
node
|
||||
);
|
||||
if (!nde) {
|
||||
return;
|
||||
}
|
||||
switch (nde.type) {
|
||||
case 'ExportDefaultDeclaration':
|
||||
switch (nde.declaration?.type) {
|
||||
case 'ClassDeclaration':
|
||||
case 'FunctionDeclaration':
|
||||
case 'TSInterfaceDeclaration':
|
||||
checkTypeParams(nde.declaration);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'ExportNamedDeclaration':
|
||||
switch (nde.declaration?.type) {
|
||||
case 'ClassDeclaration':
|
||||
case 'FunctionDeclaration':
|
||||
case 'TSTypeAliasDeclaration':
|
||||
case 'TSInterfaceDeclaration':
|
||||
checkTypeParams(nde.declaration);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'ClassDeclaration':
|
||||
case 'FunctionDeclaration':
|
||||
case 'TSTypeAliasDeclaration':
|
||||
case 'TSInterfaceDeclaration':
|
||||
checkTypeParams(nde);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const usedNameToTag = new Map();
|
||||
|
||||
/**
|
||||
* @param {import('comment-parser').Spec} potentialTag
|
||||
*/
|
||||
const checkForUsedTypes = (potentialTag) => {
|
||||
let parsedType;
|
||||
try {
|
||||
parsedType = mode === 'permissive' ?
|
||||
tryParseType(/** @type {string} */ (potentialTag.type)) :
|
||||
parseType(/** @type {string} */ (potentialTag.type), mode)
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
traverse(parsedType, (nde) => {
|
||||
const {
|
||||
type,
|
||||
value,
|
||||
} = /** @type {import('jsdoc-type-pratt-parser').NameResult} */ (nde);
|
||||
if (type === 'JsdocTypeName' && (/^[A-Z]$/).test(value)) {
|
||||
usedNames.add(value);
|
||||
if (!usedNameToTag.has(value)) {
|
||||
usedNameToTag.set(value, potentialTag);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string[]} tagNames
|
||||
*/
|
||||
const checkTagsAndTemplates = (tagNames) => {
|
||||
for (const tagName of tagNames) {
|
||||
const preferredTagName = /** @type {string} */ (utils.getPreferredTagName({
|
||||
tagName,
|
||||
}));
|
||||
const matchingTags = utils.getTags(preferredTagName);
|
||||
for (const matchingTag of matchingTags) {
|
||||
checkForUsedTypes(matchingTag);
|
||||
}
|
||||
}
|
||||
|
||||
// Could check against whitelist/blacklist
|
||||
for (const usedName of usedNames) {
|
||||
if (!templateNames.includes(usedName)) {
|
||||
report(`Missing @template ${usedName}`, null, usedNameToTag.get(usedName));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const callbackTags = utils.getTags('callback');
|
||||
const functionTags = utils.getTags('function');
|
||||
if (callbackTags.length || functionTags.length) {
|
||||
checkTagsAndTemplates(['param', 'returns']);
|
||||
return;
|
||||
}
|
||||
|
||||
const typedefTags = utils.getTags('typedef');
|
||||
if (!typedefTags.length || typedefTags.length >= 2) {
|
||||
handleTypes();
|
||||
return;
|
||||
}
|
||||
|
||||
const potentialTypedef = typedefTags[0];
|
||||
checkForUsedTypes(potentialTypedef);
|
||||
|
||||
checkTagsAndTemplates(['property']);
|
||||
}, {
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Requires template tags for each generic type parameter',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-template.md#repos-sticky-header',
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
requireSeparateTemplates: {
|
||||
type: 'boolean'
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
111
node_modules/eslint-plugin-jsdoc/src/rules/requireThrows.js
generated
vendored
Normal file
111
node_modules/eslint-plugin-jsdoc/src/rules/requireThrows.js
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
/**
|
||||
* We can skip checking for a throws value, in case the documentation is inherited
|
||||
* or the method is either a constructor or an abstract method.
|
||||
* @param {import('../iterateJsdoc.js').Utils} utils a reference to the utils which are used to probe if a tag is present or not.
|
||||
* @returns {boolean} true in case deep checking can be skipped; otherwise false.
|
||||
*/
|
||||
const canSkip = (utils) => {
|
||||
return utils.hasATag([
|
||||
// inheritdoc implies that all documentation is inherited
|
||||
// see https://jsdoc.app/tags-inheritdoc.html
|
||||
//
|
||||
// Abstract methods are by definition incomplete,
|
||||
// so it is not necessary to document that they throw an error.
|
||||
'abstract',
|
||||
'virtual',
|
||||
|
||||
// The designated type can itself document `@throws`
|
||||
'type',
|
||||
]) ||
|
||||
utils.avoidDocs();
|
||||
};
|
||||
|
||||
export default iterateJsdoc(({
|
||||
report,
|
||||
utils,
|
||||
}) => {
|
||||
// A preflight check. We do not need to run a deep check for abstract
|
||||
// functions.
|
||||
if (canSkip(utils)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tagName = /** @type {string} */ (utils.getPreferredTagName({
|
||||
tagName: 'throws',
|
||||
}));
|
||||
if (!tagName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tags = utils.getTags(tagName);
|
||||
const iteratingFunction = utils.isIteratingFunction();
|
||||
|
||||
// In case the code returns something, we expect a return value in JSDoc.
|
||||
const [
|
||||
tag,
|
||||
] = tags;
|
||||
const missingThrowsTag = typeof tag === 'undefined' || tag === null;
|
||||
|
||||
const shouldReport = () => {
|
||||
if (!missingThrowsTag) {
|
||||
if (tag.type.trim() === 'never' && iteratingFunction && utils.hasThrowValue()) {
|
||||
report(`JSDoc @${tagName} declaration set to "never" but throw value found.`);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return iteratingFunction && utils.hasThrowValue();
|
||||
};
|
||||
|
||||
if (shouldReport()) {
|
||||
report(`Missing JSDoc @${tagName} declaration.`);
|
||||
}
|
||||
}, {
|
||||
contextDefaults: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Requires that throw statements are documented.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-throws.md#repos-sticky-header',
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
contexts: {
|
||||
items: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
comment: {
|
||||
type: 'string',
|
||||
},
|
||||
context: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
exemptedBy: {
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
216
node_modules/eslint-plugin-jsdoc/src/rules/requireYields.js
generated
vendored
Normal file
216
node_modules/eslint-plugin-jsdoc/src/rules/requireYields.js
generated
vendored
Normal file
@@ -0,0 +1,216 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
/**
|
||||
* We can skip checking for a yield value, in case the documentation is inherited
|
||||
* or the method has a constructor or abstract tag.
|
||||
*
|
||||
* In either of these cases the yield value is optional or not defined.
|
||||
* @param {import('../iterateJsdoc.js').Utils} utils a reference to the utils which are used to probe if a tag is present or not.
|
||||
* @returns {boolean} true in case deep checking can be skipped; otherwise false.
|
||||
*/
|
||||
const canSkip = (utils) => {
|
||||
return utils.hasATag([
|
||||
// inheritdoc implies that all documentation is inherited
|
||||
// see https://jsdoc.app/tags-inheritdoc.html
|
||||
//
|
||||
// Abstract methods are by definition incomplete,
|
||||
// so it is not an error if it declares a yield value but does not implement it.
|
||||
'abstract',
|
||||
'virtual',
|
||||
|
||||
// Constructors do not have a yield value
|
||||
// so we can bail out here, too.
|
||||
'class',
|
||||
'constructor',
|
||||
|
||||
// Yield (and any `next`) type is specified accompanying the targeted
|
||||
// @type
|
||||
'type',
|
||||
|
||||
// This seems to imply a class as well
|
||||
'interface',
|
||||
]) ||
|
||||
utils.avoidDocs();
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('../iterateJsdoc.js').Utils} utils
|
||||
* @param {import('../iterateJsdoc.js').Report} report
|
||||
* @param {string} tagName
|
||||
* @returns {[preferredTagName?: string, missingTag?: boolean]}
|
||||
*/
|
||||
const checkTagName = (utils, report, tagName) => {
|
||||
const preferredTagName = /** @type {string} */ (utils.getPreferredTagName({
|
||||
tagName,
|
||||
}));
|
||||
if (!preferredTagName) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const tags = utils.getTags(preferredTagName);
|
||||
|
||||
if (tags.length > 1) {
|
||||
report(`Found more than one @${preferredTagName} declaration.`);
|
||||
}
|
||||
|
||||
// In case the code yields something, we expect a yields value in JSDoc.
|
||||
const [
|
||||
tag,
|
||||
] = tags;
|
||||
const missingTag = typeof tag === 'undefined' || tag === null;
|
||||
|
||||
return [
|
||||
preferredTagName, missingTag,
|
||||
];
|
||||
};
|
||||
|
||||
export default iterateJsdoc(({
|
||||
report,
|
||||
utils,
|
||||
context,
|
||||
}) => {
|
||||
const {
|
||||
next = false,
|
||||
nextWithGeneratorTag = false,
|
||||
forceRequireNext = false,
|
||||
forceRequireYields = false,
|
||||
withGeneratorTag = true,
|
||||
} = context.options[0] || {};
|
||||
|
||||
// A preflight check. We do not need to run a deep check
|
||||
// in case the @yield comment is optional or undefined.
|
||||
if (canSkip(utils)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const iteratingFunction = utils.isIteratingFunction();
|
||||
|
||||
const [
|
||||
preferredYieldTagName,
|
||||
missingYieldTag,
|
||||
] = checkTagName(
|
||||
utils, report, 'yields',
|
||||
);
|
||||
if (preferredYieldTagName) {
|
||||
const shouldReportYields = () => {
|
||||
if (!missingYieldTag) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
withGeneratorTag && utils.hasTag('generator') ||
|
||||
forceRequireYields && iteratingFunction && utils.isGenerator()
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return iteratingFunction && utils.isGenerator() && utils.hasYieldValue();
|
||||
};
|
||||
|
||||
if (shouldReportYields()) {
|
||||
report(`Missing JSDoc @${preferredYieldTagName} declaration.`);
|
||||
}
|
||||
}
|
||||
|
||||
if (next || nextWithGeneratorTag || forceRequireNext) {
|
||||
const [
|
||||
preferredNextTagName,
|
||||
missingNextTag,
|
||||
] = checkTagName(
|
||||
utils, report, 'next',
|
||||
);
|
||||
if (!preferredNextTagName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const shouldReportNext = () => {
|
||||
if (!missingNextTag) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
nextWithGeneratorTag && utils.hasTag('generator')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
!next && !forceRequireNext ||
|
||||
!iteratingFunction ||
|
||||
!utils.isGenerator()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return forceRequireNext || utils.hasYieldReturnValue();
|
||||
};
|
||||
|
||||
if (shouldReportNext()) {
|
||||
report(`Missing JSDoc @${preferredNextTagName} declaration.`);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
contextDefaults: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Requires yields are documented.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-yields.md#repos-sticky-header',
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
contexts: {
|
||||
items: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
comment: {
|
||||
type: 'string',
|
||||
},
|
||||
context: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
exemptedBy: {
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
forceRequireNext: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
forceRequireYields: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
next: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
nextWithGeneratorTag: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
withGeneratorTag: {
|
||||
default: true,
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
208
node_modules/eslint-plugin-jsdoc/src/rules/requireYieldsCheck.js
generated
vendored
Normal file
208
node_modules/eslint-plugin-jsdoc/src/rules/requireYieldsCheck.js
generated
vendored
Normal file
@@ -0,0 +1,208 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
/**
|
||||
* @param {import('../iterateJsdoc.js').Utils} utils
|
||||
* @param {import('../iterateJsdoc.js').Settings} settings
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const canSkip = (utils, settings) => {
|
||||
const voidingTags = [
|
||||
// An abstract function is by definition incomplete
|
||||
// so it is perfectly fine if a yield is documented but
|
||||
// not present within the function.
|
||||
// A subclass may inherit the doc and implement the
|
||||
// missing yield.
|
||||
'abstract',
|
||||
'virtual',
|
||||
|
||||
// Constructor functions do not have a yield value
|
||||
// so we can bail here, too.
|
||||
'class',
|
||||
'constructor',
|
||||
|
||||
// This seems to imply a class as well
|
||||
'interface',
|
||||
];
|
||||
|
||||
if (settings.mode === 'closure') {
|
||||
// Structural Interface in GCC terms, equivalent to @interface tag as far as this rule is concerned
|
||||
voidingTags.push('record');
|
||||
}
|
||||
|
||||
return utils.hasATag(voidingTags) ||
|
||||
utils.isConstructor() ||
|
||||
utils.classHasTag('interface') ||
|
||||
settings.mode === 'closure' && utils.classHasTag('record');
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('../iterateJsdoc.js').Utils} utils
|
||||
* @param {import('../iterateJsdoc.js').Report} report
|
||||
* @param {string} tagName
|
||||
* @returns {[]|[preferredTagName: string, tag: import('comment-parser').Spec]}
|
||||
*/
|
||||
const checkTagName = (utils, report, tagName) => {
|
||||
const preferredTagName = /** @type {string} */ (utils.getPreferredTagName({
|
||||
tagName,
|
||||
}));
|
||||
if (!preferredTagName) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const tags = utils.getTags(preferredTagName);
|
||||
|
||||
if (tags.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (tags.length > 1) {
|
||||
report(`Found more than one @${preferredTagName} declaration.`);
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
preferredTagName, tags[0],
|
||||
];
|
||||
};
|
||||
|
||||
export default iterateJsdoc(({
|
||||
context,
|
||||
report,
|
||||
settings,
|
||||
utils,
|
||||
}) => {
|
||||
if (canSkip(utils, settings)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
next = false,
|
||||
checkGeneratorsOnly = false,
|
||||
} = context.options[0] || {};
|
||||
|
||||
const [
|
||||
preferredYieldTagName,
|
||||
yieldTag,
|
||||
] = checkTagName(
|
||||
utils, report, 'yields',
|
||||
);
|
||||
if (preferredYieldTagName) {
|
||||
const shouldReportYields = () => {
|
||||
if (
|
||||
/** @type {import('comment-parser').Spec} */ (
|
||||
yieldTag
|
||||
).type.trim() === 'never'
|
||||
) {
|
||||
if (utils.hasYieldValue()) {
|
||||
report(`JSDoc @${preferredYieldTagName} declaration set with "never" but yield expression is present in function.`);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (checkGeneratorsOnly && !utils.isGenerator()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !utils.mayBeUndefinedTypeTag(
|
||||
/** @type {import('comment-parser').Spec} */
|
||||
(yieldTag),
|
||||
) && !utils.hasYieldValue();
|
||||
};
|
||||
|
||||
// In case a yield value is declared in JSDoc, we also expect one in the code.
|
||||
if (shouldReportYields()) {
|
||||
report(`JSDoc @${preferredYieldTagName} declaration present but yield expression not available in function.`);
|
||||
}
|
||||
}
|
||||
|
||||
if (next) {
|
||||
const [
|
||||
preferredNextTagName,
|
||||
nextTag,
|
||||
] = checkTagName(
|
||||
utils, report, 'next',
|
||||
);
|
||||
if (preferredNextTagName) {
|
||||
const shouldReportNext = () => {
|
||||
if (
|
||||
/** @type {import('comment-parser').Spec} */ (
|
||||
nextTag
|
||||
).type.trim() === 'never'
|
||||
) {
|
||||
if (utils.hasYieldReturnValue()) {
|
||||
report(`JSDoc @${preferredNextTagName} declaration set with "never" but yield expression with return value is present in function.`);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (checkGeneratorsOnly && !utils.isGenerator()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !utils.mayBeUndefinedTypeTag(
|
||||
/** @type {import('comment-parser').Spec} */
|
||||
(nextTag),
|
||||
) && !utils.hasYieldReturnValue();
|
||||
};
|
||||
|
||||
if (shouldReportNext()) {
|
||||
report(`JSDoc @${preferredNextTagName} declaration present but yield expression with return value not available in function.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Requires a yield statement in function body if a `@yields` tag is specified in jsdoc comment.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-yields-check.md#repos-sticky-header',
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
checkGeneratorsOnly: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
contexts: {
|
||||
items: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
comment: {
|
||||
type: 'string',
|
||||
},
|
||||
context: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
exemptedBy: {
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
next: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
557
node_modules/eslint-plugin-jsdoc/src/rules/sortTags.js
generated
vendored
Normal file
557
node_modules/eslint-plugin-jsdoc/src/rules/sortTags.js
generated
vendored
Normal file
@@ -0,0 +1,557 @@
|
||||
import defaultTagOrder from '../defaultTagOrder.js';
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
// eslint-disable-next-line complexity -- Temporary
|
||||
export default iterateJsdoc(({
|
||||
context,
|
||||
jsdoc,
|
||||
utils,
|
||||
}) => {
|
||||
const
|
||||
/**
|
||||
* @type {{
|
||||
* linesBetween: import('../iterateJsdoc.js').Integer,
|
||||
* tagSequence: {
|
||||
* tags: string[]
|
||||
* }[],
|
||||
* alphabetizeExtras: boolean,
|
||||
* reportTagGroupSpacing: boolean,
|
||||
* reportIntraTagGroupSpacing: boolean,
|
||||
* }}
|
||||
*/ {
|
||||
linesBetween = 1,
|
||||
tagSequence = defaultTagOrder,
|
||||
alphabetizeExtras = false,
|
||||
reportTagGroupSpacing = true,
|
||||
reportIntraTagGroupSpacing = true,
|
||||
} = context.options[0] || {};
|
||||
|
||||
const tagList = tagSequence.flatMap((obj) => {
|
||||
/* typeof obj === 'string' ? obj : */
|
||||
return obj.tags;
|
||||
});
|
||||
|
||||
const otherPos = tagList.indexOf('-other');
|
||||
const endPos = otherPos > -1 ? otherPos : tagList.length;
|
||||
|
||||
let ongoingCount = 0;
|
||||
for (const [
|
||||
idx,
|
||||
tag,
|
||||
] of
|
||||
/**
|
||||
* @type {(
|
||||
* import('@es-joy/jsdoccomment').JsdocTagWithInline & {
|
||||
* originalIndex: import('../iterateJsdoc.js').Integer,
|
||||
* originalLine: import('../iterateJsdoc.js').Integer,
|
||||
* }
|
||||
* )[]}
|
||||
*/ (jsdoc.tags).entries()) {
|
||||
tag.originalIndex = idx;
|
||||
ongoingCount += tag.source.length;
|
||||
tag.originalLine = ongoingCount;
|
||||
}
|
||||
|
||||
/** @type {import('../iterateJsdoc.js').Integer|undefined} */
|
||||
let firstChangedTagLine;
|
||||
/** @type {import('../iterateJsdoc.js').Integer|undefined} */
|
||||
let firstChangedTagIndex;
|
||||
|
||||
/**
|
||||
* @type {(import('comment-parser').Spec & {
|
||||
* originalIndex: import('../iterateJsdoc.js').Integer,
|
||||
* originalLine: import('../iterateJsdoc.js').Integer,
|
||||
* })[]}
|
||||
*/
|
||||
const sortedTags = JSON.parse(JSON.stringify(jsdoc.tags));
|
||||
sortedTags.sort(({
|
||||
tag: tagNew,
|
||||
}, {
|
||||
originalIndex,
|
||||
originalLine,
|
||||
tag: tagOld,
|
||||
}) => {
|
||||
// Optimize: Just keep relative positions if the same tag name
|
||||
if (tagNew === tagOld) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const checkOrSetFirstChanged = () => {
|
||||
if (!firstChangedTagLine || originalLine < firstChangedTagLine) {
|
||||
firstChangedTagLine = originalLine;
|
||||
firstChangedTagIndex = originalIndex;
|
||||
}
|
||||
};
|
||||
|
||||
const newPos = tagList.indexOf(tagNew);
|
||||
const oldPos = tagList.indexOf(tagOld);
|
||||
|
||||
const preferredNewPos = newPos === -1 ? endPos : newPos;
|
||||
const preferredOldPos = oldPos === -1 ? endPos : oldPos;
|
||||
|
||||
if (preferredNewPos < preferredOldPos) {
|
||||
checkOrSetFirstChanged();
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (preferredNewPos > preferredOldPos) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// preferredNewPos === preferredOldPos
|
||||
if (
|
||||
!alphabetizeExtras ||
|
||||
|
||||
// Optimize: If tagNew (or tagOld which is the same) was found in the
|
||||
// priority array, it can maintain its relative position—without need
|
||||
// of alphabetizing (secondary sorting)
|
||||
newPos >= 0
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (tagNew < tagOld) {
|
||||
checkOrSetFirstChanged();
|
||||
return -1;
|
||||
}
|
||||
|
||||
// tagNew > tagOld
|
||||
return 1;
|
||||
});
|
||||
|
||||
if (firstChangedTagLine === undefined) {
|
||||
// Should be ordered by now
|
||||
|
||||
/**
|
||||
* @type {import('comment-parser').Spec[]}
|
||||
*/
|
||||
const lastTagsOfGroup = [];
|
||||
|
||||
/**
|
||||
* @type {[
|
||||
* import('comment-parser').Spec,
|
||||
* import('../iterateJsdoc.js').Integer
|
||||
* ][]}
|
||||
*/
|
||||
const badLastTagsOfGroup = [];
|
||||
|
||||
/**
|
||||
* @param {import('comment-parser').Spec} tag
|
||||
*/
|
||||
const countTagEmptyLines = (tag) => {
|
||||
return tag.source.reduce((acc, {
|
||||
tokens: {
|
||||
description,
|
||||
name,
|
||||
type,
|
||||
end,
|
||||
tag: tg,
|
||||
},
|
||||
}) => {
|
||||
const empty = !tg && !type && !name && !description;
|
||||
// Reset the count so long as there is content
|
||||
return empty ? acc + Number(empty && !end) : 0;
|
||||
}, 0);
|
||||
};
|
||||
|
||||
let idx = 0;
|
||||
for (const {
|
||||
tags,
|
||||
} of tagSequence) {
|
||||
let innerIdx;
|
||||
/** @type {import('comment-parser').Spec} */
|
||||
let currentTag;
|
||||
/** @type {import('comment-parser').Spec|undefined} */
|
||||
let lastTag;
|
||||
do {
|
||||
currentTag = jsdoc.tags[idx];
|
||||
if (!currentTag) {
|
||||
idx++;
|
||||
break;
|
||||
}
|
||||
|
||||
innerIdx = tags.indexOf(currentTag.tag);
|
||||
|
||||
if (
|
||||
innerIdx === -1 &&
|
||||
// eslint-disable-next-line no-loop-func -- Safe
|
||||
(!tags.includes('-other') || tagSequence.some(({
|
||||
tags: tgs,
|
||||
}) => {
|
||||
return tgs.includes(currentTag.tag);
|
||||
}))
|
||||
) {
|
||||
idx++;
|
||||
break;
|
||||
}
|
||||
|
||||
lastTag = currentTag;
|
||||
|
||||
idx++;
|
||||
} while (true);
|
||||
|
||||
idx--;
|
||||
|
||||
if (lastTag) {
|
||||
lastTagsOfGroup.push(lastTag);
|
||||
const ct = countTagEmptyLines(lastTag);
|
||||
if (
|
||||
ct !== linesBetween &&
|
||||
// Use another rule for adding to end (should be of interest outside this rule)
|
||||
jsdoc.tags[idx]
|
||||
) {
|
||||
badLastTagsOfGroup.push([
|
||||
lastTag, ct,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (reportTagGroupSpacing && badLastTagsOfGroup.length) {
|
||||
/**
|
||||
* @param {import('comment-parser').Spec} tg
|
||||
* @returns {() => void}
|
||||
*/
|
||||
const fixer = (tg) => {
|
||||
return () => {
|
||||
// Due to https://github.com/syavorsky/comment-parser/issues/110 ,
|
||||
// we have to modify `jsdoc.source` rather than just modify tags
|
||||
// directly
|
||||
for (const [
|
||||
currIdx,
|
||||
{
|
||||
tokens,
|
||||
},
|
||||
] of jsdoc.source.entries()) {
|
||||
if (tokens.tag !== '@' + tg.tag) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Cannot be `tokens.end`, as dropped off last tag, so safe to
|
||||
// go on
|
||||
let newIdx = currIdx;
|
||||
|
||||
const emptyLine = () => {
|
||||
return {
|
||||
number: 0,
|
||||
source: '',
|
||||
tokens: utils.seedTokens({
|
||||
delimiter: '*',
|
||||
start: jsdoc.source[newIdx - 1].tokens.start,
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
let existingEmptyLines = 0;
|
||||
while (true) {
|
||||
const nextTokens = jsdoc.source[++newIdx]?.tokens;
|
||||
|
||||
/* c8 ignore next 3 -- Guard */
|
||||
if (!nextTokens) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Should be no `nextTokens.end` to worry about since ignored
|
||||
// if not followed by tag
|
||||
|
||||
if (nextTokens.tag) {
|
||||
// Haven't made it to last tag instance yet, so keep looking
|
||||
if (nextTokens.tag === tokens.tag) {
|
||||
existingEmptyLines = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
const lineDiff = linesBetween - existingEmptyLines;
|
||||
if (lineDiff > 0) {
|
||||
const lines = Array.from({
|
||||
length: lineDiff,
|
||||
}, () => {
|
||||
return emptyLine();
|
||||
});
|
||||
jsdoc.source.splice(newIdx, 0, ...lines);
|
||||
} else {
|
||||
// lineDiff < 0
|
||||
jsdoc.source.splice(
|
||||
newIdx + lineDiff,
|
||||
-lineDiff,
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
const empty = !nextTokens.type && !nextTokens.name &&
|
||||
!nextTokens.description;
|
||||
|
||||
if (empty) {
|
||||
existingEmptyLines++;
|
||||
} else {
|
||||
// Has content again, so reset empty line count
|
||||
existingEmptyLines = 0;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
for (const [
|
||||
srcIdx,
|
||||
src,
|
||||
] of jsdoc.source.entries()) {
|
||||
src.number = srcIdx;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
for (const [
|
||||
tg,
|
||||
] of badLastTagsOfGroup) {
|
||||
utils.reportJSDoc(
|
||||
'Tag groups do not have the expected whitespace',
|
||||
tg,
|
||||
fixer(tg),
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!reportIntraTagGroupSpacing) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [
|
||||
tagIdx,
|
||||
tag,
|
||||
] of jsdoc.tags.entries()) {
|
||||
if (!jsdoc.tags[tagIdx + 1] || lastTagsOfGroup.includes(tag)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const ct = countTagEmptyLines(tag);
|
||||
if (ct) {
|
||||
// eslint-disable-next-line complexity -- Temporary
|
||||
const fixer = () => {
|
||||
let foundFirstTag = false;
|
||||
|
||||
/** @type {string|undefined} */
|
||||
let currentTag;
|
||||
|
||||
for (const [
|
||||
currIdx,
|
||||
{
|
||||
tokens: {
|
||||
description,
|
||||
name,
|
||||
type,
|
||||
end,
|
||||
tag: tg,
|
||||
},
|
||||
},
|
||||
] of jsdoc.source.entries()) {
|
||||
if (tg) {
|
||||
foundFirstTag = true;
|
||||
currentTag = tg;
|
||||
}
|
||||
|
||||
if (!foundFirstTag) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentTag && !tg && !type && !name && !description && !end) {
|
||||
let nextIdx = currIdx;
|
||||
|
||||
let ignore = true;
|
||||
// Even if a tag of the same name as the last tags in a group,
|
||||
// could still be an earlier tag in that group
|
||||
|
||||
// eslint-disable-next-line no-loop-func -- Safe
|
||||
if (lastTagsOfGroup.some((lastTagOfGroup) => {
|
||||
return currentTag === '@' + lastTagOfGroup.tag;
|
||||
})) {
|
||||
while (true) {
|
||||
const nextTokens = jsdoc.source[++nextIdx]?.tokens;
|
||||
if (!nextTokens) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!nextTokens.tag) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Followed by the same tag name, so not actually last in group,
|
||||
// and of interest
|
||||
if (nextTokens.tag === currentTag) {
|
||||
ignore = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
while (true) {
|
||||
const nextTokens = jsdoc.source[++nextIdx]?.tokens;
|
||||
if (!nextTokens || nextTokens.end) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Not the very last tag, so don't ignore
|
||||
if (nextTokens.tag) {
|
||||
ignore = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!ignore) {
|
||||
jsdoc.source.splice(currIdx, 1);
|
||||
for (const [
|
||||
srcIdx,
|
||||
src,
|
||||
] of jsdoc.source.entries()) {
|
||||
src.number = srcIdx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
utils.reportJSDoc(
|
||||
'Intra-group tags have unexpected whitespace',
|
||||
tag,
|
||||
fixer,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const firstLine = utils.getFirstLine();
|
||||
|
||||
const fix = () => {
|
||||
const itemsToMoveRange = [
|
||||
...Array.from({
|
||||
length: jsdoc.tags.length -
|
||||
/** @type {import('../iterateJsdoc.js').Integer} */ (
|
||||
firstChangedTagIndex
|
||||
),
|
||||
}).keys(),
|
||||
];
|
||||
|
||||
const unchangedPriorTagDescriptions = jsdoc.tags.slice(
|
||||
0,
|
||||
firstChangedTagIndex,
|
||||
).reduce((ct, {
|
||||
source,
|
||||
}) => {
|
||||
return ct + source.length - 1;
|
||||
}, 0);
|
||||
|
||||
// This offset includes not only the offset from where the first tag
|
||||
// must begin, and the additional offset of where the first changed
|
||||
// tag begins, but it must also account for prior descriptions
|
||||
const initialOffset = /** @type {import('../iterateJsdoc.js').Integer} */ (
|
||||
firstLine
|
||||
) + /** @type {import('../iterateJsdoc.js').Integer} */ (firstChangedTagIndex) +
|
||||
|
||||
// May be the first tag, so don't try finding a prior one if so
|
||||
unchangedPriorTagDescriptions;
|
||||
|
||||
// Use `firstChangedTagLine` for line number to begin reporting/splicing
|
||||
for (const idx of itemsToMoveRange) {
|
||||
utils.removeTag(
|
||||
idx +
|
||||
/** @type {import('../iterateJsdoc.js').Integer} */ (
|
||||
firstChangedTagIndex
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const changedTags = sortedTags.slice(firstChangedTagIndex);
|
||||
let extraTagCount = 0;
|
||||
|
||||
for (const idx of itemsToMoveRange) {
|
||||
const changedTag = changedTags[idx];
|
||||
|
||||
utils.addTag(
|
||||
changedTag.tag,
|
||||
extraTagCount + initialOffset + idx,
|
||||
{
|
||||
...changedTag.source[0].tokens,
|
||||
|
||||
// `comment-parser` puts the `end` within the `tags` section, so
|
||||
// avoid adding another to jsdoc.source
|
||||
end: '',
|
||||
},
|
||||
);
|
||||
|
||||
for (const {
|
||||
tokens,
|
||||
} of changedTag.source.slice(1)) {
|
||||
if (!tokens.end) {
|
||||
utils.addLine(
|
||||
extraTagCount + initialOffset + idx + 1,
|
||||
{
|
||||
...tokens,
|
||||
end: '',
|
||||
},
|
||||
);
|
||||
extraTagCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
utils.reportJSDoc(
|
||||
`Tags are not in the prescribed order: ${
|
||||
tagList.join(', ') || '(alphabetical)'
|
||||
}`,
|
||||
jsdoc.tags[/** @type {import('../iterateJsdoc.js').Integer} */ (
|
||||
firstChangedTagIndex
|
||||
)],
|
||||
fix,
|
||||
true,
|
||||
);
|
||||
}, {
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Sorts tags by a specified sequence according to tag name.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/sort-tags.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
alphabetizeExtras: {
|
||||
type: 'boolean',
|
||||
},
|
||||
linesBetween: {
|
||||
type: 'integer',
|
||||
},
|
||||
reportIntraTagGroupSpacing: {
|
||||
type: 'boolean',
|
||||
},
|
||||
reportTagGroupSpacing: {
|
||||
type: 'boolean',
|
||||
},
|
||||
tagSequence: {
|
||||
items: {
|
||||
properties: {
|
||||
tags: {
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
359
node_modules/eslint-plugin-jsdoc/src/rules/tagLines.js
generated
vendored
Normal file
359
node_modules/eslint-plugin-jsdoc/src/rules/tagLines.js
generated
vendored
Normal file
@@ -0,0 +1,359 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
export default iterateJsdoc(({
|
||||
context,
|
||||
jsdoc,
|
||||
utils,
|
||||
}) => {
|
||||
const [
|
||||
alwaysNever = 'never',
|
||||
{
|
||||
count = 1,
|
||||
endLines = 0,
|
||||
startLines = 0,
|
||||
applyToEndTag = true,
|
||||
tags = {},
|
||||
} = {},
|
||||
] = context.options;
|
||||
|
||||
// eslint-disable-next-line complexity -- Temporary
|
||||
jsdoc.tags.some((tg, tagIdx) => {
|
||||
let lastTag;
|
||||
|
||||
/**
|
||||
* @type {null|import('../iterateJsdoc.js').Integer}
|
||||
*/
|
||||
let lastEmpty = null;
|
||||
|
||||
/**
|
||||
* @type {null|import('../iterateJsdoc.js').Integer}
|
||||
*/
|
||||
let reportIndex = null;
|
||||
let emptyLinesCount = 0;
|
||||
for (const [
|
||||
idx,
|
||||
{
|
||||
tokens: {
|
||||
tag,
|
||||
name,
|
||||
type,
|
||||
description,
|
||||
end,
|
||||
},
|
||||
},
|
||||
] of tg.source.entries()) {
|
||||
// May be text after a line break within a tag description
|
||||
if (description) {
|
||||
reportIndex = null;
|
||||
}
|
||||
|
||||
if (lastTag && [
|
||||
'any', 'always',
|
||||
].includes(tags[lastTag.slice(1)]?.lines)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const empty = !tag && !name && !type && !description;
|
||||
if (
|
||||
empty && !end &&
|
||||
(alwaysNever === 'never' ||
|
||||
lastTag && tags[lastTag.slice(1)]?.lines === 'never'
|
||||
)
|
||||
) {
|
||||
reportIndex = idx;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!end) {
|
||||
if (empty) {
|
||||
emptyLinesCount++;
|
||||
} else {
|
||||
emptyLinesCount = 0;
|
||||
}
|
||||
|
||||
lastEmpty = empty ? idx : null;
|
||||
}
|
||||
|
||||
lastTag = tag;
|
||||
}
|
||||
|
||||
if (
|
||||
typeof endLines === 'number' &&
|
||||
lastEmpty !== null && tagIdx === jsdoc.tags.length - 1
|
||||
) {
|
||||
const lineDiff = endLines - emptyLinesCount;
|
||||
|
||||
if (lineDiff < 0) {
|
||||
const fixer = () => {
|
||||
utils.removeTag(tagIdx, {
|
||||
tagSourceOffset: /** @type {import('../iterateJsdoc.js').Integer} */ (
|
||||
lastEmpty
|
||||
) + lineDiff + 1,
|
||||
});
|
||||
};
|
||||
|
||||
utils.reportJSDoc(
|
||||
`Expected ${endLines} trailing lines`,
|
||||
{
|
||||
line: tg.source[lastEmpty].number + lineDiff + 1,
|
||||
},
|
||||
fixer,
|
||||
);
|
||||
} else if (lineDiff > 0) {
|
||||
const fixer = () => {
|
||||
utils.addLines(
|
||||
tagIdx,
|
||||
/** @type {import('../iterateJsdoc.js').Integer} */ (lastEmpty),
|
||||
endLines - emptyLinesCount,
|
||||
);
|
||||
};
|
||||
|
||||
utils.reportJSDoc(
|
||||
`Expected ${endLines} trailing lines`,
|
||||
{
|
||||
line: tg.source[lastEmpty].number,
|
||||
},
|
||||
fixer,
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (reportIndex !== null) {
|
||||
const fixer = () => {
|
||||
utils.removeTag(tagIdx, {
|
||||
tagSourceOffset: /** @type {import('../iterateJsdoc.js').Integer} */ (
|
||||
reportIndex
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
utils.reportJSDoc(
|
||||
'Expected no lines between tags',
|
||||
{
|
||||
line: tg.source[0].number + 1,
|
||||
},
|
||||
fixer,
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
(applyToEndTag ? jsdoc.tags : jsdoc.tags.slice(0, -1)).some((tg, tagIdx) => {
|
||||
/**
|
||||
* @type {{
|
||||
* idx: import('../iterateJsdoc.js').Integer,
|
||||
* number: import('../iterateJsdoc.js').Integer
|
||||
* }[]}
|
||||
*/
|
||||
const lines = [];
|
||||
|
||||
let currentTag;
|
||||
let tagSourceIdx = 0;
|
||||
for (const [
|
||||
idx,
|
||||
{
|
||||
number,
|
||||
tokens: {
|
||||
tag,
|
||||
name,
|
||||
type,
|
||||
description,
|
||||
end,
|
||||
},
|
||||
},
|
||||
] of tg.source.entries()) {
|
||||
if (description) {
|
||||
lines.splice(0, lines.length);
|
||||
tagSourceIdx = idx;
|
||||
}
|
||||
|
||||
if (tag) {
|
||||
currentTag = tag;
|
||||
}
|
||||
|
||||
if (!tag && !name && !type && !description && !end) {
|
||||
lines.push({
|
||||
idx,
|
||||
number,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const currentTg = currentTag && tags[currentTag.slice(1)];
|
||||
const tagCount = currentTg?.count;
|
||||
|
||||
const defaultAlways = alwaysNever === 'always' && currentTg?.lines !== 'never' &&
|
||||
currentTg?.lines !== 'any' && lines.length < count;
|
||||
|
||||
let overrideAlways;
|
||||
let fixCount = count;
|
||||
if (!defaultAlways) {
|
||||
fixCount = typeof tagCount === 'number' ? tagCount : count;
|
||||
overrideAlways = currentTg?.lines === 'always' &&
|
||||
lines.length < fixCount;
|
||||
}
|
||||
|
||||
if (defaultAlways || overrideAlways) {
|
||||
const fixer = () => {
|
||||
utils.addLines(tagIdx, lines[lines.length - 1]?.idx || tagSourceIdx + 1, fixCount - lines.length);
|
||||
};
|
||||
|
||||
const line = lines[lines.length - 1]?.number || tg.source[tagSourceIdx].number;
|
||||
utils.reportJSDoc(
|
||||
`Expected ${fixCount} line${fixCount === 1 ? '' : 's'} between tags but found ${lines.length}`,
|
||||
{
|
||||
line,
|
||||
},
|
||||
fixer,
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (typeof startLines === 'number') {
|
||||
if (!jsdoc.tags.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
description,
|
||||
lastDescriptionLine,
|
||||
} = utils.getDescription();
|
||||
if (!(/\S/u).test(description)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const trailingLines = description.match(/\n+$/u)?.[0]?.length;
|
||||
const trailingDiff = (trailingLines ?? 0) - startLines;
|
||||
if (trailingDiff > 0) {
|
||||
utils.reportJSDoc(
|
||||
`Expected only ${startLines} line after block description`,
|
||||
{
|
||||
line: lastDescriptionLine - trailingDiff,
|
||||
},
|
||||
() => {
|
||||
utils.setBlockDescription((info, seedTokens, descLines) => {
|
||||
return descLines.slice(0, -trailingDiff).map((desc) => {
|
||||
return {
|
||||
number: 0,
|
||||
source: '',
|
||||
tokens: seedTokens({
|
||||
...info,
|
||||
description: desc,
|
||||
postDelimiter: desc.trim() ? info.postDelimiter : '',
|
||||
}),
|
||||
};
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
} else if (trailingDiff < 0) {
|
||||
utils.reportJSDoc(
|
||||
`Expected ${startLines} lines after block description`,
|
||||
{
|
||||
line: lastDescriptionLine,
|
||||
},
|
||||
() => {
|
||||
utils.setBlockDescription((info, seedTokens, descLines) => {
|
||||
return [
|
||||
...descLines,
|
||||
...Array.from({
|
||||
length: -trailingDiff,
|
||||
}, () => {
|
||||
return '';
|
||||
}),
|
||||
].map((desc) => {
|
||||
return {
|
||||
number: 0,
|
||||
source: '',
|
||||
tokens: seedTokens({
|
||||
...info,
|
||||
description: desc,
|
||||
postDelimiter: desc.trim() ? info.postDelimiter : '',
|
||||
}),
|
||||
};
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Enforces lines (or no lines) between tags.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/tag-lines.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [
|
||||
{
|
||||
enum: [
|
||||
'always', 'any', 'never',
|
||||
],
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
applyToEndTag: {
|
||||
type: 'boolean',
|
||||
},
|
||||
count: {
|
||||
type: 'integer',
|
||||
},
|
||||
endLines: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'integer',
|
||||
},
|
||||
{
|
||||
type: 'null',
|
||||
},
|
||||
],
|
||||
},
|
||||
startLines: {
|
||||
anyOf: [
|
||||
{
|
||||
type: 'integer',
|
||||
},
|
||||
{
|
||||
type: 'null',
|
||||
},
|
||||
],
|
||||
},
|
||||
tags: {
|
||||
patternProperties: {
|
||||
'.*': {
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
count: {
|
||||
type: 'integer',
|
||||
},
|
||||
lines: {
|
||||
enum: [
|
||||
'always', 'never', 'any',
|
||||
],
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
146
node_modules/eslint-plugin-jsdoc/src/rules/textEscaping.js
generated
vendored
Normal file
146
node_modules/eslint-plugin-jsdoc/src/rules/textEscaping.js
generated
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
|
||||
// We could disallow raw gt, quot, and apos, but allow for parity; but we do
|
||||
// not allow hex or decimal character references
|
||||
const htmlRegex = /(<|&(?!(?:amp|lt|gt|quot|apos);))(?=\S)/u;
|
||||
const markdownRegex = /(?<!\\)(`+)([^`]+)\1(?!`)/u;
|
||||
|
||||
/**
|
||||
* @param {string} desc
|
||||
* @returns {string}
|
||||
*/
|
||||
const htmlReplacer = (desc) => {
|
||||
return desc.replaceAll(new RegExp(htmlRegex, 'gu'), (_) => {
|
||||
if (_ === '<') {
|
||||
return '<';
|
||||
}
|
||||
|
||||
return '&';
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} desc
|
||||
* @returns {string}
|
||||
*/
|
||||
const markdownReplacer = (desc) => {
|
||||
return desc.replaceAll(new RegExp(markdownRegex, 'gu'), (_, backticks, encapsed) => {
|
||||
const bookend = '`'.repeat(backticks.length);
|
||||
return `\\${bookend}${encapsed}${bookend}`;
|
||||
});
|
||||
};
|
||||
|
||||
export default iterateJsdoc(({
|
||||
context,
|
||||
jsdoc,
|
||||
utils,
|
||||
}) => {
|
||||
const {
|
||||
escapeHTML,
|
||||
escapeMarkdown,
|
||||
} = context.options[0] || {};
|
||||
|
||||
if (!escapeHTML && !escapeMarkdown) {
|
||||
context.report({
|
||||
loc: {
|
||||
end: {
|
||||
column: 1,
|
||||
line: 1,
|
||||
},
|
||||
start: {
|
||||
column: 1,
|
||||
line: 1,
|
||||
},
|
||||
},
|
||||
message: 'You must include either `escapeHTML` or `escapeMarkdown`',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
descriptions,
|
||||
} = utils.getDescription();
|
||||
|
||||
if (escapeHTML) {
|
||||
if (descriptions.some((desc) => {
|
||||
return htmlRegex.test(desc);
|
||||
})) {
|
||||
const line = utils.setDescriptionLines(htmlRegex, htmlReplacer);
|
||||
utils.reportJSDoc('You have unescaped HTML characters < or &', {
|
||||
line,
|
||||
}, () => {}, true);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const tag of jsdoc.tags) {
|
||||
if (/** @type {string[]} */ (
|
||||
utils.getTagDescription(tag, true)
|
||||
).some((desc) => {
|
||||
return htmlRegex.test(desc);
|
||||
})) {
|
||||
const line = utils.setTagDescription(tag, htmlRegex, htmlReplacer) +
|
||||
tag.source[0].number;
|
||||
utils.reportJSDoc('You have unescaped HTML characters < or & in a tag', {
|
||||
line,
|
||||
}, () => {}, true);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (descriptions.some((desc) => {
|
||||
return markdownRegex.test(desc);
|
||||
})) {
|
||||
const line = utils.setDescriptionLines(markdownRegex, markdownReplacer);
|
||||
utils.reportJSDoc('You have unescaped Markdown backtick sequences', {
|
||||
line,
|
||||
}, () => {}, true);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const tag of jsdoc.tags) {
|
||||
if (/** @type {string[]} */ (
|
||||
utils.getTagDescription(tag, true)
|
||||
).some((desc) => {
|
||||
return markdownRegex.test(desc);
|
||||
})) {
|
||||
const line = utils.setTagDescription(
|
||||
tag, markdownRegex, markdownReplacer,
|
||||
) + tag.source[0].number;
|
||||
utils.reportJSDoc(
|
||||
'You have unescaped Markdown backtick sequences in a tag',
|
||||
{
|
||||
line,
|
||||
},
|
||||
() => {},
|
||||
true,
|
||||
);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: '',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/text-escaping.md#repos-sticky-header',
|
||||
},
|
||||
fixable: 'code',
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
// Option properties here (or remove the object)
|
||||
escapeHTML: {
|
||||
type: 'boolean',
|
||||
},
|
||||
escapeMarkdown: {
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
384
node_modules/eslint-plugin-jsdoc/src/rules/validTypes.js
generated
vendored
Normal file
384
node_modules/eslint-plugin-jsdoc/src/rules/validTypes.js
generated
vendored
Normal file
@@ -0,0 +1,384 @@
|
||||
import iterateJsdoc from '../iterateJsdoc.js';
|
||||
import {
|
||||
parse,
|
||||
traverse,
|
||||
tryParse,
|
||||
} from '@es-joy/jsdoccomment';
|
||||
|
||||
const inlineTags = new Set([
|
||||
'link', 'linkcode', 'linkplain',
|
||||
'tutorial',
|
||||
]);
|
||||
|
||||
const jsdocTypePrattKeywords = new Set([
|
||||
'typeof',
|
||||
'readonly',
|
||||
'import',
|
||||
'is',
|
||||
]);
|
||||
|
||||
const asExpression = /as\s+/u;
|
||||
|
||||
const suppressTypes = new Set([
|
||||
// https://github.com/google/closure-compiler/wiki/@suppress-annotations
|
||||
// https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/parsing/ParserConfig.properties#L154
|
||||
'accessControls',
|
||||
'checkDebuggerStatement',
|
||||
'checkPrototypalTypes',
|
||||
'checkRegExp',
|
||||
'checkTypes',
|
||||
'checkVars',
|
||||
'closureDepMethodUsageChecks',
|
||||
'const',
|
||||
'constantProperty',
|
||||
'deprecated',
|
||||
'duplicate',
|
||||
'es5Strict',
|
||||
'externsValidation',
|
||||
'extraProvide',
|
||||
'extraRequire',
|
||||
'globalThis',
|
||||
'invalidCasts',
|
||||
'lateProvide',
|
||||
'legacyGoogScopeRequire',
|
||||
'lintChecks',
|
||||
'messageConventions',
|
||||
'misplacedTypeAnnotation',
|
||||
'missingOverride',
|
||||
'missingPolyfill',
|
||||
'missingProperties',
|
||||
'missingProvide',
|
||||
'missingRequire',
|
||||
'missingSourcesWarnings',
|
||||
'moduleLoad',
|
||||
'nonStandardJsDocs',
|
||||
'partialAlias',
|
||||
'polymer',
|
||||
'reportUnknownTypes',
|
||||
'strictMissingProperties',
|
||||
'strictModuleDepCheck',
|
||||
'strictPrimitiveOperators',
|
||||
'suspiciousCode',
|
||||
|
||||
// Not documented in enum
|
||||
'switch',
|
||||
'transitionalSuspiciousCodeWarnings',
|
||||
'undefinedNames',
|
||||
'undefinedVars',
|
||||
'underscore',
|
||||
'unknownDefines',
|
||||
'untranspilableFeatures',
|
||||
'unusedLocalVariables',
|
||||
'unusedPrivateMembers',
|
||||
'useOfGoogProvide',
|
||||
'uselessCode',
|
||||
'visibility',
|
||||
'with',
|
||||
]);
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const tryParsePathIgnoreError = (path) => {
|
||||
try {
|
||||
tryParse(path);
|
||||
|
||||
return true;
|
||||
} catch {
|
||||
// Keep the original error for including the whole type
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
export default iterateJsdoc(({
|
||||
jsdoc,
|
||||
report,
|
||||
utils,
|
||||
context,
|
||||
settings,
|
||||
}) => {
|
||||
const {
|
||||
allowEmptyNamepaths = false,
|
||||
} = context.options[0] || {};
|
||||
const {
|
||||
mode,
|
||||
} = settings;
|
||||
|
||||
for (const tag of jsdoc.tags) {
|
||||
/**
|
||||
* @param {string} namepath
|
||||
* @param {string} [tagName]
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const validNamepathParsing = function (namepath, tagName) {
|
||||
if (
|
||||
tryParsePathIgnoreError(namepath) ||
|
||||
jsdocTypePrattKeywords.has(namepath)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let handled = false;
|
||||
|
||||
if (tagName) {
|
||||
switch (tagName) {
|
||||
case 'requires':
|
||||
case 'module': {
|
||||
if (!namepath.startsWith('module:')) {
|
||||
handled = tryParsePathIgnoreError(`module:${namepath}`);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'memberof': case 'memberof!': {
|
||||
const endChar = namepath.slice(-1);
|
||||
if ([
|
||||
'#', '.', '~',
|
||||
].includes(endChar)) {
|
||||
handled = tryParsePathIgnoreError(namepath.slice(0, -1));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'borrows': {
|
||||
const startChar = namepath.charAt(0);
|
||||
if ([
|
||||
'#', '.', '~',
|
||||
].includes(startChar)) {
|
||||
handled = tryParsePathIgnoreError(namepath.slice(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
report(`Syntax error in namepath: ${namepath}`, null, tag);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} type
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const validTypeParsing = function (type) {
|
||||
let parsedTypes;
|
||||
try {
|
||||
if (mode === 'permissive') {
|
||||
parsedTypes = tryParse(type);
|
||||
} else {
|
||||
parsedTypes = parse(type, mode);
|
||||
}
|
||||
} catch {
|
||||
report(`Syntax error in type: ${type}`, null, tag);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mode === 'closure' || mode === 'typescript') {
|
||||
traverse(parsedTypes, (node) => {
|
||||
const {
|
||||
type: typ,
|
||||
} = node;
|
||||
|
||||
if (
|
||||
(typ === 'JsdocTypeObjectField' || typ === 'JsdocTypeKeyValue') &&
|
||||
node.right?.type === 'JsdocTypeNullable' &&
|
||||
node.right?.meta?.position === 'suffix'
|
||||
) {
|
||||
report(`Syntax error in type: ${node.right.type}`, null, tag);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
if (tag.problems.length) {
|
||||
const msg = tag.problems.reduce((str, {
|
||||
message,
|
||||
}) => {
|
||||
return str + '; ' + message;
|
||||
}, '').slice(2);
|
||||
report(`Invalid name: ${msg}`, null, tag);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tag.tag === 'import') {
|
||||
// A named import will look like a type, but not be valid; we also don't
|
||||
// need to check the name/namepath
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tag.tag === 'borrows') {
|
||||
const thisNamepath = /** @type {string} */ (
|
||||
utils.getTagDescription(tag)
|
||||
).replace(asExpression, '')
|
||||
.trim();
|
||||
|
||||
if (!asExpression.test(/** @type {string} */ (
|
||||
utils.getTagDescription(tag)
|
||||
)) || !thisNamepath) {
|
||||
report(`@borrows must have an "as" expression. Found "${utils.getTagDescription(tag)}"`, null, tag);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (validNamepathParsing(thisNamepath, 'borrows')) {
|
||||
const thatNamepath = tag.name;
|
||||
|
||||
validNamepathParsing(thatNamepath);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (tag.tag === 'suppress' && mode === 'closure') {
|
||||
let parsedTypes;
|
||||
|
||||
try {
|
||||
parsedTypes = tryParse(tag.type);
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
if (parsedTypes) {
|
||||
traverse(parsedTypes, (node) => {
|
||||
let type;
|
||||
if ('value' in node && typeof node.value === 'string') {
|
||||
type = node.value;
|
||||
}
|
||||
|
||||
if (type !== undefined && !suppressTypes.has(type)) {
|
||||
report(`Syntax error in suppress type: ${type}`, null, tag);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const otherModeMaps = /** @type {import('../jsdocUtils.js').ParserMode[]} */ ([
|
||||
'jsdoc', 'typescript', 'closure', 'permissive',
|
||||
]).filter(
|
||||
(mde) => {
|
||||
return mde !== mode;
|
||||
},
|
||||
).map((mde) => {
|
||||
return utils.getTagStructureForMode(mde);
|
||||
});
|
||||
|
||||
const tagMightHaveNamePosition = utils.tagMightHaveNamePosition(tag.tag, otherModeMaps);
|
||||
if (tagMightHaveNamePosition !== true && tag.name) {
|
||||
const modeInfo = tagMightHaveNamePosition === false ? '' : ` in "${mode}" mode`;
|
||||
report(`@${tag.tag} should not have a name${modeInfo}.`, null, tag);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const mightHaveTypePosition = utils.tagMightHaveTypePosition(tag.tag, otherModeMaps);
|
||||
if (mightHaveTypePosition !== true && tag.type) {
|
||||
const modeInfo = mightHaveTypePosition === false ? '' : ` in "${mode}" mode`;
|
||||
report(`@${tag.tag} should not have a bracketed type${modeInfo}.`, null, tag);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// REQUIRED NAME
|
||||
const tagMustHaveNamePosition = utils.tagMustHaveNamePosition(tag.tag, otherModeMaps);
|
||||
|
||||
// Don't handle `@param` here though it does require name as handled by
|
||||
// `require-param-name` (`@property` would similarly seem to require one,
|
||||
// but is handled by `require-property-name`)
|
||||
if (tagMustHaveNamePosition !== false && !tag.name && !allowEmptyNamepaths && ![
|
||||
'param', 'arg', 'argument',
|
||||
'property', 'prop',
|
||||
].includes(tag.tag) &&
|
||||
(tag.tag !== 'see' || !utils.getTagDescription(tag).includes('{@link'))
|
||||
) {
|
||||
const modeInfo = tagMustHaveNamePosition === true ? '' : ` in "${mode}" mode`;
|
||||
report(`Tag @${tag.tag} must have a name/namepath${modeInfo}.`, null, tag);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// REQUIRED TYPE
|
||||
const mustHaveTypePosition = utils.tagMustHaveTypePosition(tag.tag, otherModeMaps);
|
||||
if (mustHaveTypePosition !== false && !tag.type) {
|
||||
const modeInfo = mustHaveTypePosition === true ? '' : ` in "${mode}" mode`;
|
||||
report(`Tag @${tag.tag} must have a type${modeInfo}.`, null, tag);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// REQUIRED TYPE OR NAME/NAMEPATH
|
||||
const tagMissingRequiredTypeOrNamepath = utils.tagMissingRequiredTypeOrNamepath(tag, otherModeMaps);
|
||||
if (tagMissingRequiredTypeOrNamepath !== false && !allowEmptyNamepaths) {
|
||||
const modeInfo = tagMissingRequiredTypeOrNamepath === true ? '' : ` in "${mode}" mode`;
|
||||
report(`Tag @${tag.tag} must have either a type or namepath${modeInfo}.`, null, tag);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// VALID TYPE
|
||||
const hasTypePosition = mightHaveTypePosition === true && Boolean(tag.type);
|
||||
if (hasTypePosition) {
|
||||
validTypeParsing(tag.type);
|
||||
}
|
||||
|
||||
// VALID NAME/NAMEPATH
|
||||
const hasNameOrNamepathPosition = (
|
||||
tagMustHaveNamePosition !== false ||
|
||||
utils.tagMightHaveNamepath(tag.tag)
|
||||
) && Boolean(tag.name);
|
||||
|
||||
if (hasNameOrNamepathPosition) {
|
||||
if (mode !== 'jsdoc' && tag.tag === 'template') {
|
||||
for (const namepath of utils.parseClosureTemplateTag(tag)) {
|
||||
validNamepathParsing(namepath);
|
||||
}
|
||||
} else {
|
||||
validNamepathParsing(tag.name, tag.tag);
|
||||
}
|
||||
}
|
||||
|
||||
for (const inlineTag of tag.inlineTags) {
|
||||
if (inlineTags.has(inlineTag.tag) && !inlineTag.text && !inlineTag.namepathOrURL) {
|
||||
report(`Inline tag "${inlineTag.tag}" missing content`, null, tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const inlineTag of jsdoc.inlineTags) {
|
||||
if (inlineTags.has(inlineTag.tag) && !inlineTag.text && !inlineTag.namepathOrURL) {
|
||||
report(`Inline tag "${inlineTag.tag}" missing content`);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
iterateAllJsdocs: true,
|
||||
meta: {
|
||||
docs: {
|
||||
description: 'Requires all types to be valid JSDoc or Closure compiler types without syntax errors.',
|
||||
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/valid-types.md#repos-sticky-header',
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
allowEmptyNamepaths: {
|
||||
default: false,
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
},
|
||||
],
|
||||
type: 'suggestion',
|
||||
},
|
||||
});
|
||||
238
node_modules/eslint-plugin-jsdoc/src/tagNames.js
generated
vendored
Normal file
238
node_modules/eslint-plugin-jsdoc/src/tagNames.js
generated
vendored
Normal file
@@ -0,0 +1,238 @@
|
||||
/**
|
||||
* @typedef {{
|
||||
* [key: string]: string[]
|
||||
* }} AliasedTags
|
||||
*/
|
||||
|
||||
/**
|
||||
* @type {AliasedTags}
|
||||
*/
|
||||
const jsdocTagsUndocumented = {
|
||||
// Undocumented but present; see
|
||||
// https://github.com/jsdoc/jsdoc/issues/1283#issuecomment-516816802
|
||||
// https://github.com/jsdoc/jsdoc/blob/master/packages/jsdoc/lib/jsdoc/tag/dictionary/definitions.js#L594
|
||||
modifies: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {AliasedTags}
|
||||
*/
|
||||
const jsdocTags = {
|
||||
...jsdocTagsUndocumented,
|
||||
abstract: [
|
||||
'virtual',
|
||||
],
|
||||
access: [],
|
||||
alias: [],
|
||||
async: [],
|
||||
augments: [
|
||||
'extends',
|
||||
],
|
||||
author: [],
|
||||
borrows: [],
|
||||
callback: [],
|
||||
class: [
|
||||
'constructor',
|
||||
],
|
||||
classdesc: [],
|
||||
constant: [
|
||||
'const',
|
||||
],
|
||||
constructs: [],
|
||||
copyright: [],
|
||||
default: [
|
||||
'defaultvalue',
|
||||
],
|
||||
deprecated: [],
|
||||
description: [
|
||||
'desc',
|
||||
],
|
||||
enum: [],
|
||||
event: [],
|
||||
example: [],
|
||||
exports: [],
|
||||
external: [
|
||||
'host',
|
||||
],
|
||||
file: [
|
||||
'fileoverview',
|
||||
'overview',
|
||||
],
|
||||
fires: [
|
||||
'emits',
|
||||
],
|
||||
function: [
|
||||
'func',
|
||||
'method',
|
||||
],
|
||||
generator: [],
|
||||
global: [],
|
||||
hideconstructor: [],
|
||||
ignore: [],
|
||||
implements: [],
|
||||
inheritdoc: [],
|
||||
|
||||
// Allowing casing distinct from jsdoc `definitions.js` (required in Closure)
|
||||
inheritDoc: [],
|
||||
|
||||
inner: [],
|
||||
instance: [],
|
||||
interface: [],
|
||||
kind: [],
|
||||
lends: [],
|
||||
license: [],
|
||||
listens: [],
|
||||
member: [
|
||||
'var',
|
||||
],
|
||||
memberof: [],
|
||||
'memberof!': [],
|
||||
mixes: [],
|
||||
mixin: [],
|
||||
|
||||
module: [],
|
||||
name: [],
|
||||
namespace: [],
|
||||
override: [],
|
||||
package: [],
|
||||
param: [
|
||||
'arg',
|
||||
'argument',
|
||||
],
|
||||
private: [],
|
||||
property: [
|
||||
'prop',
|
||||
],
|
||||
protected: [],
|
||||
public: [],
|
||||
readonly: [],
|
||||
requires: [],
|
||||
returns: [
|
||||
'return',
|
||||
],
|
||||
see: [],
|
||||
since: [],
|
||||
static: [],
|
||||
summary: [],
|
||||
|
||||
this: [],
|
||||
throws: [
|
||||
'exception',
|
||||
],
|
||||
todo: [],
|
||||
tutorial: [],
|
||||
type: [],
|
||||
typedef: [],
|
||||
variation: [],
|
||||
version: [],
|
||||
yields: [
|
||||
'yield',
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {AliasedTags}
|
||||
*/
|
||||
const typeScriptTags = {
|
||||
...jsdocTags,
|
||||
|
||||
// https://github.com/microsoft/TypeScript/issues/22160
|
||||
// https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/#the-jsdoc-import-tag
|
||||
import: [],
|
||||
|
||||
// https://www.typescriptlang.org/tsconfig/#stripInternal
|
||||
internal: [],
|
||||
|
||||
// https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/#overload-support-in-jsdoc
|
||||
overload: [],
|
||||
|
||||
// https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/#satisfies-support-in-jsdoc
|
||||
satisfies: [],
|
||||
|
||||
// `@template` is also in TypeScript per:
|
||||
// https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html#supported-jsdoc
|
||||
template: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {AliasedTags}
|
||||
*/
|
||||
const undocumentedClosureTags = {
|
||||
// These are in Closure source but not in jsdoc source nor in the Closure
|
||||
// docs: https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/parsing/Annotation.java
|
||||
closurePrimitive: [],
|
||||
customElement: [],
|
||||
expose: [],
|
||||
hidden: [],
|
||||
idGenerator: [],
|
||||
meaning: [],
|
||||
mixinClass: [],
|
||||
mixinFunction: [],
|
||||
ngInject: [],
|
||||
owner: [],
|
||||
typeSummary: [],
|
||||
wizaction: [],
|
||||
};
|
||||
|
||||
const {
|
||||
/* eslint-disable no-unused-vars */
|
||||
inheritdoc,
|
||||
internal,
|
||||
overload,
|
||||
satisfies,
|
||||
|
||||
// Will be inverted to prefer `return`
|
||||
returns,
|
||||
/* eslint-enable no-unused-vars */
|
||||
...typeScriptTagsInClosure
|
||||
} = typeScriptTags;
|
||||
|
||||
/**
|
||||
* @type {AliasedTags}
|
||||
*/
|
||||
const closureTags = {
|
||||
...typeScriptTagsInClosure,
|
||||
...undocumentedClosureTags,
|
||||
|
||||
// From https://github.com/google/closure-compiler/wiki/Annotating-JavaScript-for-the-Closure-Compiler
|
||||
// These are all recognized in https://github.com/jsdoc/jsdoc/blob/master/packages/jsdoc/lib/jsdoc/tag/dictionary/definitions.js
|
||||
// except for the experimental `noinline` and the casing differences noted below
|
||||
|
||||
// Defined as a synonym of `const` in jsdoc `definitions.js`
|
||||
define: [],
|
||||
|
||||
dict: [],
|
||||
export: [],
|
||||
externs: [],
|
||||
final: [],
|
||||
|
||||
// With casing distinct from jsdoc `definitions.js`
|
||||
implicitCast: [],
|
||||
|
||||
noalias: [],
|
||||
nocollapse: [],
|
||||
nocompile: [],
|
||||
noinline: [],
|
||||
nosideeffects: [],
|
||||
polymer: [],
|
||||
polymerBehavior: [],
|
||||
preserve: [],
|
||||
|
||||
// Defined as a synonym of `interface` in jsdoc `definitions.js`
|
||||
record: [],
|
||||
|
||||
return: [
|
||||
'returns',
|
||||
],
|
||||
|
||||
struct: [],
|
||||
suppress: [],
|
||||
|
||||
unrestricted: [],
|
||||
};
|
||||
|
||||
export {
|
||||
closureTags,
|
||||
jsdocTags,
|
||||
typeScriptTags,
|
||||
};
|
||||
549
node_modules/eslint-plugin-jsdoc/src/utils/hasReturnValue.js
generated
vendored
Normal file
549
node_modules/eslint-plugin-jsdoc/src/utils/hasReturnValue.js
generated
vendored
Normal file
@@ -0,0 +1,549 @@
|
||||
/**
|
||||
* @typedef {import('estree').Node|
|
||||
* import('@typescript-eslint/types').TSESTree.Node} ESTreeOrTypeScriptNode
|
||||
*/
|
||||
|
||||
/**
|
||||
* Checks if a node is a promise but has no resolve value or an empty value.
|
||||
* An `undefined` resolve does not count.
|
||||
* @param {ESTreeOrTypeScriptNode|undefined|null} node
|
||||
* @returns {boolean|undefined|null}
|
||||
*/
|
||||
const isNewPromiseExpression = (node) => {
|
||||
return node && node.type === 'NewExpression' && node.callee.type === 'Identifier' &&
|
||||
node.callee.name === 'Promise';
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {ESTreeOrTypeScriptNode|null|undefined} node
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const isVoidPromise = (node) => {
|
||||
return /** @type {import('@typescript-eslint/types').TSESTree.TSTypeReference} */ (node)?.typeArguments?.params?.[0]?.type === 'TSVoidKeyword'
|
||||
/* c8 ignore next */
|
||||
|| /** @type {import('@typescript-eslint/types').TSESTree.TSTypeReference} */ (node)?.typeParameters?.params?.[0]?.type === 'TSVoidKeyword';
|
||||
};
|
||||
|
||||
const undefinedKeywords = new Set([
|
||||
'TSVoidKeyword', 'TSUndefinedKeyword', 'TSNeverKeyword',
|
||||
]);
|
||||
|
||||
/**
|
||||
* Checks if a node has a return statement. Void return does not count.
|
||||
* @param {ESTreeOrTypeScriptNode|undefined|null} node
|
||||
* @param {boolean} [throwOnNullReturn]
|
||||
* @param {PromiseFilter} [promFilter]
|
||||
* @returns {boolean|undefined}
|
||||
*/
|
||||
// eslint-disable-next-line complexity
|
||||
const hasReturnValue = (node, throwOnNullReturn, promFilter) => {
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (node.type) {
|
||||
case 'TSDeclareFunction':
|
||||
case 'TSFunctionType':
|
||||
case 'TSMethodSignature': {
|
||||
const type = node?.returnType?.typeAnnotation?.type;
|
||||
return type && !undefinedKeywords.has(type);
|
||||
}
|
||||
|
||||
case 'MethodDefinition':
|
||||
return hasReturnValue(node.value, throwOnNullReturn, promFilter);
|
||||
case 'FunctionExpression':
|
||||
case 'FunctionDeclaration':
|
||||
case 'ArrowFunctionExpression': {
|
||||
return 'expression' in node && node.expression && (!isNewPromiseExpression(
|
||||
node.body,
|
||||
) || !isVoidPromise(node.body)) ||
|
||||
hasReturnValue(node.body, throwOnNullReturn, promFilter);
|
||||
}
|
||||
|
||||
case 'BlockStatement': {
|
||||
return node.body.some((bodyNode) => {
|
||||
return bodyNode.type !== 'FunctionDeclaration' && hasReturnValue(bodyNode, throwOnNullReturn, promFilter);
|
||||
});
|
||||
}
|
||||
|
||||
case 'LabeledStatement':
|
||||
case 'WhileStatement':
|
||||
case 'DoWhileStatement':
|
||||
case 'ForStatement':
|
||||
case 'ForInStatement':
|
||||
case 'ForOfStatement':
|
||||
case 'WithStatement': {
|
||||
return hasReturnValue(node.body, throwOnNullReturn, promFilter);
|
||||
}
|
||||
|
||||
case 'IfStatement': {
|
||||
return hasReturnValue(node.consequent, throwOnNullReturn, promFilter) ||
|
||||
hasReturnValue(node.alternate, throwOnNullReturn, promFilter);
|
||||
}
|
||||
|
||||
case 'TryStatement': {
|
||||
return hasReturnValue(node.block, throwOnNullReturn, promFilter) ||
|
||||
hasReturnValue(node.handler && node.handler.body, throwOnNullReturn, promFilter) ||
|
||||
hasReturnValue(node.finalizer, throwOnNullReturn, promFilter);
|
||||
}
|
||||
|
||||
case 'SwitchStatement': {
|
||||
return node.cases.some(
|
||||
(someCase) => {
|
||||
return someCase.consequent.some((nde) => {
|
||||
return hasReturnValue(nde, throwOnNullReturn, promFilter);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
case 'ReturnStatement': {
|
||||
// void return does not count.
|
||||
if (node.argument === null) {
|
||||
if (throwOnNullReturn) {
|
||||
throw new Error('Null return');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (promFilter && isNewPromiseExpression(node.argument)) {
|
||||
// Let caller decide how to filter, but this is, at the least,
|
||||
// a return of sorts and truthy
|
||||
return promFilter(node.argument);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a node has a return statement. Void return does not count.
|
||||
* @param {ESTreeOrTypeScriptNode|null|undefined} node
|
||||
* @param {PromiseFilter} promFilter
|
||||
* @returns {undefined|boolean|ESTreeOrTypeScriptNode}
|
||||
*/
|
||||
// eslint-disable-next-line complexity
|
||||
const allBrancheshaveReturnValues = (node, promFilter) => {
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (node.type) {
|
||||
case 'TSDeclareFunction':
|
||||
case 'TSFunctionType':
|
||||
case 'TSMethodSignature': {
|
||||
const type = node?.returnType?.typeAnnotation?.type;
|
||||
return type && !undefinedKeywords.has(type);
|
||||
}
|
||||
|
||||
// case 'MethodDefinition':
|
||||
// return allBrancheshaveReturnValues(node.value, promFilter);
|
||||
case 'FunctionExpression':
|
||||
case 'FunctionDeclaration':
|
||||
case 'ArrowFunctionExpression': {
|
||||
return 'expression' in node && node.expression && (!isNewPromiseExpression(node.body) || !isVoidPromise(node.body)) ||
|
||||
allBrancheshaveReturnValues(node.body, promFilter) ||
|
||||
/** @type {import('@typescript-eslint/types').TSESTree.BlockStatement} */
|
||||
(node.body).body.some((nde) => {
|
||||
return nde.type === 'ReturnStatement';
|
||||
});
|
||||
}
|
||||
|
||||
case 'BlockStatement': {
|
||||
const lastBodyNode = node.body.slice(-1)[0];
|
||||
return allBrancheshaveReturnValues(lastBodyNode, promFilter);
|
||||
}
|
||||
|
||||
case 'WhileStatement':
|
||||
case 'DoWhileStatement':
|
||||
if (
|
||||
/**
|
||||
* @type {import('@typescript-eslint/types').TSESTree.Literal}
|
||||
*/
|
||||
(node.test).value === true
|
||||
) {
|
||||
// If this is an infinite loop, we assume only one branch
|
||||
// is needed to provide a return
|
||||
return hasReturnValue(node.body, false, promFilter);
|
||||
}
|
||||
|
||||
// Fallthrough
|
||||
case 'LabeledStatement':
|
||||
case 'ForStatement':
|
||||
case 'ForInStatement':
|
||||
case 'ForOfStatement':
|
||||
case 'WithStatement': {
|
||||
return allBrancheshaveReturnValues(node.body, promFilter);
|
||||
}
|
||||
|
||||
case 'IfStatement': {
|
||||
return allBrancheshaveReturnValues(node.consequent, promFilter) &&
|
||||
allBrancheshaveReturnValues(node.alternate, promFilter);
|
||||
}
|
||||
|
||||
case 'TryStatement': {
|
||||
// If `finally` returns, all return
|
||||
return node.finalizer && allBrancheshaveReturnValues(node.finalizer, promFilter) ||
|
||||
// Return in `try`/`catch` may still occur despite `finally`
|
||||
allBrancheshaveReturnValues(node.block, promFilter) &&
|
||||
(!node.handler ||
|
||||
allBrancheshaveReturnValues(node.handler && node.handler.body, promFilter)) &&
|
||||
(!node.finalizer || (() => {
|
||||
try {
|
||||
hasReturnValue(node.finalizer, true, promFilter);
|
||||
} catch (error) {
|
||||
if (/** @type {Error} */ (error).message === 'Null return') {
|
||||
return false;
|
||||
}
|
||||
/* c8 ignore next 2 */
|
||||
throw error;
|
||||
}
|
||||
|
||||
// As long as not an explicit empty return, then return true
|
||||
return true;
|
||||
})());
|
||||
}
|
||||
|
||||
case 'SwitchStatement': {
|
||||
return /** @type {import('@typescript-eslint/types').TSESTree.SwitchStatement} */ (node).cases.every(
|
||||
(someCase) => {
|
||||
return !someCase.consequent.some((consNode) => {
|
||||
return consNode.type === 'BreakStatement' ||
|
||||
consNode.type === 'ReturnStatement' && consNode.argument === null;
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
case 'ThrowStatement': {
|
||||
return true;
|
||||
}
|
||||
|
||||
case 'ReturnStatement': {
|
||||
// void return does not count.
|
||||
if (node.argument === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (promFilter && isNewPromiseExpression(node.argument)) {
|
||||
// Let caller decide how to filter, but this is, at the least,
|
||||
// a return of sorts and truthy
|
||||
return promFilter(node.argument);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @callback PromiseFilter
|
||||
* @param {ESTreeOrTypeScriptNode|undefined} node
|
||||
* @returns {boolean}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Avoids further checking child nodes if a nested function shadows the
|
||||
* resolver, but otherwise, if name is used (by call or passed in as an
|
||||
* argument to another function), will be considered as non-empty.
|
||||
*
|
||||
* This could check for redeclaration of the resolver, but as such is
|
||||
* unlikely, we avoid the performance cost of checking everywhere for
|
||||
* (re)declarations or assignments.
|
||||
* @param {import('@typescript-eslint/types').TSESTree.Node|null|undefined} node
|
||||
* @param {string} resolverName
|
||||
* @returns {boolean}
|
||||
*/
|
||||
// eslint-disable-next-line complexity
|
||||
const hasNonEmptyResolverCall = (node, resolverName) => {
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Arrow function without block
|
||||
switch (node.type) {
|
||||
/* c8 ignore next 2 -- In Babel? */
|
||||
// @ts-expect-error Babel?
|
||||
case 'OptionalCallExpression':
|
||||
case 'CallExpression':
|
||||
return /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */ (
|
||||
node.callee
|
||||
).name === resolverName && (
|
||||
|
||||
// Implicit or explicit undefined
|
||||
node.arguments.length > 1 || node.arguments[0] !== undefined
|
||||
) ||
|
||||
node.arguments.some((nde) => {
|
||||
// Being passed in to another function (which might invoke it)
|
||||
return nde.type === 'Identifier' && nde.name === resolverName ||
|
||||
|
||||
// Handle nested items
|
||||
hasNonEmptyResolverCall(nde, resolverName);
|
||||
});
|
||||
case 'ChainExpression':
|
||||
case 'Decorator':
|
||||
case 'ExpressionStatement':
|
||||
return hasNonEmptyResolverCall(node.expression, resolverName);
|
||||
case 'ClassBody':
|
||||
case 'BlockStatement':
|
||||
return node.body.some((bodyNode) => {
|
||||
return hasNonEmptyResolverCall(bodyNode, resolverName);
|
||||
});
|
||||
case 'FunctionExpression':
|
||||
case 'FunctionDeclaration':
|
||||
case 'ArrowFunctionExpression': {
|
||||
// Shadowing
|
||||
if (/** @type {import('@typescript-eslint/types').TSESTree.Identifier} */ (
|
||||
node.params[0]
|
||||
)?.name === resolverName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return hasNonEmptyResolverCall(node.body, resolverName);
|
||||
}
|
||||
|
||||
case 'LabeledStatement':
|
||||
case 'WhileStatement':
|
||||
case 'DoWhileStatement':
|
||||
case 'ForStatement':
|
||||
case 'ForInStatement':
|
||||
case 'ForOfStatement':
|
||||
case 'WithStatement': {
|
||||
return hasNonEmptyResolverCall(node.body, resolverName);
|
||||
}
|
||||
|
||||
case 'ConditionalExpression':
|
||||
case 'IfStatement': {
|
||||
return hasNonEmptyResolverCall(node.test, resolverName) ||
|
||||
hasNonEmptyResolverCall(node.consequent, resolverName) ||
|
||||
hasNonEmptyResolverCall(node.alternate, resolverName);
|
||||
}
|
||||
|
||||
case 'TryStatement': {
|
||||
return hasNonEmptyResolverCall(node.block, resolverName) ||
|
||||
hasNonEmptyResolverCall(node.handler && node.handler.body, resolverName) ||
|
||||
hasNonEmptyResolverCall(node.finalizer, resolverName);
|
||||
}
|
||||
|
||||
case 'SwitchStatement': {
|
||||
return node.cases.some(
|
||||
(someCase) => {
|
||||
return someCase.consequent.some((nde) => {
|
||||
return hasNonEmptyResolverCall(nde, resolverName);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
case 'ArrayPattern':
|
||||
case 'ArrayExpression':
|
||||
return node.elements.some((element) => {
|
||||
return hasNonEmptyResolverCall(element, resolverName);
|
||||
});
|
||||
|
||||
case 'AssignmentPattern':
|
||||
return hasNonEmptyResolverCall(node.right, resolverName);
|
||||
|
||||
case 'AssignmentExpression':
|
||||
case 'BinaryExpression':
|
||||
case 'LogicalExpression': {
|
||||
return hasNonEmptyResolverCall(node.left, resolverName) ||
|
||||
hasNonEmptyResolverCall(node.right, resolverName);
|
||||
}
|
||||
|
||||
// Comma
|
||||
case 'SequenceExpression':
|
||||
case 'TemplateLiteral':
|
||||
return node.expressions.some((subExpression) => {
|
||||
return hasNonEmptyResolverCall(subExpression, resolverName);
|
||||
});
|
||||
|
||||
case 'ObjectPattern':
|
||||
case 'ObjectExpression':
|
||||
return node.properties.some((property) => {
|
||||
return hasNonEmptyResolverCall(property, resolverName);
|
||||
});
|
||||
/* c8 ignore next 2 -- In Babel? */
|
||||
// @ts-expect-error Babel?
|
||||
case 'ClassMethod':
|
||||
case 'MethodDefinition':
|
||||
return node.decorators && node.decorators.some((decorator) => {
|
||||
return hasNonEmptyResolverCall(decorator, resolverName);
|
||||
}) ||
|
||||
node.computed && hasNonEmptyResolverCall(node.key, resolverName) ||
|
||||
hasNonEmptyResolverCall(node.value, resolverName);
|
||||
|
||||
/* c8 ignore next 2 -- In Babel? */
|
||||
// @ts-expect-error Babel?
|
||||
case 'ObjectProperty':
|
||||
/* eslint-disable no-fallthrough */
|
||||
/* c8 ignore next -- In Babel? */
|
||||
case 'PropertyDefinition':
|
||||
/* c8 ignore next 2 -- In Babel? */
|
||||
// @ts-expect-error Babel?
|
||||
case 'ClassProperty':
|
||||
case 'Property':
|
||||
/* eslint-enable no-fallthrough */
|
||||
return node.computed && hasNonEmptyResolverCall(node.key, resolverName) ||
|
||||
hasNonEmptyResolverCall(node.value, resolverName);
|
||||
/* c8 ignore next 2 -- In Babel? */
|
||||
// @ts-expect-error Babel?
|
||||
case 'ObjectMethod':
|
||||
/* c8 ignore next 6 -- In Babel? */
|
||||
// @ts-expect-error
|
||||
return node.computed && hasNonEmptyResolverCall(node.key, resolverName) ||
|
||||
// @ts-expect-error
|
||||
node.arguments.some((nde) => {
|
||||
return hasNonEmptyResolverCall(nde, resolverName);
|
||||
});
|
||||
|
||||
case 'ClassExpression':
|
||||
case 'ClassDeclaration':
|
||||
return hasNonEmptyResolverCall(node.body, resolverName);
|
||||
|
||||
case 'AwaitExpression':
|
||||
case 'SpreadElement':
|
||||
case 'UnaryExpression':
|
||||
case 'YieldExpression':
|
||||
return hasNonEmptyResolverCall(node.argument, resolverName);
|
||||
|
||||
case 'VariableDeclaration': {
|
||||
return node.declarations.some((nde) => {
|
||||
return hasNonEmptyResolverCall(nde, resolverName);
|
||||
});
|
||||
}
|
||||
|
||||
case 'VariableDeclarator': {
|
||||
return hasNonEmptyResolverCall(node.id, resolverName) ||
|
||||
hasNonEmptyResolverCall(node.init, resolverName);
|
||||
}
|
||||
|
||||
case 'TaggedTemplateExpression':
|
||||
return hasNonEmptyResolverCall(node.quasi, resolverName);
|
||||
|
||||
// ?.
|
||||
/* c8 ignore next 2 -- In Babel? */
|
||||
// @ts-expect-error Babel?
|
||||
case 'OptionalMemberExpression':
|
||||
case 'MemberExpression':
|
||||
return hasNonEmptyResolverCall(node.object, resolverName) ||
|
||||
hasNonEmptyResolverCall(node.property, resolverName);
|
||||
|
||||
/* c8 ignore next 2 -- In Babel? */
|
||||
// @ts-expect-error Babel?
|
||||
case 'Import':
|
||||
case 'ImportExpression':
|
||||
return hasNonEmptyResolverCall(node.source, resolverName);
|
||||
|
||||
case 'ReturnStatement': {
|
||||
if (node.argument === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return hasNonEmptyResolverCall(node.argument, resolverName);
|
||||
}
|
||||
|
||||
/*
|
||||
// Shouldn't need to parse literals/literal components, etc.
|
||||
|
||||
case 'Identifier':
|
||||
case 'TemplateElement':
|
||||
case 'Super':
|
||||
// Exports not relevant in this context
|
||||
*/
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a Promise executor has no resolve value or an empty value.
|
||||
* An `undefined` resolve does not count.
|
||||
* @param {ESTreeOrTypeScriptNode} node
|
||||
* @param {boolean} anyPromiseAsReturn
|
||||
* @param {boolean} [allBranches]
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const hasValueOrExecutorHasNonEmptyResolveValue = (node, anyPromiseAsReturn, allBranches) => {
|
||||
const hasReturnMethod = allBranches ?
|
||||
/**
|
||||
* @param {ESTreeOrTypeScriptNode} nde
|
||||
* @param {PromiseFilter} promiseFilter
|
||||
* @returns {boolean}
|
||||
*/
|
||||
(nde, promiseFilter) => {
|
||||
let hasReturn;
|
||||
try {
|
||||
hasReturn = hasReturnValue(nde, true, promiseFilter);
|
||||
} catch (error) {
|
||||
// c8 ignore else
|
||||
if (/** @type {Error} */ (error).message === 'Null return') {
|
||||
return false;
|
||||
}
|
||||
/* c8 ignore next 2 */
|
||||
throw error;
|
||||
}
|
||||
|
||||
// `hasReturn` check needed since `throw` treated as valid return by
|
||||
// `allBrancheshaveReturnValues`
|
||||
return Boolean(hasReturn && allBrancheshaveReturnValues(nde, promiseFilter));
|
||||
} :
|
||||
/**
|
||||
* @param {ESTreeOrTypeScriptNode} nde
|
||||
* @param {PromiseFilter} promiseFilter
|
||||
* @returns {boolean}
|
||||
*/
|
||||
(nde, promiseFilter) => {
|
||||
return Boolean(hasReturnValue(nde, false, promiseFilter));
|
||||
};
|
||||
|
||||
return hasReturnMethod(node, (prom) => {
|
||||
if (anyPromiseAsReturn) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isVoidPromise(prom)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const {
|
||||
params,
|
||||
body,
|
||||
} =
|
||||
/**
|
||||
* @type {import('@typescript-eslint/types').TSESTree.FunctionExpression|
|
||||
* import('@typescript-eslint/types').TSESTree.ArrowFunctionExpression}
|
||||
*/ (
|
||||
/** @type {import('@typescript-eslint/types').TSESTree.NewExpression} */ (
|
||||
prom
|
||||
).arguments[0]
|
||||
) || {};
|
||||
|
||||
if (!params?.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const {
|
||||
name: resolverName,
|
||||
} = /** @type {import('@typescript-eslint/types').TSESTree.Identifier} */ (
|
||||
params[0]
|
||||
);
|
||||
|
||||
return hasNonEmptyResolverCall(body, resolverName);
|
||||
});
|
||||
};
|
||||
|
||||
export {
|
||||
hasReturnValue,
|
||||
hasValueOrExecutorHasNonEmptyResolveValue,
|
||||
};
|
||||
Reference in New Issue
Block a user