507 lines
15 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _exportParser = _interopRequireDefault(require("../exportParser.cjs"));
var _iterateJsdoc = require("../iterateJsdoc.cjs");
var _jsdocUtils = require("../jsdocUtils.cjs");
var _jsdoccomment = require("@es-joy/jsdoccomment");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/**
* @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} */
var _default = exports.default = {
create(context) {
/* c8 ignore next -- Fallback to deprecated method */
const {
sourceCode = context.getSourceCode()
} = context;
const settings = (0, _iterateJsdoc.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 => {
var _sourceCode$getText$m;
return count !== undefined && count > (((_sourceCode$getText$m = sourceCode.getText(node).match(/\n/gu)) === null || _sourceCode$getText$m === void 0 ? void 0 : _sourceCode$getText$m.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 = (0, _jsdoccomment.getJSDocComment)(sourceCode, node, settings);
if (jsDocNode) {
return;
}
// For those who have options configured against ANY constructors (or
// setters or getters) being reported
if ((0, _jsdocUtils.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 && (0, _jsdocUtils.isConstructor)(node)) {
const functionParameterNames = (0, _jsdocUtils.getFunctionParameterNames)(node);
if (!functionParameterNames.length && !(0, _jsdocUtils.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 = (0, _jsdoccomment.getReducedASTNode)(node, sourceCode);
const decorator = (0, _jsdoccomment.getDecorator)( /** @type {import('eslint').Rule.Node} */
baseNode);
if (decorator) {
baseNode = decorator;
}
const indent = (0, _jsdocUtils.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 === null || publicOnly === void 0 ? void 0 : publicOnly.ancestorsOnly) ?? false),
esm: Boolean((publicOnly === null || publicOnly === void 0 ? void 0 : publicOnly.esm) ?? true),
initModuleExports: Boolean((publicOnly === null || publicOnly === void 0 ? void 0 : publicOnly.cjs) ?? true),
initWindow: Boolean((publicOnly === null || publicOnly === void 0 ? void 0 : publicOnly.window) ?? false)
};
const exported = _exportParser.default.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 {
...(0, _jsdocUtils.getContextObject)((0, _jsdocUtils.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'
}
};
module.exports = exports.default;
//# sourceMappingURL=requireJsdoc.cjs.map