-
Notifications
You must be signed in to change notification settings - Fork 4
strict mode for parsing numbers #6
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
base: master
Are you sure you want to change the base?
Conversation
Codecov Report
@@ Coverage Diff @@
## master #6 +/- ##
=====================================
Coverage 100% 100%
=====================================
Files 1 1
Lines 57 91 +34
Branches 11 25 +14
=====================================
+ Hits 57 91 +34
Continue to review full report at Codecov.
|
src/index.test.ts
Outdated
'23e+0.07', | ||
]; | ||
|
||
test('throws in mode when a number with invalid e-notation is provided', (t) => { |
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.
Did you mean throws "in strict mode"?
if (/%$/.test(value)) { | ||
this.type = 'percentage'; | ||
this.value = tryParseFloat(value); | ||
this.value = tryParseNumber(value.substring(0, value.length - 1), strict); |
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.
What's going on here with the substring?
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.
it removes the percent sign so that (potentially) only the number itself will be parsed (removal of the percent sign could be omitted in non-strict mode)
@@ -8,37 +8,46 @@ const cssResolutionUnits: string[] = require('css-resolution-units'); | |||
const cssFrequencyUnits: string[] = require('css-frequency-units'); | |||
const cssTimeUnits: string[] = require('css-time-units'); | |||
|
|||
const numberPrefixPattern = /^(\+|-)?(\.)?\d/; |
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.
Why is this so far away from the implementation? Also, I can remove the trailing \d
and all the tests still pass.
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.
yes, there needs to be be test added for this:
without this you could enter "NaN%" and it would falsely validate it a number
src/index.ts
Outdated
dots = countDots(value); | ||
} | ||
if (dots > 0) { | ||
if (!allowDot) { |
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.
Some weird indentation in this block.
src/index.ts
Outdated
@@ -82,6 +95,69 @@ function tryParseFloat(value: string) { | |||
return result; | |||
} | |||
|
|||
function normalizeNumber(value: string, allowDot: boolean = true): string { |
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 was able to crunch this function down to the following without breaking tests:
function normalizeNumber(value: string, allowDot = true) {
const match = numberPrefixPattern.exec(value);
if (!match) {
throw new Error(`Invalid number: ${value}`);
}
const [, sign, dot] = match;
if (sign === '+') {
value = value.substr(1);
}
if (dot) {
if (!allowDot) {
throw new Error(`Invalid number (too many dots): ${value}`);
}
if (sign === '-') {
value = '-0' + value.substr(1);
} else {
value = '0' + value;
}
}
return (dot || countDots(value))
? value.replace(/\.?0+$/, '')
: 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.
yes -- the previous version did focus on computing as little as possible, e.g. run countDots()
only if necessary, and also apply replace(/\.?0+$/, '')
only on input where it would have an effect
but the gain of that is probably negligible
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.
Yeah and it still has to do the check before doing the replace, so it probably ends up being about the same performance there.
src/index.ts
Outdated
function tryParseStrict(value: string): number { | ||
const nval = normalizeNumber(value); | ||
const result = parseFloat(nval); | ||
if (result.toString() !== nval) { |
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.
Simplify:
if (result.toString() !== nval && !verifyZero(value) && !verifyENotation(value)) {
throw new Error(`Invalid number: ${value}`);
}
return result;
src/index.ts
Outdated
return value; | ||
} | ||
|
||
function tryParseStrict(value: string): 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.
Please remove the return type here.
} | ||
|
||
function verifyZero(value: string) { | ||
return /^[-+]?0\.0+$/.test(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.
The following works w/o breaking tests:
return parseFloat(value) === 0;
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 guess so, but parseFloat()
will always ignore trailing non-number parts in the string -- I have not thought too hard about this (in this instance), but it may be that there is such a case that it would fail here (i.e. falsely claim a non-valid input as 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.
Yes, this is the discussion I was trying to spark here. Those other cases might be worth testing. You could also consider Math.abs(value) === 0
.
src/index.ts
Outdated
const nval = normalizeNumber(value); | ||
const result = parseFloat(nval); | ||
if (result.toString() !== nval) { | ||
if (verifyZero(value) || verifyENotation(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.
Honestly, I think if we just inlined Math.abs(value) === 0
here it would be pretty 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.
do you mean Math.abs(parseFloat(value)) === 0
?
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 I don't mean that. Did you have issues w/o the parseFloat
? Because I wasn't having issues in my console.
I made those changes where did not have any questions & pushed them into the branch/PR Also: I'll open another PR for this alternative approach and maybe you can say which one is more appropriate here. |
added a strict-mode for parsing numbers (see issue #4 ):
the basic idea is the same as parsing for "numbers only" in jednano/parse-css-font#11:
manipulating the input-string so that
parseFloat().toString()
would return the exact same string.There are some special cases for which this does not work (namely e-notations; here it just makes sure that the exponent is an integer, but does not compare the complete evaluated number against the input-string) or where some more elaborate manipulation of the input-string is required (namely related to zeros at the beginning or the end), in order to successfully match a valid number against the the
parseFloat()
result.This approach does somewhat favor handling the standard cases efficiently, and applies more involved processing for the special cases.
And it does not yet support numbers with leading zeros like
00056
.I would like a general feedback, before continuing to work on this ;-)