-
-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Catch more unsafe numbers #71
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice job on this. I just left a few notes mostly for cleanup.
src/rules/no-unsafe-values.js
Outdated
@@ -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]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to match the entire number sequence? If not, can you add some comments explaining what patterns these regexes are meant to match?
const SMALL_NUMBERS = [/\.(\d+)/u, /((?:\d+\.)?\d+)e/iu]; | |
const SMALL_NUMBERS = [/^\.(\d+)$/u, /^((?:\d+\.)?\d+)e$/iu]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those are intentionally not anchored. I'll add some comments and examples inline there to make it more clear.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've just rewritten this whole section to make it more clear and less error-prone, I hope. I just translated the ABNF for number
into an anchored regex.
src/rules/no-unsafe-values.js
Outdated
@@ -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.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to give folks a bit more here because I'm guessing a lot of people don't know what "subnormal" means.
subnormal: "Subnormal numbers are outside the safe range.", | |
subnormal: "Unexpected subnormal number '{{value}}` found.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, almost nobody deals with these enough, which is why I think this particular lint is so valuable. Perhaps a link to https://en.wikipedia.org/wiki/Subnormal_number as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We generally don't put links in the error messages, but I think it would be good to add into the rule description. (At some point we'll create a page for each rule, and that would be a good place to go into more detail.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops, I didn't see this until I just sent the update. Willfix.
src/rules/no-unsafe-values.js
Outdated
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)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just eliminating one-character variables.
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)) { | |
for (const pattern of SMALL_NUMBERS) { | |
const match = txt.match(pattern); | |
// If the digits are all 0, it's ok. | |
if (match?.[1]?.match(/[1-9]/u)) { |
src/rules/no-unsafe-values.js
Outdated
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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Expanding variable names.
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) { | |
const buffer = new ArrayBuffer(8); | |
const view = new DataView(buffer); | |
view.setFloat64(0, node.value, false); | |
const bigInt = view.getBigUint64(0, false); | |
// Subnormals have an 11-bit exponent of 0 and a non-zero mantissa. | |
if ((bigInt & 0x7ff0000000000000n) === 0n) { |
src/rules/no-unsafe-values.js
Outdated
@@ -13,6 +19,11 @@ export default { | |||
|
|||
messages: { | |||
unsafeNumber: "Number outside safe range found.", | |||
unsafeInteger: "Integer outside safe range found.", | |||
unsafeZero: | |||
"This number will evaluate to zero, which is unintended.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we include the number to make this a bit clearer?
"This number will evaluate to zero, which is unintended.", | |
"The number '{{ value }}` will evaluate to zero.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's nothing interesting in the number. It's either going to be exactly the same as the highlighted text (if we use txt) or 0 (if we use node.value)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's true when people are using ESLint in an editor. People also use ESLint in their CI system, where it just outputs messages onto the terminal. In that case, having the value in the message is really helpful.
src/rules/no-unsafe-values.js
Outdated
unsafeZero: | ||
"This number will evaluate to zero, which is unintended.", | ||
subnormal: | ||
"Subnormal numbers (see https://en.wikipedia.org/wiki/Subnormal_number) are outside the safe range: '{{ value }}'.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't put URLs in messages as that makes the message more difficult to read.
"Subnormal numbers (see https://en.wikipedia.org/wiki/Subnormal_number) are outside the safe range: '{{ value }}'.", | |
"Unexpected subnormal number '{{ value }}' found. Subnormal numbers are outside the safe range.", |
tests/rules/no-unsafe-values.test.js
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add some tests for unsafeZero
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I had done that, but didn't get the file in the commit. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No worries, I do that all the time!
tests/rules/no-unsafe-values.test.js
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No worries, I do that all the time!
src/rules/no-unsafe-values.js
Outdated
@@ -13,6 +19,11 @@ export default { | |||
|
|||
messages: { | |||
unsafeNumber: "Number outside safe range found.", | |||
unsafeInteger: "Integer outside safe range found.", | |||
unsafeZero: | |||
"This number will evaluate to zero, which is unintended.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's true when people are using ESLint in an editor. People also use ESLint in their CI system, where it just outputs messages onto the terminal. In that case, having the value in the message is really helpful.
src/rules/no-unsafe-values.js
Outdated
@@ -14,7 +14,8 @@ export default { | |||
type: /** @type {const} */ ("problem"), | |||
|
|||
docs: { | |||
description: "Disallow JSON values that are unsafe for interchange", | |||
description: | |||
"Disallow JSON values that are unsafe for interchange. This includes strings with unmatched surrogates, numbers that evaluate to Infinity, numbers that evaluate to zero unintentionally, numbers that look like integers but they are too large, and subnormal numbers (see: https://en.wikipedia.org/wiki/Subnormal_number)", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Descriptions are meant to be one sentence. I think the original description was fine. The rest of these details should go in the README.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is going to modify the readme, I'm going to rebase on top of main. Hopefully the patch isn't large enough that creates a problem.
When the test was copied from a previous example, the name in the comments wasn't changed.
Flag subnormals, numbers that are flushed to zero, and overly-large integers. Refs eslint#68
Sorry forgot to fix the value issue also. On it. |
I made the rest of the errors consistent at the same time. Tests modified. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Waiting for a second review before merging.
src/rules/no-unsafe-values.js
Outdated
data: { value }, | ||
}); | ||
} | ||
} else if (!value.match(/[.e]/iu)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} else if (!value.match(/[.e]/iu)) { | |
} else if (!/[.e]/iu.test(value)) { |
Since we're only checking, so we don't need the match.
src/rules/no-unsafe-values.js
Outdated
const match = value.match(NUMBER); | ||
// assert(match, "If the regex is right, match is always truthy") | ||
|
||
// If any part of the number other than the has a non-zero digit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like some text is missing between "the" and "has"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"exponent". fixing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re-approving. Waiting for @mdjermanovic to verify his comments have been addressed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks!
Prerequisites checklist
What is the purpose of this pull request?
Catch subnormals, numbers that are flushed to zero,
and overly-large integers in no-unsafe-values.
What changes did you make? (Give an overview)
Added three numeric cases to no-unsafe-values.js:
1e-400
[Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER]
.Example: 9007199254740992
2.2250738585072009e-308
Related Issues
Refs #68
Is there anything you'd like reviewers to focus on?
Please double-check my error messages for your preferences.