|
|
/** * @fileoverview Rule to validate spacing before function paren. * @author Mathias Schreck <https://github.com/lo1tuma>
*/ "use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = { meta: { type: "layout",
docs: { description: "enforce consistent spacing before `function` definition opening parenthesis", category: "Stylistic Issues", recommended: false, url: "https://eslint.org/docs/rules/space-before-function-paren" },
fixable: "whitespace",
schema: [ { oneOf: [ { enum: ["always", "never"] }, { type: "object", properties: { anonymous: { enum: ["always", "never", "ignore"] }, named: { enum: ["always", "never", "ignore"] }, asyncArrow: { enum: ["always", "never", "ignore"] } }, additionalProperties: false } ] } ],
messages: { unexpectedSpace: "Unexpected space before function parentheses.", missingSpace: "Missing space before function parentheses." } },
create(context) { const sourceCode = context.getSourceCode(); const baseConfig = typeof context.options[0] === "string" ? context.options[0] : "always"; const overrideConfig = typeof context.options[0] === "object" ? context.options[0] : {};
/** * Determines whether a function has a name. * @param {ASTNode} node The function node. * @returns {boolean} Whether the function has a name. */ function isNamedFunction(node) { if (node.id) { return true; }
const parent = node.parent;
return parent.type === "MethodDefinition" || (parent.type === "Property" && ( parent.kind === "get" || parent.kind === "set" || parent.method ) ); }
/** * Gets the config for a given function * @param {ASTNode} node The function node * @returns {string} "always", "never", or "ignore" */ function getConfigForFunction(node) { if (node.type === "ArrowFunctionExpression") {
// Always ignore non-async functions and arrow functions without parens, e.g. async foo => bar
if (node.async && astUtils.isOpeningParenToken(sourceCode.getFirstToken(node, { skip: 1 }))) { return overrideConfig.asyncArrow || baseConfig; } } else if (isNamedFunction(node)) { return overrideConfig.named || baseConfig;
// `generator-star-spacing` should warn anonymous generators. E.g. `function* () {}`
} else if (!node.generator) { return overrideConfig.anonymous || baseConfig; }
return "ignore"; }
/** * Checks the parens of a function node * @param {ASTNode} node A function node * @returns {void} */ function checkFunction(node) { const functionConfig = getConfigForFunction(node);
if (functionConfig === "ignore") { return; }
const rightToken = sourceCode.getFirstToken(node, astUtils.isOpeningParenToken); const leftToken = sourceCode.getTokenBefore(rightToken); const hasSpacing = sourceCode.isSpaceBetweenTokens(leftToken, rightToken);
if (hasSpacing && functionConfig === "never") { context.report({ node, loc: { start: leftToken.loc.end, end: rightToken.loc.start }, messageId: "unexpectedSpace", fix(fixer) { const comments = sourceCode.getCommentsBefore(rightToken);
// Don't fix anything if there's a single line comment between the left and the right token
if (comments.some(comment => comment.type === "Line")) { return null; } return fixer.replaceTextRange( [leftToken.range[1], rightToken.range[0]], comments.reduce((text, comment) => text + sourceCode.getText(comment), "") ); } }); } else if (!hasSpacing && functionConfig === "always") { context.report({ node, loc: rightToken.loc, messageId: "missingSpace", fix: fixer => fixer.insertTextAfter(leftToken, " ") }); } }
return { ArrowFunctionExpression: checkFunction, FunctionDeclaration: checkFunction, FunctionExpression: checkFunction }; } };
|