.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / 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 module.exports = {
19     meta: {
20         type: "layout",
21
22         docs: {
23             description: "enforce consistent linebreak style for operators",
24             category: "Stylistic Issues",
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} leftSide The node that comes before the operator in `node`
140          * @private
141          * @returns {void}
142          */
143         function validateNode(node, leftSide) {
144
145             /*
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.
151              */
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);
159
160             // if single line
161             if (astUtils.isTokenOnSameLine(leftToken, operatorToken) &&
162                     astUtils.isTokenOnSameLine(operatorToken, rightToken)) {
163
164                 // do nothing.
165
166             } else if (operatorStyleOverride !== "ignore" && !astUtils.isTokenOnSameLine(leftToken, operatorToken) &&
167                     !astUtils.isTokenOnSameLine(operatorToken, rightToken)) {
168
169                 // lone operator
170                 context.report({
171                     node,
172                     loc: operatorToken.loc,
173                     messageId: "badLinebreak",
174                     data: {
175                         operator
176                     },
177                     fix
178                 });
179
180             } else if (style === "before" && astUtils.isTokenOnSameLine(leftToken, operatorToken)) {
181
182                 context.report({
183                     node,
184                     loc: operatorToken.loc,
185                     messageId: "operatorAtBeginning",
186                     data: {
187                         operator
188                     },
189                     fix
190                 });
191
192             } else if (style === "after" && astUtils.isTokenOnSameLine(operatorToken, rightToken)) {
193
194                 context.report({
195                     node,
196                     loc: operatorToken.loc,
197                     messageId: "operatorAtEnd",
198                     data: {
199                         operator
200                     },
201                     fix
202                 });
203
204             } else if (style === "none") {
205
206                 context.report({
207                     node,
208                     loc: operatorToken.loc,
209                     messageId: "noLinebreak",
210                     data: {
211                         operator
212                     },
213                     fix
214                 });
215
216             }
217         }
218
219         /**
220          * Validates a binary expression using `validateNode`
221          * @param {BinaryExpression|LogicalExpression|AssignmentExpression} node node to be validated
222          * @returns {void}
223          */
224         function validateBinaryExpression(node) {
225             validateNode(node, node.left);
226         }
227
228         //--------------------------------------------------------------------------
229         // Public
230         //--------------------------------------------------------------------------
231
232         return {
233             BinaryExpression: validateBinaryExpression,
234             LogicalExpression: validateBinaryExpression,
235             AssignmentExpression: validateBinaryExpression,
236             VariableDeclarator(node) {
237                 if (node.init) {
238                     validateNode(node, node.id);
239                 }
240             },
241             ConditionalExpression(node) {
242                 validateNode(node, node.test);
243                 validateNode(node, node.consequent);
244             }
245         };
246     }
247 };