Skip to content

Commit a3cfef7

Browse files
committed
more number handling tweaks
- fix handling of negative numbers - infer string if the number would change at all
1 parent eaa40dd commit a3cfef7

File tree

4 files changed

+48
-27
lines changed

4 files changed

+48
-27
lines changed

packages/env-spec-parser/src/helpers.ts

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,29 @@
1-
const VALID_NUMBER_REGEX = /^(0|([1-9][0-9]*))?(\.[0-9]+)?$/;
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+
27
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`
13+
// the regex check filters out a lot of ambiguous/weird cases
914
if (VALID_NUMBER_REGEX.test(valStr)) {
1015
const num = Number(valStr);
11-
// If the number is not finite, preserve as string
12-
if (!Number.isFinite(num)) {
13-
return valStr;
14-
}
15-
// For integers, check if they're within safe integer bounds
16-
// Numbers beyond MAX_SAFE_INTEGER can lose precision
17-
if (Number.isInteger(num) && !Number.isSafeInteger(num)) {
18-
return valStr;
19-
}
20-
// Check if conversion back to string uses scientific notation
21-
// This catches very large numbers that get converted to scientific notation
22-
// For example: '92183090832018209318123781721.12231' -> '9.21830908320182e+28' (precision lost)
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)
2319
const backToString = String(num);
24-
if (backToString.includes('e') || backToString.includes('E')) {
25-
return valStr;
26-
}
27-
// Check if the number is outside safe bounds for floating point precision
28-
// Numbers with absolute value > MAX_SAFE_INTEGER may lose precision
29-
if (Math.abs(num) > Number.MAX_SAFE_INTEGER) {
30-
return valStr;
31-
}
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
3227
return num;
3328
}
3429
return valStr;

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,15 +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
102111
['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()],
103117
]));
104118

105119
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)