Skip to content

Number choice() parses hex, binary, octal, scientific, and whitespace-padded inputs via Number() coercion #315

@dahlia

Description

@dahlia

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.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions