.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / no-loss-of-precision.js
1 /**
2  * @fileoverview Rule to flag numbers that will lose significant figure precision at runtime
3  * @author Jacob Moore
4  */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Rule Definition
10 //------------------------------------------------------------------------------
11
12 module.exports = {
13     meta: {
14         type: "problem",
15
16         docs: {
17             description: "disallow literal numbers that lose precision",
18             category: "Possible Errors",
19             recommended: false,
20             url: "https://eslint.org/docs/rules/no-loss-of-precision"
21         },
22         schema: [],
23         messages: {
24             noLossOfPrecision: "This number literal will lose precision at runtime."
25         }
26     },
27
28     create(context) {
29
30         /**
31          * Returns whether the node is number literal
32          * @param {Node} node the node literal being evaluated
33          * @returns {boolean} true if the node is a number literal
34          */
35         function isNumber(node) {
36             return typeof node.value === "number";
37         }
38
39         /**
40          * Gets the source code of the given number literal. Removes `_` numeric separators from the result.
41          * @param {Node} node the number `Literal` node
42          * @returns {string} raw source code of the literal, without numeric separators
43          */
44         function getRaw(node) {
45             return node.raw.replace(/_/gu, "");
46         }
47
48         /**
49          * Checks whether the number is  base ten
50          * @param {ASTNode} node the node being evaluated
51          * @returns {boolean} true if the node is in base ten
52          */
53         function isBaseTen(node) {
54             const prefixes = ["0x", "0X", "0b", "0B", "0o", "0O"];
55
56             return prefixes.every(prefix => !node.raw.startsWith(prefix)) &&
57             !/^0[0-7]+$/u.test(node.raw);
58         }
59
60         /**
61          * Checks that the user-intended non-base ten number equals the actual number after is has been converted to the Number type
62          * @param {Node} node the node being evaluated
63          * @returns {boolean} true if they do not match
64          */
65         function notBaseTenLosesPrecision(node) {
66             const rawString = getRaw(node).toUpperCase();
67             let base = 0;
68
69             if (rawString.startsWith("0B")) {
70                 base = 2;
71             } else if (rawString.startsWith("0X")) {
72                 base = 16;
73             } else {
74                 base = 8;
75             }
76
77             return !rawString.endsWith(node.value.toString(base).toUpperCase());
78         }
79
80         /**
81          * Adds a decimal point to the numeric string at index 1
82          * @param {string} stringNumber the numeric string without any decimal point
83          * @returns {string} the numeric string with a decimal point in the proper place
84          */
85         function addDecimalPointToNumber(stringNumber) {
86             return `${stringNumber.slice(0, 1)}.${stringNumber.slice(1)}`;
87         }
88
89         /**
90          * Returns the number stripped of leading zeros
91          * @param {string} numberAsString the string representation of the number
92          * @returns {string} the stripped string
93          */
94         function removeLeadingZeros(numberAsString) {
95             return numberAsString.replace(/^0*/u, "");
96         }
97
98         /**
99          * Returns the number stripped of trailing zeros
100          * @param {string} numberAsString the string representation of the number
101          * @returns {string} the stripped string
102          */
103         function removeTrailingZeros(numberAsString) {
104             return numberAsString.replace(/0*$/u, "");
105         }
106
107         /**
108          * Converts an integer to to an object containing the integer's coefficient and order of magnitude
109          * @param {string} stringInteger the string representation of the integer being converted
110          * @returns {Object} the object containing the integer's coefficient and order of magnitude
111          */
112         function normalizeInteger(stringInteger) {
113             const significantDigits = removeTrailingZeros(removeLeadingZeros(stringInteger));
114
115             return {
116                 magnitude: stringInteger.startsWith("0") ? stringInteger.length - 2 : stringInteger.length - 1,
117                 coefficient: addDecimalPointToNumber(significantDigits)
118             };
119         }
120
121         /**
122          *
123          * Converts a float to to an object containing the floats's coefficient and order of magnitude
124          * @param {string} stringFloat the string representation of the float being converted
125          * @returns {Object} the object containing the integer's coefficient and order of magnitude
126          */
127         function normalizeFloat(stringFloat) {
128             const trimmedFloat = removeLeadingZeros(stringFloat);
129
130             if (trimmedFloat.startsWith(".")) {
131                 const decimalDigits = trimmedFloat.split(".").pop();
132                 const significantDigits = removeLeadingZeros(decimalDigits);
133
134                 return {
135                     magnitude: significantDigits.length - decimalDigits.length - 1,
136                     coefficient: addDecimalPointToNumber(significantDigits)
137                 };
138
139             }
140             return {
141                 magnitude: trimmedFloat.indexOf(".") - 1,
142                 coefficient: addDecimalPointToNumber(trimmedFloat.replace(".", ""))
143
144             };
145         }
146
147
148         /**
149          * Converts a base ten number to proper scientific notation
150          * @param {string} stringNumber the string representation of the base ten number to be converted
151          * @returns {string} the number converted to scientific notation
152          */
153         function convertNumberToScientificNotation(stringNumber) {
154             const splitNumber = stringNumber.replace("E", "e").split("e");
155             const originalCoefficient = splitNumber[0];
156             const normalizedNumber = stringNumber.includes(".") ? normalizeFloat(originalCoefficient)
157                 : normalizeInteger(originalCoefficient);
158             const normalizedCoefficient = normalizedNumber.coefficient;
159             const magnitude = splitNumber.length > 1 ? (parseInt(splitNumber[1], 10) + normalizedNumber.magnitude)
160                 : normalizedNumber.magnitude;
161
162             return `${normalizedCoefficient}e${magnitude}`;
163
164         }
165
166         /**
167          * Checks that the user-intended base ten number equals the actual number after is has been converted to the Number type
168          * @param {Node} node the node being evaluated
169          * @returns {boolean} true if they do not match
170          */
171         function baseTenLosesPrecision(node) {
172             const normalizedRawNumber = convertNumberToScientificNotation(getRaw(node));
173             const requestedPrecision = normalizedRawNumber.split("e")[0].replace(".", "").length;
174
175             if (requestedPrecision > 100) {
176                 return true;
177             }
178             const storedNumber = node.value.toPrecision(requestedPrecision);
179             const normalizedStoredNumber = convertNumberToScientificNotation(storedNumber);
180
181             return normalizedRawNumber !== normalizedStoredNumber;
182         }
183
184
185         /**
186          * Checks that the user-intended number equals the actual number after is has been converted to the Number type
187          * @param {Node} node the node being evaluated
188          * @returns {boolean} true if they do not match
189          */
190         function losesPrecision(node) {
191             return isBaseTen(node) ? baseTenLosesPrecision(node) : notBaseTenLosesPrecision(node);
192         }
193
194
195         return {
196             Literal(node) {
197                 if (node.value && isNumber(node) && losesPrecision(node)) {
198                     context.report({
199                         messageId: "noLossOfPrecision",
200                         node
201                     });
202                 }
203             }
204         };
205     }
206 };