Skip to content

Commit 82a7340

Browse files
don't coerce out of bounds numbers (#219)
* don't coerce out of bounds numbers * changeset * more number handling tweaks - fix handling of negative numbers - infer string if the number would change at all --------- Co-authored-by: Theo Ephraim <[email protected]>
1 parent 23ed768 commit 82a7340

File tree

5 files changed

+60
-9
lines changed

5 files changed

+60
-9
lines changed

.changeset/purple-teams-hear.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@env-spec/parser": patch
3+
---
4+
5+
Fixed a bug where values that resembled very large numbers were being improperly coerced
Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,30 @@
1-
const VALID_NUMBER_REGEX = /^(0|([1-9][0-9]*))?(\.[0-9]+)?$/;
2-
export function autoCoerce(valStr) {
1+
// this regex helps avoid any weird numbers
2+
// - leading zeros
3+
// - scientific notation
4+
// - multiple decimal points
5+
const VALID_NUMBER_REGEX = /^-?(0|([1-9][0-9]*))?(\.[0-9]+)?$/;
6+
7+
export function autoCoerce(valStr: string): string | number | boolean | undefined {
38
if (valStr === 'true') return true;
49
if (valStr === 'false') return false;
510
if (valStr === 'undefined') return undefined;
611
// not handling `null` because its a JS specific thing... we'll try to avoid it altogether
712

8-
// special check to avoid weird number coercion like `01`, `1e10`
9-
if (VALID_NUMBER_REGEX.test(valStr)) return Number(valStr);
13+
// the regex check filters out a lot of ambiguous/weird cases
14+
if (VALID_NUMBER_REGEX.test(valStr)) {
15+
const num = Number(valStr);
16+
17+
// Check if we can convert back to a string without any changes
18+
// this could be loss in precision or just formatting (extra zeros)
19+
const backToString = String(num);
20+
if (backToString !== valStr) return valStr;
21+
22+
// if numbers are too large, we'll leave them as strings
23+
if (Number.isInteger(num) && !Number.isSafeInteger(num)) return valStr;
24+
if (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) return valStr;
25+
26+
// otherwise we can return the number
27+
return num;
28+
}
1029
return valStr;
1130
}

packages/env-spec-parser/test/values.test.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,29 @@ describe('boolean handling', basicValueTests([
9191
describe('number handling', basicValueTests([
9292
['0', 0],
9393
['123', 123],
94+
['-123', -123],
9495
['123.456', 123.456],
95-
['.0', 0],
96-
['.01230', 0.0123],
96+
['-123.456', -123.456],
97+
['0.0123', 0.0123],
98+
// if the number is not converted cleanly back to a string, we leave as a string
99+
['.0', '.0'],
100+
['.01230', '.01230'],
101+
['1.230', '1.230'],
102+
// number-ish but not quite numbers
97103
['123.456.789', '123.456.789'],
98104
['10e3', '10e3'],
99105
['001', '001'],
106+
['01', '01'],
100107
['123.', '123.'],
101108
['123..', '123..'],
109+
['Infinity', 'Infinity'],
110+
// numbers that would lose precision are treated as strings
111+
['92183090832018209318123781721.12231', '92183090832018209318123781721.12231'],
112+
['1.23123412341234123414352345234523452345234523452345234523452345234523452345', '1.23123412341234123414352345234523452345234523452345234523452345234523452345'],
113+
// max safe integer is still a number
114+
[(Number.MAX_SAFE_INTEGER).toString(), Number.MAX_SAFE_INTEGER],
115+
// but anything larger is treated as a string
116+
[(Number.MAX_SAFE_INTEGER + 1).toString(), (Number.MAX_SAFE_INTEGER + 1).toString()],
102117
]));
103118

104119
describe('function calls', basicValueTests([

packages/varlock-website/src/content/docs/env-spec/reference.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ Values are interpreted similarly for config item values, decorator values, and v
151151
#### Unquoted values
152152
- Will coerce `true`, `false`, `undefined` -- `@foo=false`
153153
- Will coerce numeric values -- `@int=123 @float=123.456`
154+
- if the number is too large, would lose precision, or would change formatting, it will remain a string
154155
- May be interpreted as a function call (see below)
155156
- Otherwise will be treated as a string
156157
- May not contain other characters depending on the context:

packages/varlock-website/src/content/docs/reference/data-types.mdx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,28 @@ The internal coercion/validation process looks like:<br/>
3232

3333

3434
### Default behavior
35-
When no `@type` is specified, a type will be inferred where possible - for static values, and some functions that return a known type. Otherwise the type will default to `string`.
35+
When no `@type` is specified, a type will be inferred where possible - for static values, and some functions that return a known type. Note that the use of quotes matters. Otherwise the type will default to `string`.
3636

3737
```env-spec
38-
INFERRED_STRING="foo"
39-
INFERRED_NUMBER=123
38+
INFERRED_STRING_QUOTED="foo"
39+
INFERRED_STRING_UNQUOTED=foo
40+
INFERRED_NUMBER=123 # infers number type
41+
QUOTED_NUM_STRING="123" # remains a string unless @type=number is used
4042
INFERRED_BOOLEAN=true
43+
44+
# return type of some functions can be inferred
4145
CONCAT_INFERS_STRING=`concat-${SOMEVAR}-will-be-string`
46+
FN_INFER_BOOLEAN=eq($VAR1, $VAR2)
4247
DEFAULTS_TO_STRING_FN=fnThatCannotInferType()
48+
49+
# with no other info, we default to string
4350
DEFAULTS_TO_STRING=
4451
```
4552

53+
Note that numeric values that would lose precision, or change any formatting (like leading/trailing zeros), will be treated as strings unless explicitly adding `@type=number`.
54+
55+
In any slightly ambiguous situation, it is better to explicitly add a `@type` decorator.
56+
4657

4758
## Built-in data types
4859

0 commit comments

Comments
 (0)