2 * @fileoverview Operator linebreak - enforces operator linebreak style of two types: after and before
3 * @author BenoƮt Zugmeyer
8 //------------------------------------------------------------------------------
10 //------------------------------------------------------------------------------
12 const astUtils = require("./utils/ast-utils");
14 //------------------------------------------------------------------------------
16 //------------------------------------------------------------------------------
23 description: "enforce consistent linebreak style for operators",
24 category: "Stylistic Issues",
26 url: "https://eslint.org/docs/rules/operator-linebreak"
31 enum: ["after", "before", "none", null]
38 additionalProperties: {
39 enum: ["after", "before", "none", "ignore"]
43 additionalProperties: false
50 operatorAtBeginning: "'{{operator}}' should be placed at the beginning of the line.",
51 operatorAtEnd: "'{{operator}}' should be placed at the end of the line.",
52 badLinebreak: "Bad line breaking before and after '{{operator}}'.",
53 noLinebreak: "There should be no line break before or after '{{operator}}'."
59 const usedDefaultGlobal = !context.options[0];
60 const globalStyle = context.options[0] || "after";
61 const options = context.options[1] || {};
62 const styleOverrides = options.overrides ? Object.assign({}, options.overrides) : {};
64 if (usedDefaultGlobal && !styleOverrides["?"]) {
65 styleOverrides["?"] = "before";
68 if (usedDefaultGlobal && !styleOverrides[":"]) {
69 styleOverrides[":"] = "before";
72 const sourceCode = context.getSourceCode();
74 //--------------------------------------------------------------------------
76 //--------------------------------------------------------------------------
79 * Gets a fixer function to fix rule issues
80 * @param {Token} operatorToken The operator token of an expression
81 * @param {string} desiredStyle The style for the rule. One of 'before', 'after', 'none'
82 * @returns {Function} A fixer function
84 function getFixer(operatorToken, desiredStyle) {
86 const tokenBefore = sourceCode.getTokenBefore(operatorToken);
87 const tokenAfter = sourceCode.getTokenAfter(operatorToken);
88 const textBefore = sourceCode.text.slice(tokenBefore.range[1], operatorToken.range[0]);
89 const textAfter = sourceCode.text.slice(operatorToken.range[1], tokenAfter.range[0]);
90 const hasLinebreakBefore = !astUtils.isTokenOnSameLine(tokenBefore, operatorToken);
91 const hasLinebreakAfter = !astUtils.isTokenOnSameLine(operatorToken, tokenAfter);
92 let newTextBefore, newTextAfter;
94 if (hasLinebreakBefore !== hasLinebreakAfter && desiredStyle !== "none") {
96 // If there is a comment before and after the operator, don't do a fix.
97 if (sourceCode.getTokenBefore(operatorToken, { includeComments: true }) !== tokenBefore &&
98 sourceCode.getTokenAfter(operatorToken, { includeComments: true }) !== tokenAfter) {
104 * If there is only one linebreak and it's on the wrong side of the operator, swap the text before and after the operator.
111 newTextBefore = textAfter;
112 newTextAfter = textBefore;
114 const LINEBREAK_REGEX = astUtils.createGlobalLinebreakMatcher();
116 // Otherwise, if no linebreak is desired and no comments interfere, replace the linebreaks with empty strings.
117 newTextBefore = desiredStyle === "before" || textBefore.trim() ? textBefore : textBefore.replace(LINEBREAK_REGEX, "");
118 newTextAfter = desiredStyle === "after" || textAfter.trim() ? textAfter : textAfter.replace(LINEBREAK_REGEX, "");
120 // If there was no change (due to interfering comments), don't output a fix.
121 if (newTextBefore === textBefore && newTextAfter === textAfter) {
126 if (newTextAfter === "" && tokenAfter.type === "Punctuator" && "+-".includes(operatorToken.value) && tokenAfter.value === operatorToken.value) {
128 // To avoid accidentally creating a ++ or -- operator, insert a space if the operator is a +/- and the following token is a unary +/-.
132 return fixer.replaceTextRange([tokenBefore.range[1], tokenAfter.range[0]], newTextBefore + operatorToken.value + newTextAfter);
137 * Checks the operator placement
138 * @param {ASTNode} node The node to check
139 * @param {ASTNode} leftSide The node that comes before the operator in `node`
143 function validateNode(node, leftSide) {
146 * When the left part of a binary expression is a single expression wrapped in
147 * parentheses (ex: `(a) + b`), leftToken will be the last token of the expression
148 * and operatorToken will be the closing parenthesis.
149 * The leftToken should be the last closing parenthesis, and the operatorToken
150 * should be the token right after that.
152 const operatorToken = sourceCode.getTokenAfter(leftSide, astUtils.isNotClosingParenToken);
153 const leftToken = sourceCode.getTokenBefore(operatorToken);
154 const rightToken = sourceCode.getTokenAfter(operatorToken);
155 const operator = operatorToken.value;
156 const operatorStyleOverride = styleOverrides[operator];
157 const style = operatorStyleOverride || globalStyle;
158 const fix = getFixer(operatorToken, style);
161 if (astUtils.isTokenOnSameLine(leftToken, operatorToken) &&
162 astUtils.isTokenOnSameLine(operatorToken, rightToken)) {
166 } else if (operatorStyleOverride !== "ignore" && !astUtils.isTokenOnSameLine(leftToken, operatorToken) &&
167 !astUtils.isTokenOnSameLine(operatorToken, rightToken)) {
172 loc: operatorToken.loc,
173 messageId: "badLinebreak",
180 } else if (style === "before" && astUtils.isTokenOnSameLine(leftToken, operatorToken)) {
184 loc: operatorToken.loc,
185 messageId: "operatorAtBeginning",
192 } else if (style === "after" && astUtils.isTokenOnSameLine(operatorToken, rightToken)) {
196 loc: operatorToken.loc,
197 messageId: "operatorAtEnd",
204 } else if (style === "none") {
208 loc: operatorToken.loc,
209 messageId: "noLinebreak",
220 * Validates a binary expression using `validateNode`
221 * @param {BinaryExpression|LogicalExpression|AssignmentExpression} node node to be validated
224 function validateBinaryExpression(node) {
225 validateNode(node, node.left);
228 //--------------------------------------------------------------------------
230 //--------------------------------------------------------------------------
233 BinaryExpression: validateBinaryExpression,
234 LogicalExpression: validateBinaryExpression,
235 AssignmentExpression: validateBinaryExpression,
236 VariableDeclarator(node) {
238 validateNode(node, node.id);
241 ConditionalExpression(node) {
242 validateNode(node, node.test);
243 validateNode(node, node.consequent);