]> git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/no-loss-of-precision.js
update to 7.1.0 sources
[pve-eslint.git] / 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 /**
41 * Checks whether the number is base ten
42 * @param {ASTNode} node the node being evaluated
43 * @returns {boolean} true if the node is in base ten
44 */
45 function isBaseTen(node) {
46 const prefixes = ["0x", "0X", "0b", "0B", "0o", "0O"];
47
48 return prefixes.every(prefix => !node.raw.startsWith(prefix)) &&
49 !/^0[0-7]+$/u.test(node.raw);
50 }
51
52 /**
53 * Checks that the user-intended non-base ten number equals the actual number after is has been converted to the Number type
54 * @param {Node} node the node being evaluated
55 * @returns {boolean} true if they do not match
56 */
57 function notBaseTenLosesPrecision(node) {
58 const rawString = node.raw.toUpperCase();
59 let base = 0;
60
61 if (rawString.startsWith("0B")) {
62 base = 2;
63 } else if (rawString.startsWith("0X")) {
64 base = 16;
65 } else {
66 base = 8;
67 }
68
69 return !rawString.endsWith(node.value.toString(base).toUpperCase());
70 }
71
72 /**
73 * Adds a decimal point to the numeric string at index 1
74 * @param {string} stringNumber the numeric string without any decimal point
75 * @returns {string} the numeric string with a decimal point in the proper place
76 */
77 function addDecimalPointToNumber(stringNumber) {
78 return `${stringNumber.slice(0, 1)}.${stringNumber.slice(1)}`;
79 }
80
81 /**
82 * Returns the number stripped of leading zeros
83 * @param {string} numberAsString the string representation of the number
84 * @returns {string} the stripped string
85 */
86 function removeLeadingZeros(numberAsString) {
87 return numberAsString.replace(/^0*/u, "");
88 }
89
90 /**
91 * Returns the number stripped of trailing zeros
92 * @param {string} numberAsString the string representation of the number
93 * @returns {string} the stripped string
94 */
95 function removeTrailingZeros(numberAsString) {
96 return numberAsString.replace(/0*$/u, "");
97 }
98
99 /**
100 * Converts an integer to to an object containing the the integer's coefficient and order of magnitude
101 * @param {string} stringInteger the string representation of the integer being converted
102 * @returns {Object} the object containing the the integer's coefficient and order of magnitude
103 */
104 function normalizeInteger(stringInteger) {
105 const significantDigits = removeTrailingZeros(removeLeadingZeros(stringInteger));
106
107 return {
108 magnitude: stringInteger.startsWith("0") ? stringInteger.length - 2 : stringInteger.length - 1,
109 coefficient: addDecimalPointToNumber(significantDigits)
110 };
111 }
112
113 /**
114 *
115 * Converts a float to to an object containing the the floats's coefficient and order of magnitude
116 * @param {string} stringFloat the string representation of the float being converted
117 * @returns {Object} the object containing the the integer's coefficient and order of magnitude
118 */
119 function normalizeFloat(stringFloat) {
120 const trimmedFloat = removeLeadingZeros(stringFloat);
121
122 if (trimmedFloat.startsWith(".")) {
123 const decimalDigits = trimmedFloat.split(".").pop();
124 const significantDigits = removeLeadingZeros(decimalDigits);
125
126 return {
127 magnitude: significantDigits.length - decimalDigits.length - 1,
128 coefficient: addDecimalPointToNumber(significantDigits)
129 };
130
131 }
132 return {
133 magnitude: trimmedFloat.indexOf(".") - 1,
134 coefficient: addDecimalPointToNumber(trimmedFloat.replace(".", ""))
135
136 };
137 }
138
139
140 /**
141 * Converts a base ten number to proper scientific notation
142 * @param {string} stringNumber the string representation of the base ten number to be converted
143 * @returns {string} the number converted to scientific notation
144 */
145 function convertNumberToScientificNotation(stringNumber) {
146 const splitNumber = stringNumber.replace("E", "e").split("e");
147 const originalCoefficient = splitNumber[0];
148 const normalizedNumber = stringNumber.includes(".") ? normalizeFloat(originalCoefficient)
149 : normalizeInteger(originalCoefficient);
150 const normalizedCoefficient = normalizedNumber.coefficient;
151 const magnitude = splitNumber.length > 1 ? (parseInt(splitNumber[1], 10) + normalizedNumber.magnitude)
152 : normalizedNumber.magnitude;
153
154 return `${normalizedCoefficient}e${magnitude}`;
155
156 }
157
158 /**
159 * Checks that the user-intended base ten number equals the actual number after is has been converted to the Number type
160 * @param {Node} node the node being evaluated
161 * @returns {boolean} true if they do not match
162 */
163 function baseTenLosesPrecision(node) {
164 const normalizedRawNumber = convertNumberToScientificNotation(node.raw);
165 const requestedPrecision = normalizedRawNumber.split("e")[0].replace(".", "").length;
166
167 if (requestedPrecision > 100) {
168 return true;
169 }
170 const storedNumber = node.value.toPrecision(requestedPrecision);
171 const normalizedStoredNumber = convertNumberToScientificNotation(storedNumber);
172
173 return normalizedRawNumber !== normalizedStoredNumber;
174 }
175
176
177 /**
178 * Checks that the user-intended number equals the actual number after is has been converted to the Number type
179 * @param {Node} node the node being evaluated
180 * @returns {boolean} true if they do not match
181 */
182 function losesPrecision(node) {
183 return isBaseTen(node) ? baseTenLosesPrecision(node) : notBaseTenLosesPrecision(node);
184 }
185
186
187 return {
188 Literal(node) {
189 if (node.value && isNumber(node) && losesPrecision(node)) {
190 context.report({
191 messageId: "noLossOfPrecision",
192 node
193 });
194 }
195 }
196 };
197 }
198 };