Actualizacion maquina principal
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / no-implicit-coercion.js
1 /**
2  * @fileoverview A rule to disallow the type conversions with shorter notations.
3  * @author Toru Nagashima
4  */
5
6 "use strict";
7
8 const astUtils = require("./utils/ast-utils");
9
10 //------------------------------------------------------------------------------
11 // Helpers
12 //------------------------------------------------------------------------------
13
14 const INDEX_OF_PATTERN = /^(?:i|lastI)ndexOf$/u;
15 const ALLOWABLE_OPERATORS = ["~", "!!", "+", "*"];
16
17 /**
18  * Parses and normalizes an option object.
19  * @param {Object} options An option object to parse.
20  * @returns {Object} The parsed and normalized option object.
21  */
22 function parseOptions(options) {
23     return {
24         boolean: "boolean" in options ? options.boolean : true,
25         number: "number" in options ? options.number : true,
26         string: "string" in options ? options.string : true,
27         allow: options.allow || []
28     };
29 }
30
31 /**
32  * Checks whether or not a node is a double logical nigating.
33  * @param {ASTNode} node An UnaryExpression node to check.
34  * @returns {boolean} Whether or not the node is a double logical nigating.
35  */
36 function isDoubleLogicalNegating(node) {
37     return (
38         node.operator === "!" &&
39         node.argument.type === "UnaryExpression" &&
40         node.argument.operator === "!"
41     );
42 }
43
44 /**
45  * Checks whether or not a node is a binary negating of `.indexOf()` method calling.
46  * @param {ASTNode} node An UnaryExpression node to check.
47  * @returns {boolean} Whether or not the node is a binary negating of `.indexOf()` method calling.
48  */
49 function isBinaryNegatingOfIndexOf(node) {
50     return (
51         node.operator === "~" &&
52         node.argument.type === "CallExpression" &&
53         node.argument.callee.type === "MemberExpression" &&
54         node.argument.callee.property.type === "Identifier" &&
55         INDEX_OF_PATTERN.test(node.argument.callee.property.name)
56     );
57 }
58
59 /**
60  * Checks whether or not a node is a multiplying by one.
61  * @param {BinaryExpression} node A BinaryExpression node to check.
62  * @returns {boolean} Whether or not the node is a multiplying by one.
63  */
64 function isMultiplyByOne(node) {
65     return node.operator === "*" && (
66         node.left.type === "Literal" && node.left.value === 1 ||
67         node.right.type === "Literal" && node.right.value === 1
68     );
69 }
70
71 /**
72  * Checks whether the result of a node is numeric or not
73  * @param {ASTNode} node The node to test
74  * @returns {boolean} true if the node is a number literal or a `Number()`, `parseInt` or `parseFloat` call
75  */
76 function isNumeric(node) {
77     return (
78         node.type === "Literal" && typeof node.value === "number" ||
79         node.type === "CallExpression" && (
80             node.callee.name === "Number" ||
81             node.callee.name === "parseInt" ||
82             node.callee.name === "parseFloat"
83         )
84     );
85 }
86
87 /**
88  * Returns the first non-numeric operand in a BinaryExpression. Designed to be
89  * used from bottom to up since it walks up the BinaryExpression trees using
90  * node.parent to find the result.
91  * @param {BinaryExpression} node The BinaryExpression node to be walked up on
92  * @returns {ASTNode|null} The first non-numeric item in the BinaryExpression tree or null
93  */
94 function getNonNumericOperand(node) {
95     const left = node.left,
96         right = node.right;
97
98     if (right.type !== "BinaryExpression" && !isNumeric(right)) {
99         return right;
100     }
101
102     if (left.type !== "BinaryExpression" && !isNumeric(left)) {
103         return left;
104     }
105
106     return null;
107 }
108
109 /**
110  * Checks whether a node is an empty string literal or not.
111  * @param {ASTNode} node The node to check.
112  * @returns {boolean} Whether or not the passed in node is an
113  * empty string literal or not.
114  */
115 function isEmptyString(node) {
116     return astUtils.isStringLiteral(node) && (node.value === "" || (node.type === "TemplateLiteral" && node.quasis.length === 1 && node.quasis[0].value.cooked === ""));
117 }
118
119 /**
120  * Checks whether or not a node is a concatenating with an empty string.
121  * @param {ASTNode} node A BinaryExpression node to check.
122  * @returns {boolean} Whether or not the node is a concatenating with an empty string.
123  */
124 function isConcatWithEmptyString(node) {
125     return node.operator === "+" && (
126         (isEmptyString(node.left) && !astUtils.isStringLiteral(node.right)) ||
127         (isEmptyString(node.right) && !astUtils.isStringLiteral(node.left))
128     );
129 }
130
131 /**
132  * Checks whether or not a node is appended with an empty string.
133  * @param {ASTNode} node An AssignmentExpression node to check.
134  * @returns {boolean} Whether or not the node is appended with an empty string.
135  */
136 function isAppendEmptyString(node) {
137     return node.operator === "+=" && isEmptyString(node.right);
138 }
139
140 /**
141  * Returns the operand that is not an empty string from a flagged BinaryExpression.
142  * @param {ASTNode} node The flagged BinaryExpression node to check.
143  * @returns {ASTNode} The operand that is not an empty string from a flagged BinaryExpression.
144  */
145 function getNonEmptyOperand(node) {
146     return isEmptyString(node.left) ? node.right : node.left;
147 }
148
149 //------------------------------------------------------------------------------
150 // Rule Definition
151 //------------------------------------------------------------------------------
152
153 module.exports = {
154     meta: {
155         type: "suggestion",
156
157         docs: {
158             description: "disallow shorthand type conversions",
159             category: "Best Practices",
160             recommended: false,
161             url: "https://eslint.org/docs/rules/no-implicit-coercion"
162         },
163
164         fixable: "code",
165
166         schema: [{
167             type: "object",
168             properties: {
169                 boolean: {
170                     type: "boolean",
171                     default: true
172                 },
173                 number: {
174                     type: "boolean",
175                     default: true
176                 },
177                 string: {
178                     type: "boolean",
179                     default: true
180                 },
181                 allow: {
182                     type: "array",
183                     items: {
184                         enum: ALLOWABLE_OPERATORS
185                     },
186                     uniqueItems: true
187                 }
188             },
189             additionalProperties: false
190         }]
191     },
192
193     create(context) {
194         const options = parseOptions(context.options[0] || {});
195         const sourceCode = context.getSourceCode();
196
197         /**
198          * Reports an error and autofixes the node
199          * @param {ASTNode} node An ast node to report the error on.
200          * @param {string} recommendation The recommended code for the issue
201          * @param {bool} shouldFix Whether this report should fix the node
202          * @returns {void}
203          */
204         function report(node, recommendation, shouldFix) {
205             context.report({
206                 node,
207                 message: "use `{{recommendation}}` instead.",
208                 data: {
209                     recommendation
210                 },
211                 fix(fixer) {
212                     if (!shouldFix) {
213                         return null;
214                     }
215
216                     const tokenBefore = sourceCode.getTokenBefore(node);
217
218                     if (
219                         tokenBefore &&
220                         tokenBefore.range[1] === node.range[0] &&
221                         !astUtils.canTokensBeAdjacent(tokenBefore, recommendation)
222                     ) {
223                         return fixer.replaceText(node, ` ${recommendation}`);
224                     }
225                     return fixer.replaceText(node, recommendation);
226                 }
227             });
228         }
229
230         return {
231             UnaryExpression(node) {
232                 let operatorAllowed;
233
234                 // !!foo
235                 operatorAllowed = options.allow.indexOf("!!") >= 0;
236                 if (!operatorAllowed && options.boolean && isDoubleLogicalNegating(node)) {
237                     const recommendation = `Boolean(${sourceCode.getText(node.argument.argument)})`;
238
239                     report(node, recommendation, true);
240                 }
241
242                 // ~foo.indexOf(bar)
243                 operatorAllowed = options.allow.indexOf("~") >= 0;
244                 if (!operatorAllowed && options.boolean && isBinaryNegatingOfIndexOf(node)) {
245                     const recommendation = `${sourceCode.getText(node.argument)} !== -1`;
246
247                     report(node, recommendation, false);
248                 }
249
250                 // +foo
251                 operatorAllowed = options.allow.indexOf("+") >= 0;
252                 if (!operatorAllowed && options.number && node.operator === "+" && !isNumeric(node.argument)) {
253                     const recommendation = `Number(${sourceCode.getText(node.argument)})`;
254
255                     report(node, recommendation, true);
256                 }
257             },
258
259             // Use `:exit` to prevent double reporting
260             "BinaryExpression:exit"(node) {
261                 let operatorAllowed;
262
263                 // 1 * foo
264                 operatorAllowed = options.allow.indexOf("*") >= 0;
265                 const nonNumericOperand = !operatorAllowed && options.number && isMultiplyByOne(node) && getNonNumericOperand(node);
266
267                 if (nonNumericOperand) {
268                     const recommendation = `Number(${sourceCode.getText(nonNumericOperand)})`;
269
270                     report(node, recommendation, true);
271                 }
272
273                 // "" + foo
274                 operatorAllowed = options.allow.indexOf("+") >= 0;
275                 if (!operatorAllowed && options.string && isConcatWithEmptyString(node)) {
276                     const recommendation = `String(${sourceCode.getText(getNonEmptyOperand(node))})`;
277
278                     report(node, recommendation, true);
279                 }
280             },
281
282             AssignmentExpression(node) {
283
284                 // foo += ""
285                 const operatorAllowed = options.allow.indexOf("+") >= 0;
286
287                 if (!operatorAllowed && options.string && isAppendEmptyString(node)) {
288                     const code = sourceCode.getText(getNonEmptyOperand(node));
289                     const recommendation = `${code} = String(${code})`;
290
291                     report(node, recommendation, true);
292                 }
293             }
294         };
295     }
296 };