/** * @fileoverview Rule to disallow empty functions. * @author Toru Nagashima */ 'use strict'; //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ const astUtils = require('./utils/ast-utils'); //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ const ALLOW_OPTIONS = Object.freeze([ 'functions', 'arrowFunctions', 'generatorFunctions', 'methods', 'generatorMethods', 'getters', 'setters', 'constructors', 'asyncFunctions', 'asyncMethods', ]); /** * Gets the kind of a given function node. * @param {ASTNode} node A function node to get. This is one of * an ArrowFunctionExpression, a FunctionDeclaration, or a * FunctionExpression. * @returns {string} The kind of the function. This is one of "functions", * "arrowFunctions", "generatorFunctions", "asyncFunctions", "methods", * "generatorMethods", "asyncMethods", "getters", "setters", and * "constructors". */ function getKind(node) { const parent = node.parent; let kind; if (node.type === 'ArrowFunctionExpression') { return 'arrowFunctions'; } // Detects main kind. if (parent.type === 'Property') { if (parent.kind === 'get') { return 'getters'; } if (parent.kind === 'set') { return 'setters'; } kind = parent.method ? 'methods' : 'functions'; } else if (parent.type === 'MethodDefinition') { if (parent.kind === 'get') { return 'getters'; } if (parent.kind === 'set') { return 'setters'; } if (parent.kind === 'constructor') { return 'constructors'; } kind = 'methods'; } else { kind = 'functions'; } // Detects prefix. let prefix; if (node.generator) { prefix = 'generator'; } else if (node.async) { prefix = 'async'; } else { return kind; } return prefix + kind[0].toUpperCase() + kind.slice(1); } //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ /** @type {import('../types').Rule.RuleModule} */ module.exports = { meta: { type: 'suggestion', defaultOptions: [{ allow: [] }], docs: { description: 'Disallow empty functions', recommended: false, url: 'https://eslint.org/docs/latest/rules/no-empty-function', }, schema: [ { type: 'object', properties: { allow: { type: 'array', items: { enum: ALLOW_OPTIONS }, uniqueItems: true, }, }, additionalProperties: false, }, ], messages: { unexpected: 'Unexpected empty {{name}}.', }, }, create(context) { const [{ allow }] = context.options; const sourceCode = context.sourceCode; /** * Reports a given function node if the node matches the following patterns. * * - Not allowed by options. * - The body is empty. * - The body doesn't have any comments. * @param {ASTNode} node A function node to report. This is one of * an ArrowFunctionExpression, a FunctionDeclaration, or a * FunctionExpression. * @returns {void} */ function reportIfEmpty(node) { const kind = getKind(node); const name = astUtils.getFunctionNameWithKind(node); const innerComments = sourceCode.getTokens(node.body, { includeComments: true, filter: astUtils.isCommentToken, }); if ( !allow.includes(kind) && node.body.type === 'BlockStatement' && node.body.body.length === 0 && innerComments.length === 0 ) { context.report({ node, loc: node.body.loc, messageId: 'unexpected', data: { name }, }); } } return { ArrowFunctionExpression: reportIfEmpty, FunctionDeclaration: reportIfEmpty, FunctionExpression: reportIfEmpty, }; }, };