mirror of https://github.com/lukechilds/node.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
186 lines
6.5 KiB
186 lines
6.5 KiB
/**
|
|
* @fileoverview Operator linebreak - enforces operator linebreak style of two types: after and before
|
|
* @author Benoît Zugmeyer
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
const astUtils = require("../ast-utils");
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Rule Definition
|
|
//------------------------------------------------------------------------------
|
|
|
|
module.exports = {
|
|
meta: {
|
|
docs: {
|
|
description: "enforce consistent linebreak style for operators",
|
|
category: "Stylistic Issues",
|
|
recommended: false
|
|
},
|
|
|
|
schema: [
|
|
{
|
|
enum: ["after", "before", "none", null]
|
|
},
|
|
{
|
|
type: "object",
|
|
properties: {
|
|
overrides: {
|
|
type: "object",
|
|
properties: {
|
|
anyOf: {
|
|
type: "string",
|
|
enum: ["after", "before", "none", "ignore"]
|
|
}
|
|
}
|
|
}
|
|
},
|
|
additionalProperties: false
|
|
}
|
|
]
|
|
},
|
|
|
|
create(context) {
|
|
|
|
const usedDefaultGlobal = !context.options[0];
|
|
const globalStyle = context.options[0] || "after";
|
|
const options = context.options[1] || {};
|
|
const styleOverrides = options.overrides ? Object.assign({}, options.overrides) : {};
|
|
|
|
if (usedDefaultGlobal && !styleOverrides["?"]) {
|
|
styleOverrides["?"] = "before";
|
|
}
|
|
|
|
if (usedDefaultGlobal && !styleOverrides[":"]) {
|
|
styleOverrides[":"] = "before";
|
|
}
|
|
|
|
const sourceCode = context.getSourceCode();
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Helpers
|
|
//--------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Checks the operator placement
|
|
* @param {ASTNode} node The node to check
|
|
* @param {ASTNode} leftSide The node that comes before the operator in `node`
|
|
* @private
|
|
* @returns {void}
|
|
*/
|
|
function validateNode(node, leftSide) {
|
|
let leftToken = sourceCode.getLastToken(leftSide);
|
|
let operatorToken = sourceCode.getTokenAfter(leftToken);
|
|
|
|
// When the left part of a binary expression is a single expression wrapped in
|
|
// parentheses (ex: `(a) + b`), leftToken will be the last token of the expression
|
|
// and operatorToken will be the closing parenthesis.
|
|
// The leftToken should be the last closing parenthesis, and the operatorToken
|
|
// should be the token right after that.
|
|
while (operatorToken.value === ")") {
|
|
leftToken = operatorToken;
|
|
operatorToken = sourceCode.getTokenAfter(operatorToken);
|
|
}
|
|
|
|
const rightToken = sourceCode.getTokenAfter(operatorToken);
|
|
const operator = operatorToken.value;
|
|
const operatorStyleOverride = styleOverrides[operator];
|
|
const style = operatorStyleOverride || globalStyle;
|
|
|
|
// if single line
|
|
if (astUtils.isTokenOnSameLine(leftToken, operatorToken) &&
|
|
astUtils.isTokenOnSameLine(operatorToken, rightToken)) {
|
|
|
|
return;
|
|
|
|
} else if (operatorStyleOverride !== "ignore" && !astUtils.isTokenOnSameLine(leftToken, operatorToken) &&
|
|
!astUtils.isTokenOnSameLine(operatorToken, rightToken)) {
|
|
|
|
// lone operator
|
|
context.report({
|
|
node,
|
|
loc: {
|
|
line: operatorToken.loc.end.line,
|
|
column: operatorToken.loc.end.column
|
|
},
|
|
message: "Bad line breaking before and after '{{operator}}'.",
|
|
data: {
|
|
operator
|
|
}
|
|
});
|
|
|
|
} else if (style === "before" && astUtils.isTokenOnSameLine(leftToken, operatorToken)) {
|
|
|
|
context.report({
|
|
node,
|
|
loc: {
|
|
line: operatorToken.loc.end.line,
|
|
column: operatorToken.loc.end.column
|
|
},
|
|
message: "'{{operator}}' should be placed at the beginning of the line.",
|
|
data: {
|
|
operator
|
|
}
|
|
});
|
|
|
|
} else if (style === "after" && astUtils.isTokenOnSameLine(operatorToken, rightToken)) {
|
|
|
|
context.report({
|
|
node,
|
|
loc: {
|
|
line: operatorToken.loc.end.line,
|
|
column: operatorToken.loc.end.column
|
|
},
|
|
message: "'{{operator}}' should be placed at the end of the line.",
|
|
data: {
|
|
operator
|
|
}
|
|
});
|
|
|
|
} else if (style === "none") {
|
|
|
|
context.report({
|
|
node,
|
|
loc: {
|
|
line: operatorToken.loc.end.line,
|
|
column: operatorToken.loc.end.column
|
|
},
|
|
message: "There should be no line break before or after '{{operator}}'.",
|
|
data: {
|
|
operator
|
|
}
|
|
});
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validates a binary expression using `validateNode`
|
|
* @param {BinaryExpression|LogicalExpression|AssignmentExpression} node node to be validated
|
|
* @returns {void}
|
|
*/
|
|
function validateBinaryExpression(node) {
|
|
validateNode(node, node.left);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Public
|
|
//--------------------------------------------------------------------------
|
|
|
|
return {
|
|
BinaryExpression: validateBinaryExpression,
|
|
LogicalExpression: validateBinaryExpression,
|
|
AssignmentExpression: validateBinaryExpression,
|
|
VariableDeclarator(node) {
|
|
if (node.init) {
|
|
validateNode(node, node.id);
|
|
}
|
|
},
|
|
ConditionalExpression(node) {
|
|
validateNode(node, node.test);
|
|
validateNode(node, node.consequent);
|
|
}
|
|
};
|
|
}
|
|
};
|
|
|