[d565449] | 1 | /**
|
---|
| 2 | * @fileoverview enforce consistent line breaks inside function parentheses
|
---|
| 3 | * @author Teddy Katz
|
---|
| 4 | * @deprecated in ESLint v8.53.0
|
---|
| 5 | */
|
---|
| 6 | "use strict";
|
---|
| 7 |
|
---|
| 8 | //------------------------------------------------------------------------------
|
---|
| 9 | // Requirements
|
---|
| 10 | //------------------------------------------------------------------------------
|
---|
| 11 |
|
---|
| 12 | const astUtils = require("./utils/ast-utils");
|
---|
| 13 |
|
---|
| 14 | //------------------------------------------------------------------------------
|
---|
| 15 | // Rule Definition
|
---|
| 16 | //------------------------------------------------------------------------------
|
---|
| 17 |
|
---|
| 18 | /** @type {import('../shared/types').Rule} */
|
---|
| 19 | module.exports = {
|
---|
| 20 | meta: {
|
---|
| 21 | deprecated: true,
|
---|
| 22 | replacedBy: [],
|
---|
| 23 | type: "layout",
|
---|
| 24 |
|
---|
| 25 | docs: {
|
---|
| 26 | description: "Enforce consistent line breaks inside function parentheses",
|
---|
| 27 | recommended: false,
|
---|
| 28 | url: "https://eslint.org/docs/latest/rules/function-paren-newline"
|
---|
| 29 | },
|
---|
| 30 |
|
---|
| 31 | fixable: "whitespace",
|
---|
| 32 |
|
---|
| 33 | schema: [
|
---|
| 34 | {
|
---|
| 35 | oneOf: [
|
---|
| 36 | {
|
---|
| 37 | enum: ["always", "never", "consistent", "multiline", "multiline-arguments"]
|
---|
| 38 | },
|
---|
| 39 | {
|
---|
| 40 | type: "object",
|
---|
| 41 | properties: {
|
---|
| 42 | minItems: {
|
---|
| 43 | type: "integer",
|
---|
| 44 | minimum: 0
|
---|
| 45 | }
|
---|
| 46 | },
|
---|
| 47 | additionalProperties: false
|
---|
| 48 | }
|
---|
| 49 | ]
|
---|
| 50 | }
|
---|
| 51 | ],
|
---|
| 52 |
|
---|
| 53 | messages: {
|
---|
| 54 | expectedBefore: "Expected newline before ')'.",
|
---|
| 55 | expectedAfter: "Expected newline after '('.",
|
---|
| 56 | expectedBetween: "Expected newline between arguments/params.",
|
---|
| 57 | unexpectedBefore: "Unexpected newline before ')'.",
|
---|
| 58 | unexpectedAfter: "Unexpected newline after '('."
|
---|
| 59 | }
|
---|
| 60 | },
|
---|
| 61 |
|
---|
| 62 | create(context) {
|
---|
| 63 | const sourceCode = context.sourceCode;
|
---|
| 64 | const rawOption = context.options[0] || "multiline";
|
---|
| 65 | const multilineOption = rawOption === "multiline";
|
---|
| 66 | const multilineArgumentsOption = rawOption === "multiline-arguments";
|
---|
| 67 | const consistentOption = rawOption === "consistent";
|
---|
| 68 | let minItems;
|
---|
| 69 |
|
---|
| 70 | if (typeof rawOption === "object") {
|
---|
| 71 | minItems = rawOption.minItems;
|
---|
| 72 | } else if (rawOption === "always") {
|
---|
| 73 | minItems = 0;
|
---|
| 74 | } else if (rawOption === "never") {
|
---|
| 75 | minItems = Infinity;
|
---|
| 76 | } else {
|
---|
| 77 | minItems = null;
|
---|
| 78 | }
|
---|
| 79 |
|
---|
| 80 | //----------------------------------------------------------------------
|
---|
| 81 | // Helpers
|
---|
| 82 | //----------------------------------------------------------------------
|
---|
| 83 |
|
---|
| 84 | /**
|
---|
| 85 | * Determines whether there should be newlines inside function parens
|
---|
| 86 | * @param {ASTNode[]} elements The arguments or parameters in the list
|
---|
| 87 | * @param {boolean} hasLeftNewline `true` if the left paren has a newline in the current code.
|
---|
| 88 | * @returns {boolean} `true` if there should be newlines inside the function parens
|
---|
| 89 | */
|
---|
| 90 | function shouldHaveNewlines(elements, hasLeftNewline) {
|
---|
| 91 | if (multilineArgumentsOption && elements.length === 1) {
|
---|
| 92 | return hasLeftNewline;
|
---|
| 93 | }
|
---|
| 94 | if (multilineOption || multilineArgumentsOption) {
|
---|
| 95 | return elements.some((element, index) => index !== elements.length - 1 && element.loc.end.line !== elements[index + 1].loc.start.line);
|
---|
| 96 | }
|
---|
| 97 | if (consistentOption) {
|
---|
| 98 | return hasLeftNewline;
|
---|
| 99 | }
|
---|
| 100 | return elements.length >= minItems;
|
---|
| 101 | }
|
---|
| 102 |
|
---|
| 103 | /**
|
---|
| 104 | * Validates parens
|
---|
| 105 | * @param {Object} parens An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token
|
---|
| 106 | * @param {ASTNode[]} elements The arguments or parameters in the list
|
---|
| 107 | * @returns {void}
|
---|
| 108 | */
|
---|
| 109 | function validateParens(parens, elements) {
|
---|
| 110 | const leftParen = parens.leftParen;
|
---|
| 111 | const rightParen = parens.rightParen;
|
---|
| 112 | const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParen);
|
---|
| 113 | const tokenBeforeRightParen = sourceCode.getTokenBefore(rightParen);
|
---|
| 114 | const hasLeftNewline = !astUtils.isTokenOnSameLine(leftParen, tokenAfterLeftParen);
|
---|
| 115 | const hasRightNewline = !astUtils.isTokenOnSameLine(tokenBeforeRightParen, rightParen);
|
---|
| 116 | const needsNewlines = shouldHaveNewlines(elements, hasLeftNewline);
|
---|
| 117 |
|
---|
| 118 | if (hasLeftNewline && !needsNewlines) {
|
---|
| 119 | context.report({
|
---|
| 120 | node: leftParen,
|
---|
| 121 | messageId: "unexpectedAfter",
|
---|
| 122 | fix(fixer) {
|
---|
| 123 | return sourceCode.getText().slice(leftParen.range[1], tokenAfterLeftParen.range[0]).trim()
|
---|
| 124 |
|
---|
| 125 | // If there is a comment between the ( and the first element, don't do a fix.
|
---|
| 126 | ? null
|
---|
| 127 | : fixer.removeRange([leftParen.range[1], tokenAfterLeftParen.range[0]]);
|
---|
| 128 | }
|
---|
| 129 | });
|
---|
| 130 | } else if (!hasLeftNewline && needsNewlines) {
|
---|
| 131 | context.report({
|
---|
| 132 | node: leftParen,
|
---|
| 133 | messageId: "expectedAfter",
|
---|
| 134 | fix: fixer => fixer.insertTextAfter(leftParen, "\n")
|
---|
| 135 | });
|
---|
| 136 | }
|
---|
| 137 |
|
---|
| 138 | if (hasRightNewline && !needsNewlines) {
|
---|
| 139 | context.report({
|
---|
| 140 | node: rightParen,
|
---|
| 141 | messageId: "unexpectedBefore",
|
---|
| 142 | fix(fixer) {
|
---|
| 143 | return sourceCode.getText().slice(tokenBeforeRightParen.range[1], rightParen.range[0]).trim()
|
---|
| 144 |
|
---|
| 145 | // If there is a comment between the last element and the ), don't do a fix.
|
---|
| 146 | ? null
|
---|
| 147 | : fixer.removeRange([tokenBeforeRightParen.range[1], rightParen.range[0]]);
|
---|
| 148 | }
|
---|
| 149 | });
|
---|
| 150 | } else if (!hasRightNewline && needsNewlines) {
|
---|
| 151 | context.report({
|
---|
| 152 | node: rightParen,
|
---|
| 153 | messageId: "expectedBefore",
|
---|
| 154 | fix: fixer => fixer.insertTextBefore(rightParen, "\n")
|
---|
| 155 | });
|
---|
| 156 | }
|
---|
| 157 | }
|
---|
| 158 |
|
---|
| 159 | /**
|
---|
| 160 | * Validates a list of arguments or parameters
|
---|
| 161 | * @param {Object} parens An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token
|
---|
| 162 | * @param {ASTNode[]} elements The arguments or parameters in the list
|
---|
| 163 | * @returns {void}
|
---|
| 164 | */
|
---|
| 165 | function validateArguments(parens, elements) {
|
---|
| 166 | const leftParen = parens.leftParen;
|
---|
| 167 | const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParen);
|
---|
| 168 | const hasLeftNewline = !astUtils.isTokenOnSameLine(leftParen, tokenAfterLeftParen);
|
---|
| 169 | const needsNewlines = shouldHaveNewlines(elements, hasLeftNewline);
|
---|
| 170 |
|
---|
| 171 | for (let i = 0; i <= elements.length - 2; i++) {
|
---|
| 172 | const currentElement = elements[i];
|
---|
| 173 | const nextElement = elements[i + 1];
|
---|
| 174 | const hasNewLine = currentElement.loc.end.line !== nextElement.loc.start.line;
|
---|
| 175 |
|
---|
| 176 | if (!hasNewLine && needsNewlines) {
|
---|
| 177 | context.report({
|
---|
| 178 | node: currentElement,
|
---|
| 179 | messageId: "expectedBetween",
|
---|
| 180 | fix: fixer => fixer.insertTextBefore(nextElement, "\n")
|
---|
| 181 | });
|
---|
| 182 | }
|
---|
| 183 | }
|
---|
| 184 | }
|
---|
| 185 |
|
---|
| 186 | /**
|
---|
| 187 | * Gets the left paren and right paren tokens of a node.
|
---|
| 188 | * @param {ASTNode} node The node with parens
|
---|
| 189 | * @throws {TypeError} Unexpected node type.
|
---|
| 190 | * @returns {Object} An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token.
|
---|
| 191 | * Can also return `null` if an expression has no parens (e.g. a NewExpression with no arguments, or an ArrowFunctionExpression
|
---|
| 192 | * with a single parameter)
|
---|
| 193 | */
|
---|
| 194 | function getParenTokens(node) {
|
---|
| 195 | switch (node.type) {
|
---|
| 196 | case "NewExpression":
|
---|
| 197 | if (!node.arguments.length &&
|
---|
| 198 | !(
|
---|
| 199 | astUtils.isOpeningParenToken(sourceCode.getLastToken(node, { skip: 1 })) &&
|
---|
| 200 | astUtils.isClosingParenToken(sourceCode.getLastToken(node)) &&
|
---|
| 201 | node.callee.range[1] < node.range[1]
|
---|
| 202 | )
|
---|
| 203 | ) {
|
---|
| 204 |
|
---|
| 205 | // If the NewExpression does not have parens (e.g. `new Foo`), return null.
|
---|
| 206 | return null;
|
---|
| 207 | }
|
---|
| 208 |
|
---|
| 209 | // falls through
|
---|
| 210 |
|
---|
| 211 | case "CallExpression":
|
---|
| 212 | return {
|
---|
| 213 | leftParen: sourceCode.getTokenAfter(node.callee, astUtils.isOpeningParenToken),
|
---|
| 214 | rightParen: sourceCode.getLastToken(node)
|
---|
| 215 | };
|
---|
| 216 |
|
---|
| 217 | case "FunctionDeclaration":
|
---|
| 218 | case "FunctionExpression": {
|
---|
| 219 | const leftParen = sourceCode.getFirstToken(node, astUtils.isOpeningParenToken);
|
---|
| 220 | const rightParen = node.params.length
|
---|
| 221 | ? sourceCode.getTokenAfter(node.params[node.params.length - 1], astUtils.isClosingParenToken)
|
---|
| 222 | : sourceCode.getTokenAfter(leftParen);
|
---|
| 223 |
|
---|
| 224 | return { leftParen, rightParen };
|
---|
| 225 | }
|
---|
| 226 |
|
---|
| 227 | case "ArrowFunctionExpression": {
|
---|
| 228 | const firstToken = sourceCode.getFirstToken(node, { skip: (node.async ? 1 : 0) });
|
---|
| 229 |
|
---|
| 230 | if (!astUtils.isOpeningParenToken(firstToken)) {
|
---|
| 231 |
|
---|
| 232 | // If the ArrowFunctionExpression has a single param without parens, return null.
|
---|
| 233 | return null;
|
---|
| 234 | }
|
---|
| 235 |
|
---|
| 236 | const rightParen = node.params.length
|
---|
| 237 | ? sourceCode.getTokenAfter(node.params[node.params.length - 1], astUtils.isClosingParenToken)
|
---|
| 238 | : sourceCode.getTokenAfter(firstToken);
|
---|
| 239 |
|
---|
| 240 | return {
|
---|
| 241 | leftParen: firstToken,
|
---|
| 242 | rightParen
|
---|
| 243 | };
|
---|
| 244 | }
|
---|
| 245 |
|
---|
| 246 | case "ImportExpression": {
|
---|
| 247 | const leftParen = sourceCode.getFirstToken(node, 1);
|
---|
| 248 | const rightParen = sourceCode.getLastToken(node);
|
---|
| 249 |
|
---|
| 250 | return { leftParen, rightParen };
|
---|
| 251 | }
|
---|
| 252 |
|
---|
| 253 | default:
|
---|
| 254 | throw new TypeError(`unexpected node with type ${node.type}`);
|
---|
| 255 | }
|
---|
| 256 | }
|
---|
| 257 |
|
---|
| 258 | //----------------------------------------------------------------------
|
---|
| 259 | // Public
|
---|
| 260 | //----------------------------------------------------------------------
|
---|
| 261 |
|
---|
| 262 | return {
|
---|
| 263 | [[
|
---|
| 264 | "ArrowFunctionExpression",
|
---|
| 265 | "CallExpression",
|
---|
| 266 | "FunctionDeclaration",
|
---|
| 267 | "FunctionExpression",
|
---|
| 268 | "ImportExpression",
|
---|
| 269 | "NewExpression"
|
---|
| 270 | ]](node) {
|
---|
| 271 | const parens = getParenTokens(node);
|
---|
| 272 | let params;
|
---|
| 273 |
|
---|
| 274 | if (node.type === "ImportExpression") {
|
---|
| 275 | params = [node.source];
|
---|
| 276 | } else if (astUtils.isFunction(node)) {
|
---|
| 277 | params = node.params;
|
---|
| 278 | } else {
|
---|
| 279 | params = node.arguments;
|
---|
| 280 | }
|
---|
| 281 |
|
---|
| 282 | if (parens) {
|
---|
| 283 | validateParens(parens, params);
|
---|
| 284 |
|
---|
| 285 | if (multilineArgumentsOption) {
|
---|
| 286 | validateArguments(parens, params);
|
---|
| 287 | }
|
---|
| 288 | }
|
---|
| 289 | }
|
---|
| 290 | };
|
---|
| 291 | }
|
---|
| 292 | };
|
---|