From ada2c891298382f82dfabf37cacd59a1057b2bb7 Mon Sep 17 00:00:00 2001 From: Milos Djermanovic Date: Fri, 3 Jul 2020 21:50:40 +0200 Subject: [PATCH] Fix: support typescript generics in arrow-parens (fixes #12570) (#13451) --- lib/rules/arrow-parens.js | 199 +++++++------- .../arrow-parens/generics-extends-complex.js | 249 ++++++++++++++++++ .../parsers/arrow-parens/generics-extends.js | 151 +++++++++++ .../arrow-parens/generics-simple-async.js | 128 +++++++++ .../arrow-parens/generics-simple-no-params.js | 106 ++++++++ .../parsers/arrow-parens/generics-simple.js | 119 +++++++++ tests/lib/rules/arrow-parens.js | 134 ++++++++++ 7 files changed, 978 insertions(+), 108 deletions(-) create mode 100644 tests/fixtures/parsers/arrow-parens/generics-extends-complex.js create mode 100644 tests/fixtures/parsers/arrow-parens/generics-extends.js create mode 100644 tests/fixtures/parsers/arrow-parens/generics-simple-async.js create mode 100644 tests/fixtures/parsers/arrow-parens/generics-simple-no-params.js create mode 100644 tests/fixtures/parsers/arrow-parens/generics-simple.js diff --git a/lib/rules/arrow-parens.js b/lib/rules/arrow-parens.js index bfd32447ac6..eaa1aab0238 100644 --- a/lib/rules/arrow-parens.js +++ b/lib/rules/arrow-parens.js @@ -15,15 +15,12 @@ const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ /** - * Get location should be reported by AST node. - * @param {ASTNode} node AST Node. - * @returns {Location} Location information. + * Determines if the given arrow function has block body. + * @param {ASTNode} node `ArrowFunctionExpression` node. + * @returns {boolean} `true` if the function has block body. */ -function getLocation(node) { - return { - start: node.params[0].loc.start, - end: node.params[node.params.length - 1].loc.end - }; +function hasBlockBody(node) { + return node.body.type === "BlockStatement"; } //------------------------------------------------------------------------------ @@ -75,126 +72,112 @@ module.exports = { const sourceCode = context.getSourceCode(); /** - * Determines whether a arrow function argument end with `)` - * @param {ASTNode} node The arrow function node. - * @returns {void} + * Finds opening paren of parameters for the given arrow function, if it exists. + * It is assumed that the given arrow function has exactly one parameter. + * @param {ASTNode} node `ArrowFunctionExpression` node. + * @returns {Token|null} the opening paren, or `null` if the given arrow function doesn't have parens of parameters. */ - function parens(node) { - const isAsync = node.async; - const firstTokenOfParam = sourceCode.getFirstToken(node, isAsync ? 1 : 0); - - /** - * Remove the parenthesis around a parameter - * @param {Fixer} fixer Fixer - * @returns {string} fixed parameter - */ - function fixParamsWithParenthesis(fixer) { - const paramToken = sourceCode.getTokenAfter(firstTokenOfParam); - - /* - * ES8 allows Trailing commas in function parameter lists and calls - * https://github.com/eslint/eslint/issues/8834 - */ - const closingParenToken = sourceCode.getTokenAfter(paramToken, astUtils.isClosingParenToken); - const asyncToken = isAsync ? sourceCode.getTokenBefore(firstTokenOfParam) : null; - const shouldAddSpaceForAsync = asyncToken && (asyncToken.range[1] === firstTokenOfParam.range[0]); - - return fixer.replaceTextRange([ - firstTokenOfParam.range[0], - closingParenToken.range[1] - ], `${shouldAddSpaceForAsync ? " " : ""}${paramToken.value}`); + function findOpeningParenOfParams(node) { + const tokenBeforeParams = sourceCode.getTokenBefore(node.params[0]); + + if ( + tokenBeforeParams && + astUtils.isOpeningParenToken(tokenBeforeParams) && + node.range[0] <= tokenBeforeParams.range[0] + ) { + return tokenBeforeParams; } - /** - * Checks whether there are comments inside the params or not. - * @returns {boolean} `true` if there are comments inside of parens, else `false` - */ - function hasCommentsInParens() { - if (astUtils.isOpeningParenToken(firstTokenOfParam)) { - const closingParenToken = sourceCode.getTokenAfter(node.params[0], astUtils.isClosingParenToken); + return null; + } - return closingParenToken && sourceCode.commentsExistBetween(firstTokenOfParam, closingParenToken); - } - return false; + /** + * Finds closing paren of parameters for the given arrow function. + * It is assumed that the given arrow function has parens of parameters and that it has exactly one parameter. + * @param {ASTNode} node `ArrowFunctionExpression` node. + * @returns {Token} the closing paren of parameters. + */ + function getClosingParenOfParams(node) { + return sourceCode.getTokenAfter(node.params[0], astUtils.isClosingParenToken); + } - } + /** + * Determines whether the given arrow function has comments inside parens of parameters. + * It is assumed that the given arrow function has parens of parameters. + * @param {ASTNode} node `ArrowFunctionExpression` node. + * @param {Token} openingParen Opening paren of parameters. + * @returns {boolean} `true` if the function has at least one comment inside of parens of parameters. + */ + function hasCommentsInParensOfParams(node, openingParen) { + return sourceCode.commentsExistBetween(openingParen, getClosingParenOfParams(node)); + } - if (hasCommentsInParens()) { - return; - } + /** + * Determines whether the given arrow function has unexpected tokens before opening paren of parameters, + * in which case it will be assumed that the existing parens of parameters are necessary. + * Only tokens within the range of the arrow function (tokens that are part of the arrow function) are taken into account. + * Example: (a) => b + * @param {ASTNode} node `ArrowFunctionExpression` node. + * @param {Token} openingParen Opening paren of parameters. + * @returns {boolean} `true` if the function has at least one unexpected token. + */ + function hasUnexpectedTokensBeforeOpeningParen(node, openingParen) { + const expectedCount = node.async ? 1 : 0; - // "as-needed", { "requireForBlockBody": true }: x => x - if ( - requireForBlockBody && - node.params[0].type === "Identifier" && - !node.params[0].typeAnnotation && - node.body.type !== "BlockStatement" && - !node.returnType - ) { - if (astUtils.isOpeningParenToken(firstTokenOfParam)) { - context.report({ - node, - messageId: "unexpectedParensInline", - loc: getLocation(node), - fix: fixParamsWithParenthesis - }); - } - return; - } + return sourceCode.getFirstToken(node, { skip: expectedCount }) !== openingParen; + } - if ( - requireForBlockBody && - node.body.type === "BlockStatement" - ) { - if (!astUtils.isOpeningParenToken(firstTokenOfParam)) { - context.report({ - node, - messageId: "expectedParensBlock", - loc: getLocation(node), - fix(fixer) { - return fixer.replaceText(firstTokenOfParam, `(${firstTokenOfParam.value})`); - } - }); - } - return; - } + return { + "ArrowFunctionExpression[params.length=1]"(node) { + const shouldHaveParens = !asNeeded || requireForBlockBody && hasBlockBody(node); + const openingParen = findOpeningParenOfParams(node); + const hasParens = openingParen !== null; + const [param] = node.params; - // "as-needed": x => x - if (asNeeded && - node.params[0].type === "Identifier" && - !node.params[0].typeAnnotation && - !node.returnType - ) { - if (astUtils.isOpeningParenToken(firstTokenOfParam)) { + if (shouldHaveParens && !hasParens) { context.report({ node, - messageId: "unexpectedParens", - loc: getLocation(node), - fix: fixParamsWithParenthesis + messageId: requireForBlockBody ? "expectedParensBlock" : "expectedParens", + loc: param.loc, + *fix(fixer) { + yield fixer.insertTextBefore(param, "("); + yield fixer.insertTextAfter(param, ")"); + } }); } - return; - } - if (firstTokenOfParam.type === "Identifier") { - const after = sourceCode.getTokenAfter(firstTokenOfParam); - - // (x) => x - if (after.value !== ")") { + if ( + !shouldHaveParens && + hasParens && + param.type === "Identifier" && + !param.typeAnnotation && + !node.returnType && + !hasCommentsInParensOfParams(node, openingParen) && + !hasUnexpectedTokensBeforeOpeningParen(node, openingParen) + ) { context.report({ node, - messageId: "expectedParens", - loc: getLocation(node), - fix(fixer) { - return fixer.replaceText(firstTokenOfParam, `(${firstTokenOfParam.value})`); + messageId: requireForBlockBody ? "unexpectedParensInline" : "unexpectedParens", + loc: param.loc, + *fix(fixer) { + const tokenBeforeOpeningParen = sourceCode.getTokenBefore(openingParen); + const closingParen = getClosingParenOfParams(node); + + if ( + tokenBeforeOpeningParen && + tokenBeforeOpeningParen.range[1] === openingParen.range[0] && + !astUtils.canTokensBeAdjacent(tokenBeforeOpeningParen, sourceCode.getFirstToken(param)) + ) { + yield fixer.insertTextBefore(openingParen, " "); + } + + // remove parens, whitespace inside parens, and possible trailing comma + yield fixer.removeRange([openingParen.range[0], param.range[0]]); + yield fixer.removeRange([param.range[1], closingParen.range[1]]); } }); } } - } - - return { - "ArrowFunctionExpression[params.length=1]": parens }; } }; diff --git a/tests/fixtures/parsers/arrow-parens/generics-extends-complex.js b/tests/fixtures/parsers/arrow-parens/generics-extends-complex.js new file mode 100644 index 00000000000..4a27b495277 --- /dev/null +++ b/tests/fixtures/parsers/arrow-parens/generics-extends-complex.js @@ -0,0 +1,249 @@ +"use strict"; + +/** + * Parser: @typescript-eslint/parser@3.5.0 + * Source code: + * (a) => b + */ + +exports.parse = () => ({ + type: "Program", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "ArrowFunctionExpression", + generator: false, + id: null, + params: [ + { + type: "Identifier", + name: "a", + range: [24, 25], + loc: { + start: { line: 1, column: 24 }, + end: { line: 1, column: 25 }, + }, + }, + ], + body: { + type: "Identifier", + name: "b", + range: [30, 31], + loc: { start: { line: 1, column: 30 }, end: { line: 1, column: 31 } }, + }, + async: false, + expression: true, + range: [0, 31], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 31 } }, + typeParameters: { + type: "TSTypeParameterDeclaration", + range: [0, 23], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 23 } }, + params: [ + { + type: "TSTypeParameter", + name: { + type: "Identifier", + name: "T", + range: [1, 2], + loc: { + start: { line: 1, column: 1 }, + end: { line: 1, column: 2 }, + }, + }, + constraint: { + type: "TSIntersectionType", + types: [ + { + type: "TSParenthesizedType", + typeAnnotation: { + type: "TSUnionType", + types: [ + { + type: "TSTypeReference", + typeName: { + type: "Identifier", + name: "A", + range: [12, 13], + loc: { + start: { line: 1, column: 12 }, + end: { line: 1, column: 13 }, + }, + }, + range: [12, 13], + loc: { + start: { line: 1, column: 12 }, + end: { line: 1, column: 13 }, + }, + }, + { + type: "TSTypeReference", + typeName: { + type: "Identifier", + name: "B", + range: [16, 17], + loc: { + start: { line: 1, column: 16 }, + end: { line: 1, column: 17 }, + }, + }, + range: [16, 17], + loc: { + start: { line: 1, column: 16 }, + end: { line: 1, column: 17 }, + }, + }, + ], + range: [12, 17], + loc: { + start: { line: 1, column: 12 }, + end: { line: 1, column: 17 }, + }, + }, + range: [11, 18], + loc: { + start: { line: 1, column: 11 }, + end: { line: 1, column: 18 }, + }, + }, + { + type: "TSTypeReference", + typeName: { + type: "Identifier", + name: "C", + range: [21, 22], + loc: { + start: { line: 1, column: 21 }, + end: { line: 1, column: 22 }, + }, + }, + range: [21, 22], + loc: { + start: { line: 1, column: 21 }, + end: { line: 1, column: 22 }, + }, + }, + ], + range: [11, 22], + loc: { + start: { line: 1, column: 11 }, + end: { line: 1, column: 22 }, + }, + }, + range: [1, 22], + loc: { + start: { line: 1, column: 1 }, + end: { line: 1, column: 22 }, + }, + }, + ], + }, + }, + range: [0, 31], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 31 } }, + }, + ], + sourceType: "script", + range: [0, 31], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 31 } }, + tokens: [ + { + type: "Punctuator", + value: "<", + range: [0, 1], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } }, + }, + { + type: "Identifier", + value: "T", + range: [1, 2], + loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 2 } }, + }, + { + type: "Keyword", + value: "extends", + range: [3, 10], + loc: { start: { line: 1, column: 3 }, end: { line: 1, column: 10 } }, + }, + { + type: "Punctuator", + value: "(", + range: [11, 12], + loc: { start: { line: 1, column: 11 }, end: { line: 1, column: 12 } }, + }, + { + type: "Identifier", + value: "A", + range: [12, 13], + loc: { start: { line: 1, column: 12 }, end: { line: 1, column: 13 } }, + }, + { + type: "Punctuator", + value: "|", + range: [14, 15], + loc: { start: { line: 1, column: 14 }, end: { line: 1, column: 15 } }, + }, + { + type: "Identifier", + value: "B", + range: [16, 17], + loc: { start: { line: 1, column: 16 }, end: { line: 1, column: 17 } }, + }, + { + type: "Punctuator", + value: ")", + range: [17, 18], + loc: { start: { line: 1, column: 17 }, end: { line: 1, column: 18 } }, + }, + { + type: "Punctuator", + value: "&", + range: [19, 20], + loc: { start: { line: 1, column: 19 }, end: { line: 1, column: 20 } }, + }, + { + type: "Identifier", + value: "C", + range: [21, 22], + loc: { start: { line: 1, column: 21 }, end: { line: 1, column: 22 } }, + }, + { + type: "Punctuator", + value: ">", + range: [22, 23], + loc: { start: { line: 1, column: 22 }, end: { line: 1, column: 23 } }, + }, + { + type: "Punctuator", + value: "(", + range: [23, 24], + loc: { start: { line: 1, column: 23 }, end: { line: 1, column: 24 } }, + }, + { + type: "Identifier", + value: "a", + range: [24, 25], + loc: { start: { line: 1, column: 24 }, end: { line: 1, column: 25 } }, + }, + { + type: "Punctuator", + value: ")", + range: [25, 26], + loc: { start: { line: 1, column: 25 }, end: { line: 1, column: 26 } }, + }, + { + type: "Punctuator", + value: "=>", + range: [27, 29], + loc: { start: { line: 1, column: 27 }, end: { line: 1, column: 29 } }, + }, + { + type: "Identifier", + value: "b", + range: [30, 31], + loc: { start: { line: 1, column: 30 }, end: { line: 1, column: 31 } }, + }, + ], + comments: [], + }); diff --git a/tests/fixtures/parsers/arrow-parens/generics-extends.js b/tests/fixtures/parsers/arrow-parens/generics-extends.js new file mode 100644 index 00000000000..ee70b565635 --- /dev/null +++ b/tests/fixtures/parsers/arrow-parens/generics-extends.js @@ -0,0 +1,151 @@ +"use strict"; + +/** + * Parser: @typescript-eslint/parser@3.5.0 + * Source code: + * (a) => b + */ + +exports.parse = () => ({ + type: "Program", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "ArrowFunctionExpression", + generator: false, + id: null, + params: [ + { + type: "Identifier", + name: "a", + range: [14, 15], + loc: { + start: { line: 1, column: 14 }, + end: { line: 1, column: 15 }, + }, + }, + ], + body: { + type: "Identifier", + name: "b", + range: [20, 21], + loc: { start: { line: 1, column: 20 }, end: { line: 1, column: 21 } }, + }, + async: false, + expression: true, + range: [0, 21], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 21 } }, + typeParameters: { + type: "TSTypeParameterDeclaration", + range: [0, 13], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 13 } }, + params: [ + { + type: "TSTypeParameter", + name: { + type: "Identifier", + name: "T", + range: [1, 2], + loc: { + start: { line: 1, column: 1 }, + end: { line: 1, column: 2 }, + }, + }, + constraint: { + type: "TSTypeReference", + typeName: { + type: "Identifier", + name: "A", + range: [11, 12], + loc: { + start: { line: 1, column: 11 }, + end: { line: 1, column: 12 }, + }, + }, + range: [11, 12], + loc: { + start: { line: 1, column: 11 }, + end: { line: 1, column: 12 }, + }, + }, + range: [1, 12], + loc: { + start: { line: 1, column: 1 }, + end: { line: 1, column: 12 }, + }, + }, + ], + }, + }, + range: [0, 21], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 21 } }, + }, + ], + sourceType: "script", + range: [0, 21], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 21 } }, + tokens: [ + { + type: "Punctuator", + value: "<", + range: [0, 1], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } }, + }, + { + type: "Identifier", + value: "T", + range: [1, 2], + loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 2 } }, + }, + { + type: "Keyword", + value: "extends", + range: [3, 10], + loc: { start: { line: 1, column: 3 }, end: { line: 1, column: 10 } }, + }, + { + type: "Identifier", + value: "A", + range: [11, 12], + loc: { start: { line: 1, column: 11 }, end: { line: 1, column: 12 } }, + }, + { + type: "Punctuator", + value: ">", + range: [12, 13], + loc: { start: { line: 1, column: 12 }, end: { line: 1, column: 13 } }, + }, + { + type: "Punctuator", + value: "(", + range: [13, 14], + loc: { start: { line: 1, column: 13 }, end: { line: 1, column: 14 } }, + }, + { + type: "Identifier", + value: "a", + range: [14, 15], + loc: { start: { line: 1, column: 14 }, end: { line: 1, column: 15 } }, + }, + { + type: "Punctuator", + value: ")", + range: [15, 16], + loc: { start: { line: 1, column: 15 }, end: { line: 1, column: 16 } }, + }, + { + type: "Punctuator", + value: "=>", + range: [17, 19], + loc: { start: { line: 1, column: 17 }, end: { line: 1, column: 19 } }, + }, + { + type: "Identifier", + value: "b", + range: [20, 21], + loc: { start: { line: 1, column: 20 }, end: { line: 1, column: 21 } }, + }, + ], + comments: [], + }); diff --git a/tests/fixtures/parsers/arrow-parens/generics-simple-async.js b/tests/fixtures/parsers/arrow-parens/generics-simple-async.js new file mode 100644 index 00000000000..68926127f2b --- /dev/null +++ b/tests/fixtures/parsers/arrow-parens/generics-simple-async.js @@ -0,0 +1,128 @@ +"use strict"; + +/** + * Parser: @typescript-eslint/parser@3.5.0 + * Source code: + * async (a) => b + */ + +exports.parse = () => ({ + type: "Program", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "ArrowFunctionExpression", + generator: false, + id: null, + params: [ + { + type: "Identifier", + name: "a", + range: [10, 11], + loc: { + start: { line: 1, column: 10 }, + end: { line: 1, column: 11 }, + }, + }, + ], + body: { + type: "Identifier", + name: "b", + range: [16, 17], + loc: { start: { line: 1, column: 16 }, end: { line: 1, column: 17 } }, + }, + async: true, + expression: true, + range: [0, 17], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 17 } }, + typeParameters: { + type: "TSTypeParameterDeclaration", + range: [6, 9], + loc: { start: { line: 1, column: 6 }, end: { line: 1, column: 9 } }, + params: [ + { + type: "TSTypeParameter", + name: { + type: "Identifier", + name: "T", + range: [7, 8], + loc: { + start: { line: 1, column: 7 }, + end: { line: 1, column: 8 }, + }, + }, + range: [7, 8], + loc: { + start: { line: 1, column: 7 }, + end: { line: 1, column: 8 }, + }, + }, + ], + }, + }, + range: [0, 17], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 17 } }, + }, + ], + sourceType: "script", + range: [0, 17], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 17 } }, + tokens: [ + { + type: "Identifier", + value: "async", + range: [0, 5], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 5 } }, + }, + { + type: "Punctuator", + value: "<", + range: [6, 7], + loc: { start: { line: 1, column: 6 }, end: { line: 1, column: 7 } }, + }, + { + type: "Identifier", + value: "T", + range: [7, 8], + loc: { start: { line: 1, column: 7 }, end: { line: 1, column: 8 } }, + }, + { + type: "Punctuator", + value: ">", + range: [8, 9], + loc: { start: { line: 1, column: 8 }, end: { line: 1, column: 9 } }, + }, + { + type: "Punctuator", + value: "(", + range: [9, 10], + loc: { start: { line: 1, column: 9 }, end: { line: 1, column: 10 } }, + }, + { + type: "Identifier", + value: "a", + range: [10, 11], + loc: { start: { line: 1, column: 10 }, end: { line: 1, column: 11 } }, + }, + { + type: "Punctuator", + value: ")", + range: [11, 12], + loc: { start: { line: 1, column: 11 }, end: { line: 1, column: 12 } }, + }, + { + type: "Punctuator", + value: "=>", + range: [13, 15], + loc: { start: { line: 1, column: 13 }, end: { line: 1, column: 15 } }, + }, + { + type: "Identifier", + value: "b", + range: [16, 17], + loc: { start: { line: 1, column: 16 }, end: { line: 1, column: 17 } }, + }, + ], + comments: [], + }); diff --git a/tests/fixtures/parsers/arrow-parens/generics-simple-no-params.js b/tests/fixtures/parsers/arrow-parens/generics-simple-no-params.js new file mode 100644 index 00000000000..ab17a7d8804 --- /dev/null +++ b/tests/fixtures/parsers/arrow-parens/generics-simple-no-params.js @@ -0,0 +1,106 @@ +"use strict"; + +/** + * Parser: @typescript-eslint/parser@3.5.0 + * Source code: + * () => b + */ + +exports.parse = () => ({ + type: "Program", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "ArrowFunctionExpression", + generator: false, + id: null, + params: [], + body: { + type: "Identifier", + name: "b", + range: [9, 10], + loc: { start: { line: 1, column: 9 }, end: { line: 1, column: 10 } }, + }, + async: false, + expression: true, + range: [0, 10], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 10 } }, + typeParameters: { + type: "TSTypeParameterDeclaration", + range: [0, 3], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 3 } }, + params: [ + { + type: "TSTypeParameter", + name: { + type: "Identifier", + name: "T", + range: [1, 2], + loc: { + start: { line: 1, column: 1 }, + end: { line: 1, column: 2 }, + }, + }, + range: [1, 2], + loc: { + start: { line: 1, column: 1 }, + end: { line: 1, column: 2 }, + }, + }, + ], + }, + }, + range: [0, 10], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 10 } }, + }, + ], + sourceType: "script", + range: [0, 10], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 10 } }, + tokens: [ + { + type: "Punctuator", + value: "<", + range: [0, 1], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } }, + }, + { + type: "Identifier", + value: "T", + range: [1, 2], + loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 2 } }, + }, + { + type: "Punctuator", + value: ">", + range: [2, 3], + loc: { start: { line: 1, column: 2 }, end: { line: 1, column: 3 } }, + }, + { + type: "Punctuator", + value: "(", + range: [3, 4], + loc: { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, + }, + { + type: "Punctuator", + value: ")", + range: [4, 5], + loc: { start: { line: 1, column: 4 }, end: { line: 1, column: 5 } }, + }, + { + type: "Punctuator", + value: "=>", + range: [6, 8], + loc: { start: { line: 1, column: 6 }, end: { line: 1, column: 8 } }, + }, + { + type: "Identifier", + value: "b", + range: [9, 10], + loc: { start: { line: 1, column: 9 }, end: { line: 1, column: 10 } }, + }, + ], + comments: [], + }); diff --git a/tests/fixtures/parsers/arrow-parens/generics-simple.js b/tests/fixtures/parsers/arrow-parens/generics-simple.js new file mode 100644 index 00000000000..92f1a5bdb81 --- /dev/null +++ b/tests/fixtures/parsers/arrow-parens/generics-simple.js @@ -0,0 +1,119 @@ +"use strict"; + +/** + * Parser: @typescript-eslint/parser@3.5.0 + * Source code: + * (a) => b + */ + +exports.parse = () => ({ + type: "Program", + body: [ + { + type: "ExpressionStatement", + expression: { + type: "ArrowFunctionExpression", + generator: false, + id: null, + params: [ + { + type: "Identifier", + name: "a", + range: [4, 5], + loc: { start: { line: 1, column: 4 }, end: { line: 1, column: 5 } }, + }, + ], + body: { + type: "Identifier", + name: "b", + range: [10, 11], + loc: { start: { line: 1, column: 10 }, end: { line: 1, column: 11 } }, + }, + async: false, + expression: true, + range: [0, 11], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 11 } }, + typeParameters: { + type: "TSTypeParameterDeclaration", + range: [0, 3], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 3 } }, + params: [ + { + type: "TSTypeParameter", + name: { + type: "Identifier", + name: "T", + range: [1, 2], + loc: { + start: { line: 1, column: 1 }, + end: { line: 1, column: 2 }, + }, + }, + range: [1, 2], + loc: { + start: { line: 1, column: 1 }, + end: { line: 1, column: 2 }, + }, + }, + ], + }, + }, + range: [0, 11], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 11 } }, + }, + ], + sourceType: "script", + range: [0, 11], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 11 } }, + tokens: [ + { + type: "Punctuator", + value: "<", + range: [0, 1], + loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } }, + }, + { + type: "Identifier", + value: "T", + range: [1, 2], + loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 2 } }, + }, + { + type: "Punctuator", + value: ">", + range: [2, 3], + loc: { start: { line: 1, column: 2 }, end: { line: 1, column: 3 } }, + }, + { + type: "Punctuator", + value: "(", + range: [3, 4], + loc: { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, + }, + { + type: "Identifier", + value: "a", + range: [4, 5], + loc: { start: { line: 1, column: 4 }, end: { line: 1, column: 5 } }, + }, + { + type: "Punctuator", + value: ")", + range: [5, 6], + loc: { start: { line: 1, column: 5 }, end: { line: 1, column: 6 } }, + }, + { + type: "Punctuator", + value: "=>", + range: [7, 9], + loc: { start: { line: 1, column: 7 }, end: { line: 1, column: 9 } }, + }, + { + type: "Identifier", + value: "b", + range: [10, 11], + loc: { start: { line: 1, column: 10 }, end: { line: 1, column: 11 } }, + }, + ], + comments: [], + }); diff --git a/tests/lib/rules/arrow-parens.js b/tests/lib/rules/arrow-parens.js index edd1ae6da8a..61d76358440 100644 --- a/tests/lib/rules/arrow-parens.js +++ b/tests/lib/rules/arrow-parens.js @@ -45,16 +45,22 @@ const valid = [ { code: "a.then((foo) => {});", options: ["always"] }, { code: "a.then((foo) => { if (true) {}; });", options: ["always"] }, { code: "a.then(async (foo) => { if (true) {}; });", options: ["always"], parserOptions: { ecmaVersion: 8 } }, + { code: "(a: T) => a", options: ["always"], parser: parser("identifer-type") }, + { code: "(a): T => a", options: ["always"], parser: parser("return-type") }, // "as-needed" { code: "() => {}", options: ["as-needed"] }, { code: "a => {}", options: ["as-needed"] }, { code: "a => a", options: ["as-needed"] }, + { code: "a => (a)", options: ["as-needed"] }, + { code: "(a => a)", options: ["as-needed"] }, + { code: "((a => a))", options: ["as-needed"] }, { code: "([a, b]) => {}", options: ["as-needed"] }, { code: "({ a, b }) => {}", options: ["as-needed"] }, { code: "(a = 10) => {}", options: ["as-needed"] }, { code: "(...a) => a[0]", options: ["as-needed"] }, { code: "(a, b) => {}", options: ["as-needed"] }, + { code: "async a => a", options: ["as-needed"], parserOptions: { ecmaVersion: 8 } }, { code: "async ([a, b]) => {}", options: ["as-needed"], parserOptions: { ecmaVersion: 8 } }, { code: "async (a, b) => {}", options: ["as-needed"], parserOptions: { ecmaVersion: 8 } }, { code: "(a: T) => a", options: ["as-needed"], parser: parser("identifer-type") }, @@ -63,6 +69,9 @@ const valid = [ // "as-needed", { "requireForBlockBody": true } { code: "() => {}", options: ["as-needed", { requireForBlockBody: true }] }, { code: "a => a", options: ["as-needed", { requireForBlockBody: true }] }, + { code: "a => (a)", options: ["as-needed", { requireForBlockBody: true }] }, + { code: "(a => a)", options: ["as-needed", { requireForBlockBody: true }] }, + { code: "((a => a))", options: ["as-needed", { requireForBlockBody: true }] }, { code: "([a, b]) => {}", options: ["as-needed", { requireForBlockBody: true }] }, { code: "([a, b]) => a", options: ["as-needed", { requireForBlockBody: true }] }, { code: "({ a, b }) => {}", options: ["as-needed", { requireForBlockBody: true }] }, @@ -136,6 +145,83 @@ const valid = [ { code: "var bar = (/*comment here*/{a}) => a", options: ["as-needed"] + }, + + // generics + { + code: "(a) => b", + options: ["always"], + parser: parser("generics-simple") + }, + { + code: "(a) => b", + options: ["as-needed"], + parser: parser("generics-simple") + }, + { + code: "(a) => b", + options: ["as-needed", { requireForBlockBody: true }], + parser: parser("generics-simple") + }, + { + code: "async (a) => b", + options: ["always"], + parser: parser("generics-simple-async") + }, + { + code: "async (a) => b", + options: ["as-needed"], + parser: parser("generics-simple-async") + }, + { + code: "async (a) => b", + options: ["as-needed", { requireForBlockBody: true }], + parser: parser("generics-simple-async") + }, + { + code: "() => b", + options: ["always"], + parser: parser("generics-simple-no-params") + }, + { + code: "() => b", + options: ["as-needed"], + parser: parser("generics-simple-no-params") + }, + { + code: "() => b", + options: ["as-needed", { requireForBlockBody: true }], + parser: parser("generics-simple-no-params") + }, + { + code: "(a) => b", + options: ["always"], + parser: parser("generics-extends") + }, + { + code: "(a) => b", + options: ["as-needed"], + parser: parser("generics-extends") + }, + { + code: "(a) => b", + options: ["as-needed", { requireForBlockBody: true }], + parser: parser("generics-extends") + }, + { + code: "(a) => b", + options: ["always"], + parser: parser("generics-extends-complex") + }, + { + code: "(a) => b", + options: ["as-needed"], + parser: parser("generics-extends-complex") + }, + { + code: "(a) => b", + options: ["as-needed", { requireForBlockBody: true }], + parser: parser("generics-extends-complex") } ]; @@ -236,6 +322,30 @@ const invalid = [ type }] }, + { + code: "( a ) => b", + output: "a => b", + options: ["as-needed"], + errors: [{ + line: 1, + column: 4, + endColumn: 5, + messageId: "unexpectedParens", + type + }] + }, + { + code: "(\na\n) => b", + output: "a => b", + options: ["as-needed"], + errors: [{ + line: 2, + column: 1, + endColumn: 2, + messageId: "unexpectedParens", + type + }] + }, { code: "(a,) => a", output: "a => a", @@ -275,6 +385,30 @@ const invalid = [ type }] }, + { + code: "typeof((a) => {})", + output: "typeof(a => {})", + options: ["as-needed"], + errors: [{ + line: 1, + column: 9, + endColumn: 10, + messageId: "unexpectedParens", + type + }] + }, + { + code: "function *f() { yield(a) => a; }", + output: "function *f() { yield a => a; }", + options: ["as-needed"], + errors: [{ + line: 1, + column: 23, + endColumn: 24, + messageId: "unexpectedParens", + type + }] + }, // "as-needed", { "requireForBlockBody": true } {