Gazebo_simulation-Frontend/node_modules/eslint/lib/rules/implicit-arrow-linebreak.js
2020-12-21 10:29:31 -05:00

233 lines
8.7 KiB
JavaScript

/**
* @fileoverview enforce the location of arrow function bodies
* @author Sharmila Jesupaul
*/
"use strict";
const {
isArrowToken,
isParenthesised,
isOpeningParenToken
} = require("../util/ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
type: "layout",
docs: {
description: "enforce the location of arrow function bodies",
category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/implicit-arrow-linebreak"
},
fixable: "whitespace",
schema: [
{
enum: ["beside", "below"]
}
]
},
create(context) {
const sourceCode = context.getSourceCode();
//----------------------------------------------------------------------
// Helpers
//----------------------------------------------------------------------
/**
* Gets the applicable preference for a particular keyword
* @returns {string} The applicable option for the keyword, e.g. 'beside'
*/
function getOption() {
return context.options[0] || "beside";
}
/**
* Formats the comments depending on whether it's a line or block comment.
* @param {Comment[]} comments The array of comments between the arrow and body
* @param {Integer} column The column number of the first token
* @returns {string} A string of comment text joined by line breaks
*/
function formatComments(comments, column) {
const whiteSpaces = " ".repeat(column);
return `${comments.map(comment => {
if (comment.type === "Line") {
return `//${comment.value}`;
}
return `/*${comment.value}*/`;
}).join(`\n${whiteSpaces}`)}\n${whiteSpaces}`;
}
/**
* Finds the first token to prepend comments to depending on the parent type
* @param {Node} node The validated node
* @returns {Token|Node} The node to prepend comments to
*/
function findFirstToken(node) {
switch (node.parent.type) {
case "VariableDeclarator":
// If the parent is first or only declarator, return the declaration, else, declarator
return sourceCode.getFirstToken(
node.parent.parent.declarations.length === 1 ||
node.parent.parent.declarations[0].id.name === node.parent.id.name
? node.parent.parent : node.parent
);
case "CallExpression":
case "Property":
// find the object key
return sourceCode.getFirstToken(node.parent);
default:
return node;
}
}
/**
* Helper function for adding parentheses fixes for nodes containing nested arrow functions
* @param {Fixer} fixer Fixer
* @param {Token} arrow - The arrow token
* @param {ASTNode} arrowBody - The arrow function body
* @returns {Function[]} autofixer -- wraps function bodies with parentheses
*/
function addParentheses(fixer, arrow, arrowBody) {
const parenthesesFixes = [];
let closingParentheses = "";
let followingBody = arrowBody;
let currentArrow = arrow;
while (currentArrow) {
if (!isParenthesised(sourceCode, followingBody)) {
parenthesesFixes.push(
fixer.insertTextAfter(currentArrow, " (")
);
const paramsToken = sourceCode.getTokenBefore(currentArrow, token =>
isOpeningParenToken(token) || token.type === "Identifier");
const whiteSpaces = " ".repeat(paramsToken.loc.start.column);
closingParentheses = `\n${whiteSpaces})${closingParentheses}`;
}
currentArrow = sourceCode.getTokenAfter(currentArrow, isArrowToken);
if (currentArrow) {
followingBody = sourceCode.getTokenAfter(currentArrow, token => !isOpeningParenToken(token));
}
}
return [...parenthesesFixes,
fixer.insertTextAfter(arrowBody, closingParentheses)
];
}
/**
* Autofixes the function body to collapse onto the same line as the arrow.
* If comments exist, prepends the comments before the arrow function.
* If the function body contains arrow functions, appends the function bodies with parentheses.
* @param {Token} arrowToken The arrow token.
* @param {ASTNode} arrowBody the function body
* @param {ASTNode} node The evaluated node
* @returns {Function} autofixer -- validates the node to adhere to besides
*/
function autoFixBesides(arrowToken, arrowBody, node) {
return fixer => {
const placeBesides = fixer.replaceTextRange([arrowToken.range[1], arrowBody.range[0]], " ");
const comments = sourceCode.getCommentsInside(node).filter(comment =>
comment.loc.start.line < arrowBody.loc.start.line);
if (comments.length) {
// If the grandparent is not a variable declarator
if (
arrowBody.parent &&
arrowBody.parent.parent &&
arrowBody.parent.parent.type !== "VariableDeclarator"
) {
// If any arrow functions follow, return the necessary parens fixes.
if (sourceCode.getTokenAfter(arrowToken, isArrowToken) && arrowBody.parent.parent.type !== "VariableDeclarator") {
return addParentheses(fixer, arrowToken, arrowBody);
}
// If any arrow functions precede, the necessary fixes have already been returned, so return null.
if (sourceCode.getTokenBefore(arrowToken, isArrowToken) && arrowBody.parent.parent.type !== "VariableDeclarator") {
return null;
}
}
const firstToken = findFirstToken(node);
const commentText = formatComments(comments, firstToken.loc.start.column);
const commentBeforeExpression = fixer.insertTextBeforeRange(
firstToken.range,
commentText
);
return [placeBesides, commentBeforeExpression];
}
return placeBesides;
};
}
/**
* Validates the location of an arrow function body
* @param {ASTNode} node The arrow function body
* @returns {void}
*/
function validateExpression(node) {
const option = getOption();
let tokenBefore = sourceCode.getTokenBefore(node.body);
const hasParens = tokenBefore.value === "(";
if (node.type === "BlockStatement") {
return;
}
let fixerTarget = node.body;
if (hasParens) {
// Gets the first token before the function body that is not an open paren
tokenBefore = sourceCode.getTokenBefore(node.body, token => token.value !== "(");
fixerTarget = sourceCode.getTokenAfter(tokenBefore);
}
if (tokenBefore.loc.end.line === fixerTarget.loc.start.line && option === "below") {
context.report({
node: fixerTarget,
message: "Expected a linebreak before this expression.",
fix: fixer => fixer.insertTextBefore(fixerTarget, "\n")
});
} else if (tokenBefore.loc.end.line !== fixerTarget.loc.start.line && option === "beside") {
context.report({
node: fixerTarget,
message: "Expected no linebreak before this expression.",
fix: autoFixBesides(tokenBefore, fixerTarget, node)
});
}
}
//----------------------------------------------------------------------
// Public
//----------------------------------------------------------------------
return {
ArrowFunctionExpression: node => validateExpression(node)
};
}
};