Permalink
Cannot retrieve contributors at this time
296 lines (259 sloc)
12.7 KB
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
codeql-action/node_modules/eslint/lib/rules/arrow-body-style.js
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @fileoverview Rule to require braces in arrow function body. | |
* @author Alberto Rodríguez | |
*/ | |
"use strict"; | |
//------------------------------------------------------------------------------ | |
// Requirements | |
//------------------------------------------------------------------------------ | |
const astUtils = require("./utils/ast-utils"); | |
//------------------------------------------------------------------------------ | |
// Rule Definition | |
//------------------------------------------------------------------------------ | |
/** @type {import('../shared/types').Rule} */ | |
module.exports = { | |
meta: { | |
type: "suggestion", | |
docs: { | |
description: "Require braces around arrow function bodies", | |
recommended: false, | |
url: "https://eslint.org/docs/latest/rules/arrow-body-style" | |
}, | |
schema: { | |
anyOf: [ | |
{ | |
type: "array", | |
items: [ | |
{ | |
enum: ["always", "never"] | |
} | |
], | |
minItems: 0, | |
maxItems: 1 | |
}, | |
{ | |
type: "array", | |
items: [ | |
{ | |
enum: ["as-needed"] | |
}, | |
{ | |
type: "object", | |
properties: { | |
requireReturnForObjectLiteral: { type: "boolean" } | |
}, | |
additionalProperties: false | |
} | |
], | |
minItems: 0, | |
maxItems: 2 | |
} | |
] | |
}, | |
fixable: "code", | |
messages: { | |
unexpectedOtherBlock: "Unexpected block statement surrounding arrow body.", | |
unexpectedEmptyBlock: "Unexpected block statement surrounding arrow body; put a value of `undefined` immediately after the `=>`.", | |
unexpectedObjectBlock: "Unexpected block statement surrounding arrow body; parenthesize the returned value and move it immediately after the `=>`.", | |
unexpectedSingleBlock: "Unexpected block statement surrounding arrow body; move the returned value immediately after the `=>`.", | |
expectedBlock: "Expected block statement surrounding arrow body." | |
} | |
}, | |
create(context) { | |
const options = context.options; | |
const always = options[0] === "always"; | |
const asNeeded = !options[0] || options[0] === "as-needed"; | |
const never = options[0] === "never"; | |
const requireReturnForObjectLiteral = options[1] && options[1].requireReturnForObjectLiteral; | |
const sourceCode = context.sourceCode; | |
let funcInfo = null; | |
/** | |
* Checks whether the given node has ASI problem or not. | |
* @param {Token} token The token to check. | |
* @returns {boolean} `true` if it changes semantics if `;` or `}` followed by the token are removed. | |
*/ | |
function hasASIProblem(token) { | |
return token && token.type === "Punctuator" && /^[([/`+-]/u.test(token.value); | |
} | |
/** | |
* Gets the closing parenthesis by the given node. | |
* @param {ASTNode} node first node after an opening parenthesis. | |
* @returns {Token} The found closing parenthesis token. | |
*/ | |
function findClosingParen(node) { | |
let nodeToCheck = node; | |
while (!astUtils.isParenthesised(sourceCode, nodeToCheck)) { | |
nodeToCheck = nodeToCheck.parent; | |
} | |
return sourceCode.getTokenAfter(nodeToCheck); | |
} | |
/** | |
* Check whether the node is inside of a for loop's init | |
* @param {ASTNode} node node is inside for loop | |
* @returns {boolean} `true` if the node is inside of a for loop, else `false` | |
*/ | |
function isInsideForLoopInitializer(node) { | |
if (node && node.parent) { | |
if (node.parent.type === "ForStatement" && node.parent.init === node) { | |
return true; | |
} | |
return isInsideForLoopInitializer(node.parent); | |
} | |
return false; | |
} | |
/** | |
* Determines whether a arrow function body needs braces | |
* @param {ASTNode} node The arrow function node. | |
* @returns {void} | |
*/ | |
function validate(node) { | |
const arrowBody = node.body; | |
if (arrowBody.type === "BlockStatement") { | |
const blockBody = arrowBody.body; | |
if (blockBody.length !== 1 && !never) { | |
return; | |
} | |
if (asNeeded && requireReturnForObjectLiteral && blockBody[0].type === "ReturnStatement" && | |
blockBody[0].argument && blockBody[0].argument.type === "ObjectExpression") { | |
return; | |
} | |
if (never || asNeeded && blockBody[0].type === "ReturnStatement") { | |
let messageId; | |
if (blockBody.length === 0) { | |
messageId = "unexpectedEmptyBlock"; | |
} else if (blockBody.length > 1) { | |
messageId = "unexpectedOtherBlock"; | |
} else if (blockBody[0].argument === null) { | |
messageId = "unexpectedSingleBlock"; | |
} else if (astUtils.isOpeningBraceToken(sourceCode.getFirstToken(blockBody[0], { skip: 1 }))) { | |
messageId = "unexpectedObjectBlock"; | |
} else { | |
messageId = "unexpectedSingleBlock"; | |
} | |
context.report({ | |
node, | |
loc: arrowBody.loc, | |
messageId, | |
fix(fixer) { | |
const fixes = []; | |
if (blockBody.length !== 1 || | |
blockBody[0].type !== "ReturnStatement" || | |
!blockBody[0].argument || | |
hasASIProblem(sourceCode.getTokenAfter(arrowBody)) | |
) { | |
return fixes; | |
} | |
const openingBrace = sourceCode.getFirstToken(arrowBody); | |
const closingBrace = sourceCode.getLastToken(arrowBody); | |
const firstValueToken = sourceCode.getFirstToken(blockBody[0], 1); | |
const lastValueToken = sourceCode.getLastToken(blockBody[0]); | |
const commentsExist = | |
sourceCode.commentsExistBetween(openingBrace, firstValueToken) || | |
sourceCode.commentsExistBetween(lastValueToken, closingBrace); | |
/* | |
* Remove tokens around the return value. | |
* If comments don't exist, remove extra spaces as well. | |
*/ | |
if (commentsExist) { | |
fixes.push( | |
fixer.remove(openingBrace), | |
fixer.remove(closingBrace), | |
fixer.remove(sourceCode.getTokenAfter(openingBrace)) // return keyword | |
); | |
} else { | |
fixes.push( | |
fixer.removeRange([openingBrace.range[0], firstValueToken.range[0]]), | |
fixer.removeRange([lastValueToken.range[1], closingBrace.range[1]]) | |
); | |
} | |
/* | |
* If the first token of the return value is `{` or the return value is a sequence expression, | |
* enclose the return value by parentheses to avoid syntax error. | |
*/ | |
if (astUtils.isOpeningBraceToken(firstValueToken) || blockBody[0].argument.type === "SequenceExpression" || (funcInfo.hasInOperator && isInsideForLoopInitializer(node))) { | |
if (!astUtils.isParenthesised(sourceCode, blockBody[0].argument)) { | |
fixes.push( | |
fixer.insertTextBefore(firstValueToken, "("), | |
fixer.insertTextAfter(lastValueToken, ")") | |
); | |
} | |
} | |
/* | |
* If the last token of the return statement is semicolon, remove it. | |
* Non-block arrow body is an expression, not a statement. | |
*/ | |
if (astUtils.isSemicolonToken(lastValueToken)) { | |
fixes.push(fixer.remove(lastValueToken)); | |
} | |
return fixes; | |
} | |
}); | |
} | |
} else { | |
if (always || (asNeeded && requireReturnForObjectLiteral && arrowBody.type === "ObjectExpression")) { | |
context.report({ | |
node, | |
loc: arrowBody.loc, | |
messageId: "expectedBlock", | |
fix(fixer) { | |
const fixes = []; | |
const arrowToken = sourceCode.getTokenBefore(arrowBody, astUtils.isArrowToken); | |
const [firstTokenAfterArrow, secondTokenAfterArrow] = sourceCode.getTokensAfter(arrowToken, { count: 2 }); | |
const lastToken = sourceCode.getLastToken(node); | |
let parenthesisedObjectLiteral = null; | |
if ( | |
astUtils.isOpeningParenToken(firstTokenAfterArrow) && | |
astUtils.isOpeningBraceToken(secondTokenAfterArrow) | |
) { | |
const braceNode = sourceCode.getNodeByRangeIndex(secondTokenAfterArrow.range[0]); | |
if (braceNode.type === "ObjectExpression") { | |
parenthesisedObjectLiteral = braceNode; | |
} | |
} | |
// If the value is object literal, remove parentheses which were forced by syntax. | |
if (parenthesisedObjectLiteral) { | |
const openingParenToken = firstTokenAfterArrow; | |
const openingBraceToken = secondTokenAfterArrow; | |
if (astUtils.isTokenOnSameLine(openingParenToken, openingBraceToken)) { | |
fixes.push(fixer.replaceText(openingParenToken, "{return ")); | |
} else { | |
// Avoid ASI | |
fixes.push( | |
fixer.replaceText(openingParenToken, "{"), | |
fixer.insertTextBefore(openingBraceToken, "return ") | |
); | |
} | |
// Closing paren for the object doesn't have to be lastToken, e.g.: () => ({}).foo() | |
fixes.push(fixer.remove(findClosingParen(parenthesisedObjectLiteral))); | |
fixes.push(fixer.insertTextAfter(lastToken, "}")); | |
} else { | |
fixes.push(fixer.insertTextBefore(firstTokenAfterArrow, "{return ")); | |
fixes.push(fixer.insertTextAfter(lastToken, "}")); | |
} | |
return fixes; | |
} | |
}); | |
} | |
} | |
} | |
return { | |
"BinaryExpression[operator='in']"() { | |
let info = funcInfo; | |
while (info) { | |
info.hasInOperator = true; | |
info = info.upper; | |
} | |
}, | |
ArrowFunctionExpression() { | |
funcInfo = { | |
upper: funcInfo, | |
hasInOperator: false | |
}; | |
}, | |
"ArrowFunctionExpression:exit"(node) { | |
validate(node); | |
funcInfo = funcInfo.upper; | |
} | |
}; | |
} | |
}; |