Summary
The number overload of choice() parses input with Number(input), so it accepts many coercion forms that Optique's other numeric parsers intentionally reject, including empty strings, whitespace-only strings, hexadecimal, binary, octal, scientific notation, and special numeric values like Infinity when they appear in the choice list. This also makes the parse grammar wider than the help/suggestion surface, which only exposes the canonical choice literals.
Documentation context
docs/concepts/valueparsers.md says Optique value parsers "fail fast, fail clearly" and describes choice() as restricting input to one of several predefined literals. That wording implies number choices should accept the intended CLI number syntax for those literals, not the much wider JavaScript Number() coercion grammar.
Reproduction
import { choice } from "@optique/core/valueparser";
const parser = choice([0, 2, 8, 16, Infinity, -Infinity]);
console.log(parser.parse(""));
console.log(parser.parse(" "));
console.log(parser.parse("0x10"));
console.log(parser.parse("0b10"));
console.log(parser.parse("0o10"));
console.log(parser.parse("2e0"));
console.log(parser.parse("Infinity"));
console.log(parser.parse("-Infinity"));
console.log([...parser.suggest!("")]);
Current behavior:
"" succeeds with 0
- whitespace-only input succeeds with
0
"0x10" succeeds with 16
"0b10" succeeds with 2
"0o10" succeeds with 8
"2e0" succeeds with 2
"Infinity" succeeds when Infinity is in the choice list
"-Infinity" succeeds when -Infinity is in the choice list
- suggestions still only show the canonical configured literals
Expected behavior
Number choices should either clearly document that they use full JavaScript Number() coercion semantics, or they should align with Optique's other numeric parsers and accept only the intended CLI number syntax.
Actual behavior
The parser silently accepts these extra coercion forms because it delegates to Number(input) and then checks membership in the choice list.
Notes
This is especially surprising because float() and integer() explicitly reject empty strings, whitespace-only strings, and non-decimal literals like 0x10, 0b10, and 0o10, so number choice() currently has a much wider numeric grammar than the dedicated numeric parsers.
The implementation cause is visible in packages/core/src/valueparser.ts, where the number branch of choice() does const parsed = Number(input); and then only checks numberChoices.indexOf(parsed) >= 0. There is no separate lexical validation step for CLI number syntax.
Summary
The number overload of
choice()parses input withNumber(input), so it accepts many coercion forms that Optique's other numeric parsers intentionally reject, including empty strings, whitespace-only strings, hexadecimal, binary, octal, scientific notation, and special numeric values likeInfinitywhen they appear in the choice list. This also makes the parse grammar wider than the help/suggestion surface, which only exposes the canonical choice literals.Documentation context
docs/concepts/valueparsers.mdsays Optique value parsers "fail fast, fail clearly" and describeschoice()as restricting input to one of several predefined literals. That wording implies number choices should accept the intended CLI number syntax for those literals, not the much wider JavaScriptNumber()coercion grammar.Reproduction
Current behavior:
""succeeds with00"0x10"succeeds with16"0b10"succeeds with2"0o10"succeeds with8"2e0"succeeds with2"Infinity"succeeds whenInfinityis in the choice list"-Infinity"succeeds when-Infinityis in the choice listExpected behavior
Number choices should either clearly document that they use full JavaScript
Number()coercion semantics, or they should align with Optique's other numeric parsers and accept only the intended CLI number syntax.Actual behavior
The parser silently accepts these extra coercion forms because it delegates to
Number(input)and then checks membership in the choice list.Notes
This is especially surprising because
float()andinteger()explicitly reject empty strings, whitespace-only strings, and non-decimal literals like0x10,0b10, and0o10, so numberchoice()currently has a much wider numeric grammar than the dedicated numeric parsers.The implementation cause is visible in
packages/core/src/valueparser.ts, where the number branch ofchoice()doesconst parsed = Number(input);and then only checksnumberChoices.indexOf(parsed) >= 0. There is no separate lexical validation step for CLI number syntax.