Skip to content

Commit

Permalink
feat: Catch more unsafe numbers
Browse files Browse the repository at this point in the history
Flag subnormals, numbers that are flushed to zero,
and overly-large integers.

Refs eslint#68
  • Loading branch information
hildjj committed Dec 4, 2024
1 parent 99c2c95 commit f0a6cd0
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 0 deletions.
48 changes: 48 additions & 0 deletions src/rules/no-unsafe-values.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
* @author Bradley Meck Farias
*/

// These numbers evaluated to zero unexpectedly. If the group's digits
// are all zero, that's fine.
const SMALL_NUMBERS = [/\.(\d+)/u, /((?:\d+\.)?\d+)e/iu];

export default {
meta: {
type: /** @type {const} */ ("problem"),
Expand All @@ -13,6 +17,8 @@ export default {

messages: {
unsafeNumber: "Number outside safe range found.",
unsafeInteger: "Integer outside safe range found.",
subnormal: "Subnormal numbers are outside the safe range.",
loneSurrogate: "Lone surrogate '{{ surrogate }}' found.",
},
},
Expand All @@ -25,6 +31,48 @@ export default {
loc: node.loc,
messageId: "unsafeNumber",
});
} else {
const txt = context.sourceCode.getText(node);
if (node.value === 0) {
// If the value has been rounded down to 0, but there was some fraction
// or non-zero part before an e-, this is a very small number that doesn't
// fit inside an f64.
for (const r of SMALL_NUMBERS) {
const m = txt.match(r);
// If the digits are all 0, it's ok.
if (m?.[1]?.match(/[1-9]/u)) {
context.report({
loc: node.loc,
messageId: "unsafeNumber",
});
break;
}
}
} else if (!txt.match(/[.e]/iu)) {
// Intended to be an integer
if (
node.value > Number.MAX_SAFE_INTEGER ||
node.value < Number.MIN_SAFE_INTEGER
) {
context.report({
loc: node.loc,
messageId: "unsafeInteger",
});
}
} else {
// Floating point. Check for subnormal.
const ab = new ArrayBuffer(8);
const dv = new DataView(ab);
dv.setFloat64(0, node.value, false);
const bi = dv.getBigUint64(0, false);
// Subnormals have an 11-bit exponent of 0 and a non-zero mantissa.
if ((bi & 0x7ff0000000000000n) === 0n) {
context.report({
loc: node.loc,
messageId: "subnormal",
});
}
}
}
},
String(node) {
Expand Down
108 changes: 108 additions & 0 deletions tests/rules/no-unsafe-values.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,5 +131,113 @@ ruleTester.run("no-unsafe-values", rule, {
},
],
},
{
code: "1e-400",
errors: [
{
messageId: "unsafeNumber",
line: 1,
column: 1,
endLine: 1,
endColumn: 7,
},
],
},
{
code: "-1e-400",
errors: [
{
messageId: "unsafeNumber",
line: 1,
column: 1,
endLine: 1,
endColumn: 8,
},
],
},
{
code: "0.01e-400",
errors: [
{
messageId: "unsafeNumber",
line: 1,
column: 1,
endLine: 1,
endColumn: 10,
},
],
},
{
code: "-10.2e-402",
errors: [
{
messageId: "unsafeNumber",
line: 1,
column: 1,
endLine: 1,
endColumn: 11,
},
],
},
{
code: `0.${"0".repeat(400)}1`,
errors: [
{
messageId: "unsafeNumber",
line: 1,
column: 1,
endLine: 1,
endColumn: 404,
},
],
},
{
code: "9007199254740992",
errors: [
{
messageId: "unsafeInteger",
line: 1,
column: 1,
endLine: 1,
endColumn: 17,
},
],
},
{
code: "-9007199254740992",
errors: [
{
messageId: "unsafeInteger",
line: 1,
column: 1,
endLine: 1,
endColumn: 18,
},
],
},
{
code: "2.2250738585072009e-308",
errors: [
{
messageId: "subnormal",
line: 1,
column: 1,
endLine: 1,
endColumn: 24,
},
],
},
{
code: "-2.2250738585072009e-308",
errors: [
{
messageId: "subnormal",
line: 1,
column: 1,
endLine: 1,
endColumn: 25,
},
],
},
],
});

0 comments on commit f0a6cd0

Please sign in to comment.