]> git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/operator-linebreak.js
import 8.4.0 source
[pve-eslint.git] / eslint / lib / rules / operator-linebreak.js
1 /**
2 * @fileoverview Operator linebreak - enforces operator linebreak style of two types: after and before
3 * @author BenoƮt Zugmeyer
4 */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Requirements
10 //------------------------------------------------------------------------------
11
12 const astUtils = require("./utils/ast-utils");
13
14 //------------------------------------------------------------------------------
15 // Rule Definition
16 //------------------------------------------------------------------------------
17
18 /** @type {import('../shared/types').Rule} */
19 module.exports = {
20 meta: {
21 type: "layout",
22
23 docs: {
24 description: "enforce consistent linebreak style for operators",
25 recommended: false,
26 url: "https://eslint.org/docs/rules/operator-linebreak"
27 },
28
29 schema: [
30 {
31 enum: ["after", "before", "none", null]
32 },
33 {
34 type: "object",
35 properties: {
36 overrides: {
37 type: "object",
38 additionalProperties: {
39 enum: ["after", "before", "none", "ignore"]
40 }
41 }
42 },
43 additionalProperties: false
44 }
45 ],
46
47 fixable: "code",
48
49 messages: {
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}}'."
54 }
55 },
56
57 create(context) {
58
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) : {};
63
64 if (usedDefaultGlobal && !styleOverrides["?"]) {
65 styleOverrides["?"] = "before";
66 }
67
68 if (usedDefaultGlobal && !styleOverrides[":"]) {
69 styleOverrides[":"] = "before";
70 }
71
72 const sourceCode = context.getSourceCode();
73
74 //--------------------------------------------------------------------------
75 // Helpers
76 //--------------------------------------------------------------------------
77
78 /**
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
83 */
84 function getFixer(operatorToken, desiredStyle) {
85 return fixer => {
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;
93
94 if (hasLinebreakBefore !== hasLinebreakAfter && desiredStyle !== "none") {
95
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) {
99
100 return null;
101 }
102
103 /*
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.
105 * foo &&
106 * bar
107 * would get fixed to
108 * foo
109 * && bar
110 */
111 newTextBefore = textAfter;
112 newTextAfter = textBefore;
113 } else {
114 const LINEBREAK_REGEX = astUtils.createGlobalLinebreakMatcher();
115
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, "");
119
120 // If there was no change (due to interfering comments), don't output a fix.
121 if (newTextBefore === textBefore && newTextAfter === textAfter) {
122 return null;
123 }
124 }
125
126 if (newTextAfter === "" && tokenAfter.type === "Punctuator" && "+-".includes(operatorToken.value) && tokenAfter.value === operatorToken.value) {
127
128 // To avoid accidentally creating a ++ or -- operator, insert a space if the operator is a +/- and the following token is a unary +/-.
129 newTextAfter += " ";
130 }
131
132 return fixer.replaceTextRange([tokenBefore.range[1], tokenAfter.range[0]], newTextBefore + operatorToken.value + newTextAfter);
133 };
134 }
135
136 /**
137 * Checks the operator placement
138 * @param {ASTNode} node The node to check
139 * @param {ASTNode} rightSide The node that comes after the operator in `node`
140 * @param {string} operator The operator
141 * @private
142 * @returns {void}
143 */
144 function validateNode(node, rightSide, operator) {
145
146 /*
147 * Find the operator token by searching from the right side, because between the left side and the operator
148 * there could be additional tokens from type annotations. Search specifically for the token which
149 * value equals the operator, in order to skip possible opening parentheses before the right side node.
150 */
151 const operatorToken = sourceCode.getTokenBefore(rightSide, token => token.value === operator);
152 const leftToken = sourceCode.getTokenBefore(operatorToken);
153 const rightToken = sourceCode.getTokenAfter(operatorToken);
154 const operatorStyleOverride = styleOverrides[operator];
155 const style = operatorStyleOverride || globalStyle;
156 const fix = getFixer(operatorToken, style);
157
158 // if single line
159 if (astUtils.isTokenOnSameLine(leftToken, operatorToken) &&
160 astUtils.isTokenOnSameLine(operatorToken, rightToken)) {
161
162 // do nothing.
163
164 } else if (operatorStyleOverride !== "ignore" && !astUtils.isTokenOnSameLine(leftToken, operatorToken) &&
165 !astUtils.isTokenOnSameLine(operatorToken, rightToken)) {
166
167 // lone operator
168 context.report({
169 node,
170 loc: operatorToken.loc,
171 messageId: "badLinebreak",
172 data: {
173 operator
174 },
175 fix
176 });
177
178 } else if (style === "before" && astUtils.isTokenOnSameLine(leftToken, operatorToken)) {
179
180 context.report({
181 node,
182 loc: operatorToken.loc,
183 messageId: "operatorAtBeginning",
184 data: {
185 operator
186 },
187 fix
188 });
189
190 } else if (style === "after" && astUtils.isTokenOnSameLine(operatorToken, rightToken)) {
191
192 context.report({
193 node,
194 loc: operatorToken.loc,
195 messageId: "operatorAtEnd",
196 data: {
197 operator
198 },
199 fix
200 });
201
202 } else if (style === "none") {
203
204 context.report({
205 node,
206 loc: operatorToken.loc,
207 messageId: "noLinebreak",
208 data: {
209 operator
210 },
211 fix
212 });
213
214 }
215 }
216
217 /**
218 * Validates a binary expression using `validateNode`
219 * @param {BinaryExpression|LogicalExpression|AssignmentExpression} node node to be validated
220 * @returns {void}
221 */
222 function validateBinaryExpression(node) {
223 validateNode(node, node.right, node.operator);
224 }
225
226 //--------------------------------------------------------------------------
227 // Public
228 //--------------------------------------------------------------------------
229
230 return {
231 BinaryExpression: validateBinaryExpression,
232 LogicalExpression: validateBinaryExpression,
233 AssignmentExpression: validateBinaryExpression,
234 VariableDeclarator(node) {
235 if (node.init) {
236 validateNode(node, node.init, "=");
237 }
238 },
239 PropertyDefinition(node) {
240 if (node.value) {
241 validateNode(node, node.value, "=");
242 }
243 },
244 ConditionalExpression(node) {
245 validateNode(node, node.consequent, "?");
246 validateNode(node, node.alternate, ":");
247 }
248 };
249 }
250 };