@optique/core 1.0.0-dev.1510 → 1.0.0-dev.1519

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/facade.cjs CHANGED
@@ -587,6 +587,45 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
587
587
  const versionCommandNames = versionCommandConfig?.names ?? ["version"];
588
588
  const completionCommandNames = completionCommandConfig?.names ?? ["completion"];
589
589
  const completionOptionNames = completionOptionConfig?.names ?? ["--completion"];
590
+ const activeMetaEntries = [];
591
+ if (options.help && helpOptionConfig) activeMetaEntries.push([
592
+ "option",
593
+ "help option",
594
+ helpOptionNames
595
+ ]);
596
+ if (options.help && helpCommandConfig) activeMetaEntries.push([
597
+ "command",
598
+ "help command",
599
+ helpCommandNames
600
+ ]);
601
+ if (options.version && versionOptionConfig) activeMetaEntries.push([
602
+ "option",
603
+ "version option",
604
+ versionOptionNames
605
+ ]);
606
+ if (options.version && versionCommandConfig) activeMetaEntries.push([
607
+ "command",
608
+ "version command",
609
+ versionCommandNames
610
+ ]);
611
+ if (options.completion && completionOptionConfig) activeMetaEntries.push([
612
+ "option",
613
+ "completion option",
614
+ completionOptionNames,
615
+ true
616
+ ]);
617
+ if (options.completion && completionCommandConfig) activeMetaEntries.push([
618
+ "command",
619
+ "completion command",
620
+ completionCommandNames
621
+ ]);
622
+ require_validate.validateMetaNameCollisions({
623
+ leadingOptions: require_usage.extractLeadingOptionNames(parser.usage, true),
624
+ leadingCommands: require_usage.extractLeadingCommandNames(parser.usage, true),
625
+ allOptions: require_usage.extractOptionNames(parser.usage, true),
626
+ allCommands: require_usage.extractCommandNames(parser.usage, true),
627
+ allLiterals: require_usage.extractLiteralValues(parser.usage)
628
+ }, activeMetaEntries);
590
629
  const defaultShells = {
591
630
  bash: require_completion.bash,
592
631
  fish: require_completion.fish,
@@ -1173,15 +1212,15 @@ function disposeContextsSync(contexts) {
1173
1212
  */
1174
1213
  async function runWith(parser, programName, contexts, options) {
1175
1214
  const args = options?.args ?? [];
1176
- if (needsEarlyExit(args, options)) {
1177
- if (parser.$mode === "async") return runParser(parser, programName, args, options);
1178
- return Promise.resolve(runParser(parser, programName, args, options));
1179
- }
1180
1215
  if (contexts.length === 0) {
1181
1216
  if (parser.$mode === "async") return runParser(parser, programName, args, options);
1182
1217
  return Promise.resolve(runParser(parser, programName, args, options));
1183
1218
  }
1184
1219
  try {
1220
+ if (needsEarlyExit(args, options)) {
1221
+ if (parser.$mode === "async") return runParser(parser, programName, args, options);
1222
+ return Promise.resolve(runParser(parser, programName, args, options));
1223
+ }
1185
1224
  const ctxOptions = options?.contextOptions;
1186
1225
  const { annotations: phase1Annotations, annotationsList: phase1AnnotationsList, hasDynamic: needsTwoPhase } = await collectPhase1Annotations(contexts, ctxOptions);
1187
1226
  if (!needsTwoPhase) {
@@ -1245,9 +1284,9 @@ async function runWith(parser, programName, contexts, options) {
1245
1284
  function runWithSync(parser, programName, contexts, options) {
1246
1285
  if (parser.$mode !== "sync") throw new TypeError("Cannot use an async parser with runWithSync(). Use runWith() or runWithAsync() instead.");
1247
1286
  const args = options?.args ?? [];
1248
- if (needsEarlyExit(args, options)) return runParser(parser, programName, args, options);
1249
1287
  if (contexts.length === 0) return runParser(parser, programName, args, options);
1250
1288
  try {
1289
+ if (needsEarlyExit(args, options)) return runParser(parser, programName, args, options);
1251
1290
  const ctxOptions = options?.contextOptions;
1252
1291
  const { annotations: phase1Annotations, annotationsList: phase1AnnotationsList, hasDynamic: needsTwoPhase } = collectPhase1AnnotationsSync(contexts, ctxOptions);
1253
1292
  if (!needsTwoPhase) {
package/dist/facade.js CHANGED
@@ -2,8 +2,8 @@ import { injectAnnotations } from "./annotations.js";
2
2
  import { commandLine, formatMessage, lineBreak, message, optionName, text, value } from "./message.js";
3
3
  import { bash, fish, nu, pwsh, zsh } from "./completion.js";
4
4
  import { dispatchByMode } from "./mode-dispatch.js";
5
- import { validateCommandNames, validateOptionNames } from "./validate.js";
6
- import { formatUsage } from "./usage.js";
5
+ import { validateCommandNames, validateMetaNameCollisions, validateOptionNames } from "./validate.js";
6
+ import { extractCommandNames, extractLeadingCommandNames, extractLeadingOptionNames, extractLiteralValues, extractOptionNames, formatUsage } from "./usage.js";
7
7
  import { formatDocPage } from "./doc.js";
8
8
  import { group, longestMatch, object } from "./constructs.js";
9
9
  import { multiple, optional, withDefault } from "./modifiers.js";
@@ -587,6 +587,45 @@ function runParser(parserOrProgram, programNameOrArgs, argsOrOptions, optionsPar
587
587
  const versionCommandNames = versionCommandConfig?.names ?? ["version"];
588
588
  const completionCommandNames = completionCommandConfig?.names ?? ["completion"];
589
589
  const completionOptionNames = completionOptionConfig?.names ?? ["--completion"];
590
+ const activeMetaEntries = [];
591
+ if (options.help && helpOptionConfig) activeMetaEntries.push([
592
+ "option",
593
+ "help option",
594
+ helpOptionNames
595
+ ]);
596
+ if (options.help && helpCommandConfig) activeMetaEntries.push([
597
+ "command",
598
+ "help command",
599
+ helpCommandNames
600
+ ]);
601
+ if (options.version && versionOptionConfig) activeMetaEntries.push([
602
+ "option",
603
+ "version option",
604
+ versionOptionNames
605
+ ]);
606
+ if (options.version && versionCommandConfig) activeMetaEntries.push([
607
+ "command",
608
+ "version command",
609
+ versionCommandNames
610
+ ]);
611
+ if (options.completion && completionOptionConfig) activeMetaEntries.push([
612
+ "option",
613
+ "completion option",
614
+ completionOptionNames,
615
+ true
616
+ ]);
617
+ if (options.completion && completionCommandConfig) activeMetaEntries.push([
618
+ "command",
619
+ "completion command",
620
+ completionCommandNames
621
+ ]);
622
+ validateMetaNameCollisions({
623
+ leadingOptions: extractLeadingOptionNames(parser.usage, true),
624
+ leadingCommands: extractLeadingCommandNames(parser.usage, true),
625
+ allOptions: extractOptionNames(parser.usage, true),
626
+ allCommands: extractCommandNames(parser.usage, true),
627
+ allLiterals: extractLiteralValues(parser.usage)
628
+ }, activeMetaEntries);
590
629
  const defaultShells = {
591
630
  bash,
592
631
  fish,
@@ -1173,15 +1212,15 @@ function disposeContextsSync(contexts) {
1173
1212
  */
1174
1213
  async function runWith(parser, programName, contexts, options) {
1175
1214
  const args = options?.args ?? [];
1176
- if (needsEarlyExit(args, options)) {
1177
- if (parser.$mode === "async") return runParser(parser, programName, args, options);
1178
- return Promise.resolve(runParser(parser, programName, args, options));
1179
- }
1180
1215
  if (contexts.length === 0) {
1181
1216
  if (parser.$mode === "async") return runParser(parser, programName, args, options);
1182
1217
  return Promise.resolve(runParser(parser, programName, args, options));
1183
1218
  }
1184
1219
  try {
1220
+ if (needsEarlyExit(args, options)) {
1221
+ if (parser.$mode === "async") return runParser(parser, programName, args, options);
1222
+ return Promise.resolve(runParser(parser, programName, args, options));
1223
+ }
1185
1224
  const ctxOptions = options?.contextOptions;
1186
1225
  const { annotations: phase1Annotations, annotationsList: phase1AnnotationsList, hasDynamic: needsTwoPhase } = await collectPhase1Annotations(contexts, ctxOptions);
1187
1226
  if (!needsTwoPhase) {
@@ -1245,9 +1284,9 @@ async function runWith(parser, programName, contexts, options) {
1245
1284
  function runWithSync(parser, programName, contexts, options) {
1246
1285
  if (parser.$mode !== "sync") throw new TypeError("Cannot use an async parser with runWithSync(). Use runWith() or runWithAsync() instead.");
1247
1286
  const args = options?.args ?? [];
1248
- if (needsEarlyExit(args, options)) return runParser(parser, programName, args, options);
1249
1287
  if (contexts.length === 0) return runParser(parser, programName, args, options);
1250
1288
  try {
1289
+ if (needsEarlyExit(args, options)) return runParser(parser, programName, args, options);
1251
1290
  const ctxOptions = options?.contextOptions;
1252
1291
  const { annotations: phase1Annotations, annotationsList: phase1AnnotationsList, hasDynamic: needsTwoPhase } = collectPhase1AnnotationsSync(contexts, ctxOptions);
1253
1292
  if (!needsTwoPhase) {
package/dist/index.cjs CHANGED
@@ -53,6 +53,9 @@ exports.ensureNonEmptyString = require_nonempty.ensureNonEmptyString;
53
53
  exports.envVar = require_message.envVar;
54
54
  exports.extractArgumentMetavars = require_usage.extractArgumentMetavars;
55
55
  exports.extractCommandNames = require_usage.extractCommandNames;
56
+ exports.extractLeadingCommandNames = require_usage.extractLeadingCommandNames;
57
+ exports.extractLeadingOptionNames = require_usage.extractLeadingOptionNames;
58
+ exports.extractLiteralValues = require_usage.extractLiteralValues;
56
59
  exports.extractOptionNames = require_usage.extractOptionNames;
57
60
  exports.fail = require_primitives.fail;
58
61
  exports.fish = require_completion.fish;
package/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Annotations, ParseOptions, annotationKey, getAnnotations } from "./annotations.cjs";
2
2
  import { NonEmptyString, ensureNonEmptyString, isNonEmptyString } from "./nonempty.cjs";
3
3
  import { Message, MessageFormatOptions, MessageTerm, ValueSetOptions, commandLine, envVar, formatMessage, lineBreak, link, message, metavar, optionName, optionNames, text, value, valueSet, values } from "./message.cjs";
4
- import { HiddenVisibility, OptionName, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, cloneUsage, cloneUsageTerm, extractArgumentMetavars, extractCommandNames, extractOptionNames, formatUsage, formatUsageTerm, isDocHidden, isSuggestionHidden, isUsageHidden, mergeHidden, normalizeUsage } from "./usage.cjs";
4
+ import { HiddenVisibility, OptionName, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, cloneUsage, cloneUsageTerm, extractArgumentMetavars, extractCommandNames, extractLeadingCommandNames, extractLeadingOptionNames, extractLiteralValues, extractOptionNames, formatUsage, formatUsageTerm, isDocHidden, isSuggestionHidden, isUsageHidden, mergeHidden, normalizeUsage } from "./usage.cjs";
5
5
  import { DocEntry, DocFragment, DocFragments, DocPage, DocPageFormatOptions, DocSection, ShowChoicesOptions, ShowDefaultOptions, cloneDocEntry, deduplicateDocEntries, deduplicateDocFragments, formatDocPage, isDocEntryHidden } from "./doc.cjs";
6
6
  import { ChoiceOptions, ChoiceOptionsBase, ChoiceOptionsNumber, ChoiceOptionsString, CidrOptions, CidrValue, DeferredMap, DomainOptions, EmailOptions, FloatOptions, HostnameOptions, IntegerOptionsBigInt, IntegerOptionsNumber, IpOptions, Ipv4Options, Ipv6Options, LocaleOptions, MacAddressOptions, PortOptionsBigInt, PortOptionsNumber, PortRangeOptionsBigInt, PortRangeOptionsNumber, PortRangeValueBigInt, PortRangeValueNumber, SocketAddressOptions, SocketAddressValue, StringOptions, UrlOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, checkBooleanOption, checkEnumOption, choice, cidr, domain, email, float, hostname, integer, ip, ipv4, ipv6, isValueParser, locale, macAddress, port, portRange, socketAddress, string, url, uuid } from "./valueparser.cjs";
7
7
  import { ConditionalErrorOptions, ConditionalOptions, DuplicateOptionError, GroupOptions, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, conditional, group, longestMatch, merge, object, or, tuple } from "./constructs.cjs";
@@ -12,4 +12,4 @@ import { CombineModes, DocState, InferMode, InferValue, Mode, ModeIterable, Mode
12
12
  import { ShellCompletion, bash, fish, nu, pwsh, zsh } from "./completion.cjs";
13
13
  import { ParserValuePlaceholder, SourceContext } from "./context.cjs";
14
14
  import { CommandSubConfig, ContextOptionsParam, ExtractRequiredOptions, OptionSubConfig, RunOptions, RunParserError, RunWithOptions, SubstituteParserValue, runParser, runParserAsync, runParserSync, runWith, runWithAsync, runWithSync } from "./facade.cjs";
15
- export { type Annotations, AnyDependencySource, ArgumentErrorOptions, ArgumentOptions, ChoiceOptions, ChoiceOptionsBase, ChoiceOptionsNumber, ChoiceOptionsString, CidrOptions, CidrValue, CombineMode, CombineModes, CombinedDependencyMode, CommandErrorOptions, CommandOptions, CommandSubConfig, ConditionalErrorOptions, ConditionalOptions, ContextOptionsParam, DeferredMap, DeferredParseState, DependencyError, DependencyMode, DependencyRegistry, DependencySource, DependencySourceState, DependencyValue, DependencyValues, DeriveAsyncOptions, DeriveFromAsyncOptions, DeriveFromOptions, DeriveFromSyncOptions, DeriveOptions, DeriveSyncOptions, DerivedValueParser, DocEntry, DocFragment, DocFragments, DocPage, DocPageFormatOptions, DocSection, DocState, DomainOptions, DuplicateOptionError, EmailOptions, ExtractRequiredOptions, FlagErrorOptions, FlagOptions, FloatOptions, GroupOptions, HiddenVisibility, HostnameOptions, InferMode, InferValue, IntegerOptionsBigInt, IntegerOptionsNumber, IpOptions, Ipv4Options, Ipv6Options, LocaleOptions, LongestMatchErrorOptions, LongestMatchOptions, MacAddressOptions, MergeOptions, type Message, type MessageFormatOptions, type MessageTerm, Mode, ModeIterable, ModeValue, MultipleErrorOptions, MultipleOptions, NoMatchContext, NonEmptyString, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionName, OptionOptions, OptionState, OptionSubConfig, OrErrorOptions, OrOptions, type ParseOptions, Parser, ParserContext, ParserResult, ParserValuePlaceholder, PassThroughFormat, PassThroughOptions, PendingDependencySourceState, PortOptionsBigInt, PortOptionsNumber, PortRangeOptionsBigInt, PortRangeOptionsNumber, PortRangeValueBigInt, PortRangeValueNumber, ResolvedDependency, Result, RunOptions, RunParserError, RunWithOptions, ShellCompletion, ShowChoicesOptions, ShowDefaultOptions, SocketAddressOptions, SocketAddressValue, SourceContext, StringOptions, SubstituteParserValue, Suggestion, TupleOptions, UrlOptions, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, type ValueSetOptions, WithDefaultError, WithDefaultOptions, annotationKey, argument, bash, checkBooleanOption, checkEnumOption, choice, cidr, cloneDocEntry, cloneUsage, cloneUsageTerm, command, commandLine, concat, conditional, constant, createDeferredParseState, createDependencySourceState, createPendingDependencySourceState, deduplicateDocEntries, deduplicateDocFragments, defaultValues, deferredParseMarker, dependency, dependencyId, dependencyIds, dependencySourceMarker, dependencySourceStateMarker, deriveFrom, deriveFromAsync, deriveFromSync, derivedValueParserMarker, domain, email, ensureNonEmptyString, envVar, extractArgumentMetavars, extractCommandNames, extractOptionNames, fail, fish, flag, float, formatDependencyError, formatDocPage, formatMessage, formatUsage, formatUsageTerm, getAnnotations, getDefaultValuesFunction, getDependencyIds, getDocPage, getDocPageAsync, getDocPageSync, group, hostname, integer, ip, ipv4, ipv6, isDeferredParseState, isDependencySource, isDependencySourceState, isDerivedValueParser, isDocEntryHidden, isDocHidden, isNonEmptyString, isPendingDependencySourceState, isSuggestionHidden, isUsageHidden, isValueParser, isWrappedDependencySource, lineBreak, link, locale, longestMatch, macAddress, map, merge, mergeHidden, message, metavar, multiple, nonEmpty, normalizeUsage, nu, object, option, optionName, optionNames, optional, or, parse, parseAsync, parseSync, parseWithDependency, passThrough, pendingDependencySourceStateMarker, port, portRange, pwsh, runParser, runParserAsync, runParserSync, runWith, runWithAsync, runWithSync, socketAddress, string, suggest, suggestAsync, suggestSync, suggestWithDependency, text, transformsDependencyValue, transformsDependencyValueMarker, tuple, url, uuid, value, valueSet, values, withDefault, wrappedDependencySourceMarker, zsh };
15
+ export { type Annotations, AnyDependencySource, ArgumentErrorOptions, ArgumentOptions, ChoiceOptions, ChoiceOptionsBase, ChoiceOptionsNumber, ChoiceOptionsString, CidrOptions, CidrValue, CombineMode, CombineModes, CombinedDependencyMode, CommandErrorOptions, CommandOptions, CommandSubConfig, ConditionalErrorOptions, ConditionalOptions, ContextOptionsParam, DeferredMap, DeferredParseState, DependencyError, DependencyMode, DependencyRegistry, DependencySource, DependencySourceState, DependencyValue, DependencyValues, DeriveAsyncOptions, DeriveFromAsyncOptions, DeriveFromOptions, DeriveFromSyncOptions, DeriveOptions, DeriveSyncOptions, DerivedValueParser, DocEntry, DocFragment, DocFragments, DocPage, DocPageFormatOptions, DocSection, DocState, DomainOptions, DuplicateOptionError, EmailOptions, ExtractRequiredOptions, FlagErrorOptions, FlagOptions, FloatOptions, GroupOptions, HiddenVisibility, HostnameOptions, InferMode, InferValue, IntegerOptionsBigInt, IntegerOptionsNumber, IpOptions, Ipv4Options, Ipv6Options, LocaleOptions, LongestMatchErrorOptions, LongestMatchOptions, MacAddressOptions, MergeOptions, type Message, type MessageFormatOptions, type MessageTerm, Mode, ModeIterable, ModeValue, MultipleErrorOptions, MultipleOptions, NoMatchContext, NonEmptyString, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionName, OptionOptions, OptionState, OptionSubConfig, OrErrorOptions, OrOptions, type ParseOptions, Parser, ParserContext, ParserResult, ParserValuePlaceholder, PassThroughFormat, PassThroughOptions, PendingDependencySourceState, PortOptionsBigInt, PortOptionsNumber, PortRangeOptionsBigInt, PortRangeOptionsNumber, PortRangeValueBigInt, PortRangeValueNumber, ResolvedDependency, Result, RunOptions, RunParserError, RunWithOptions, ShellCompletion, ShowChoicesOptions, ShowDefaultOptions, SocketAddressOptions, SocketAddressValue, SourceContext, StringOptions, SubstituteParserValue, Suggestion, TupleOptions, UrlOptions, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, type ValueSetOptions, WithDefaultError, WithDefaultOptions, annotationKey, argument, bash, checkBooleanOption, checkEnumOption, choice, cidr, cloneDocEntry, cloneUsage, cloneUsageTerm, command, commandLine, concat, conditional, constant, createDeferredParseState, createDependencySourceState, createPendingDependencySourceState, deduplicateDocEntries, deduplicateDocFragments, defaultValues, deferredParseMarker, dependency, dependencyId, dependencyIds, dependencySourceMarker, dependencySourceStateMarker, deriveFrom, deriveFromAsync, deriveFromSync, derivedValueParserMarker, domain, email, ensureNonEmptyString, envVar, extractArgumentMetavars, extractCommandNames, extractLeadingCommandNames, extractLeadingOptionNames, extractLiteralValues, extractOptionNames, fail, fish, flag, float, formatDependencyError, formatDocPage, formatMessage, formatUsage, formatUsageTerm, getAnnotations, getDefaultValuesFunction, getDependencyIds, getDocPage, getDocPageAsync, getDocPageSync, group, hostname, integer, ip, ipv4, ipv6, isDeferredParseState, isDependencySource, isDependencySourceState, isDerivedValueParser, isDocEntryHidden, isDocHidden, isNonEmptyString, isPendingDependencySourceState, isSuggestionHidden, isUsageHidden, isValueParser, isWrappedDependencySource, lineBreak, link, locale, longestMatch, macAddress, map, merge, mergeHidden, message, metavar, multiple, nonEmpty, normalizeUsage, nu, object, option, optionName, optionNames, optional, or, parse, parseAsync, parseSync, parseWithDependency, passThrough, pendingDependencySourceStateMarker, port, portRange, pwsh, runParser, runParserAsync, runParserSync, runWith, runWithAsync, runWithSync, socketAddress, string, suggest, suggestAsync, suggestSync, suggestWithDependency, text, transformsDependencyValue, transformsDependencyValueMarker, tuple, url, uuid, value, valueSet, values, withDefault, wrappedDependencySourceMarker, zsh };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Annotations, ParseOptions, annotationKey, getAnnotations } from "./annotations.js";
2
2
  import { NonEmptyString, ensureNonEmptyString, isNonEmptyString } from "./nonempty.js";
3
3
  import { Message, MessageFormatOptions, MessageTerm, ValueSetOptions, commandLine, envVar, formatMessage, lineBreak, link, message, metavar, optionName, optionNames, text, value, valueSet, values } from "./message.js";
4
- import { HiddenVisibility, OptionName, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, cloneUsage, cloneUsageTerm, extractArgumentMetavars, extractCommandNames, extractOptionNames, formatUsage, formatUsageTerm, isDocHidden, isSuggestionHidden, isUsageHidden, mergeHidden, normalizeUsage } from "./usage.js";
4
+ import { HiddenVisibility, OptionName, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, cloneUsage, cloneUsageTerm, extractArgumentMetavars, extractCommandNames, extractLeadingCommandNames, extractLeadingOptionNames, extractLiteralValues, extractOptionNames, formatUsage, formatUsageTerm, isDocHidden, isSuggestionHidden, isUsageHidden, mergeHidden, normalizeUsage } from "./usage.js";
5
5
  import { DocEntry, DocFragment, DocFragments, DocPage, DocPageFormatOptions, DocSection, ShowChoicesOptions, ShowDefaultOptions, cloneDocEntry, deduplicateDocEntries, deduplicateDocFragments, formatDocPage, isDocEntryHidden } from "./doc.js";
6
6
  import { ChoiceOptions, ChoiceOptionsBase, ChoiceOptionsNumber, ChoiceOptionsString, CidrOptions, CidrValue, DeferredMap, DomainOptions, EmailOptions, FloatOptions, HostnameOptions, IntegerOptionsBigInt, IntegerOptionsNumber, IpOptions, Ipv4Options, Ipv6Options, LocaleOptions, MacAddressOptions, PortOptionsBigInt, PortOptionsNumber, PortRangeOptionsBigInt, PortRangeOptionsNumber, PortRangeValueBigInt, PortRangeValueNumber, SocketAddressOptions, SocketAddressValue, StringOptions, UrlOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, checkBooleanOption, checkEnumOption, choice, cidr, domain, email, float, hostname, integer, ip, ipv4, ipv6, isValueParser, locale, macAddress, port, portRange, socketAddress, string, url, uuid } from "./valueparser.js";
7
7
  import { ConditionalErrorOptions, ConditionalOptions, DuplicateOptionError, GroupOptions, LongestMatchErrorOptions, LongestMatchOptions, MergeOptions, NoMatchContext, ObjectErrorOptions, ObjectOptions, OrErrorOptions, OrOptions, TupleOptions, concat, conditional, group, longestMatch, merge, object, or, tuple } from "./constructs.js";
@@ -12,4 +12,4 @@ import { CombineModes, DocState, InferMode, InferValue, Mode, ModeIterable, Mode
12
12
  import { ShellCompletion, bash, fish, nu, pwsh, zsh } from "./completion.js";
13
13
  import { ParserValuePlaceholder, SourceContext } from "./context.js";
14
14
  import { CommandSubConfig, ContextOptionsParam, ExtractRequiredOptions, OptionSubConfig, RunOptions, RunParserError, RunWithOptions, SubstituteParserValue, runParser, runParserAsync, runParserSync, runWith, runWithAsync, runWithSync } from "./facade.js";
15
- export { type Annotations, AnyDependencySource, ArgumentErrorOptions, ArgumentOptions, ChoiceOptions, ChoiceOptionsBase, ChoiceOptionsNumber, ChoiceOptionsString, CidrOptions, CidrValue, CombineMode, CombineModes, CombinedDependencyMode, CommandErrorOptions, CommandOptions, CommandSubConfig, ConditionalErrorOptions, ConditionalOptions, ContextOptionsParam, DeferredMap, DeferredParseState, DependencyError, DependencyMode, DependencyRegistry, DependencySource, DependencySourceState, DependencyValue, DependencyValues, DeriveAsyncOptions, DeriveFromAsyncOptions, DeriveFromOptions, DeriveFromSyncOptions, DeriveOptions, DeriveSyncOptions, DerivedValueParser, DocEntry, DocFragment, DocFragments, DocPage, DocPageFormatOptions, DocSection, DocState, DomainOptions, DuplicateOptionError, EmailOptions, ExtractRequiredOptions, FlagErrorOptions, FlagOptions, FloatOptions, GroupOptions, HiddenVisibility, HostnameOptions, InferMode, InferValue, IntegerOptionsBigInt, IntegerOptionsNumber, IpOptions, Ipv4Options, Ipv6Options, LocaleOptions, LongestMatchErrorOptions, LongestMatchOptions, MacAddressOptions, MergeOptions, type Message, type MessageFormatOptions, type MessageTerm, Mode, ModeIterable, ModeValue, MultipleErrorOptions, MultipleOptions, NoMatchContext, NonEmptyString, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionName, OptionOptions, OptionState, OptionSubConfig, OrErrorOptions, OrOptions, type ParseOptions, Parser, ParserContext, ParserResult, ParserValuePlaceholder, PassThroughFormat, PassThroughOptions, PendingDependencySourceState, PortOptionsBigInt, PortOptionsNumber, PortRangeOptionsBigInt, PortRangeOptionsNumber, PortRangeValueBigInt, PortRangeValueNumber, ResolvedDependency, Result, RunOptions, RunParserError, RunWithOptions, ShellCompletion, ShowChoicesOptions, ShowDefaultOptions, SocketAddressOptions, SocketAddressValue, SourceContext, StringOptions, SubstituteParserValue, Suggestion, TupleOptions, UrlOptions, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, type ValueSetOptions, WithDefaultError, WithDefaultOptions, annotationKey, argument, bash, checkBooleanOption, checkEnumOption, choice, cidr, cloneDocEntry, cloneUsage, cloneUsageTerm, command, commandLine, concat, conditional, constant, createDeferredParseState, createDependencySourceState, createPendingDependencySourceState, deduplicateDocEntries, deduplicateDocFragments, defaultValues, deferredParseMarker, dependency, dependencyId, dependencyIds, dependencySourceMarker, dependencySourceStateMarker, deriveFrom, deriveFromAsync, deriveFromSync, derivedValueParserMarker, domain, email, ensureNonEmptyString, envVar, extractArgumentMetavars, extractCommandNames, extractOptionNames, fail, fish, flag, float, formatDependencyError, formatDocPage, formatMessage, formatUsage, formatUsageTerm, getAnnotations, getDefaultValuesFunction, getDependencyIds, getDocPage, getDocPageAsync, getDocPageSync, group, hostname, integer, ip, ipv4, ipv6, isDeferredParseState, isDependencySource, isDependencySourceState, isDerivedValueParser, isDocEntryHidden, isDocHidden, isNonEmptyString, isPendingDependencySourceState, isSuggestionHidden, isUsageHidden, isValueParser, isWrappedDependencySource, lineBreak, link, locale, longestMatch, macAddress, map, merge, mergeHidden, message, metavar, multiple, nonEmpty, normalizeUsage, nu, object, option, optionName, optionNames, optional, or, parse, parseAsync, parseSync, parseWithDependency, passThrough, pendingDependencySourceStateMarker, port, portRange, pwsh, runParser, runParserAsync, runParserSync, runWith, runWithAsync, runWithSync, socketAddress, string, suggest, suggestAsync, suggestSync, suggestWithDependency, text, transformsDependencyValue, transformsDependencyValueMarker, tuple, url, uuid, value, valueSet, values, withDefault, wrappedDependencySourceMarker, zsh };
15
+ export { type Annotations, AnyDependencySource, ArgumentErrorOptions, ArgumentOptions, ChoiceOptions, ChoiceOptionsBase, ChoiceOptionsNumber, ChoiceOptionsString, CidrOptions, CidrValue, CombineMode, CombineModes, CombinedDependencyMode, CommandErrorOptions, CommandOptions, CommandSubConfig, ConditionalErrorOptions, ConditionalOptions, ContextOptionsParam, DeferredMap, DeferredParseState, DependencyError, DependencyMode, DependencyRegistry, DependencySource, DependencySourceState, DependencyValue, DependencyValues, DeriveAsyncOptions, DeriveFromAsyncOptions, DeriveFromOptions, DeriveFromSyncOptions, DeriveOptions, DeriveSyncOptions, DerivedValueParser, DocEntry, DocFragment, DocFragments, DocPage, DocPageFormatOptions, DocSection, DocState, DomainOptions, DuplicateOptionError, EmailOptions, ExtractRequiredOptions, FlagErrorOptions, FlagOptions, FloatOptions, GroupOptions, HiddenVisibility, HostnameOptions, InferMode, InferValue, IntegerOptionsBigInt, IntegerOptionsNumber, IpOptions, Ipv4Options, Ipv6Options, LocaleOptions, LongestMatchErrorOptions, LongestMatchOptions, MacAddressOptions, MergeOptions, type Message, type MessageFormatOptions, type MessageTerm, Mode, ModeIterable, ModeValue, MultipleErrorOptions, MultipleOptions, NoMatchContext, NonEmptyString, ObjectErrorOptions, ObjectOptions, OptionErrorOptions, OptionName, OptionOptions, OptionState, OptionSubConfig, OrErrorOptions, OrOptions, type ParseOptions, Parser, ParserContext, ParserResult, ParserValuePlaceholder, PassThroughFormat, PassThroughOptions, PendingDependencySourceState, PortOptionsBigInt, PortOptionsNumber, PortRangeOptionsBigInt, PortRangeOptionsNumber, PortRangeValueBigInt, PortRangeValueNumber, ResolvedDependency, Result, RunOptions, RunParserError, RunWithOptions, ShellCompletion, ShowChoicesOptions, ShowDefaultOptions, SocketAddressOptions, SocketAddressValue, SourceContext, StringOptions, SubstituteParserValue, Suggestion, TupleOptions, UrlOptions, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, Uuid, UuidOptions, ValueParser, ValueParserResult, type ValueSetOptions, WithDefaultError, WithDefaultOptions, annotationKey, argument, bash, checkBooleanOption, checkEnumOption, choice, cidr, cloneDocEntry, cloneUsage, cloneUsageTerm, command, commandLine, concat, conditional, constant, createDeferredParseState, createDependencySourceState, createPendingDependencySourceState, deduplicateDocEntries, deduplicateDocFragments, defaultValues, deferredParseMarker, dependency, dependencyId, dependencyIds, dependencySourceMarker, dependencySourceStateMarker, deriveFrom, deriveFromAsync, deriveFromSync, derivedValueParserMarker, domain, email, ensureNonEmptyString, envVar, extractArgumentMetavars, extractCommandNames, extractLeadingCommandNames, extractLeadingOptionNames, extractLiteralValues, extractOptionNames, fail, fish, flag, float, formatDependencyError, formatDocPage, formatMessage, formatUsage, formatUsageTerm, getAnnotations, getDefaultValuesFunction, getDependencyIds, getDocPage, getDocPageAsync, getDocPageSync, group, hostname, integer, ip, ipv4, ipv6, isDeferredParseState, isDependencySource, isDependencySourceState, isDerivedValueParser, isDocEntryHidden, isDocHidden, isNonEmptyString, isPendingDependencySourceState, isSuggestionHidden, isUsageHidden, isValueParser, isWrappedDependencySource, lineBreak, link, locale, longestMatch, macAddress, map, merge, mergeHidden, message, metavar, multiple, nonEmpty, normalizeUsage, nu, object, option, optionName, optionNames, optional, or, parse, parseAsync, parseSync, parseWithDependency, passThrough, pendingDependencySourceStateMarker, port, portRange, pwsh, runParser, runParserAsync, runParserSync, runWith, runWithAsync, runWithSync, socketAddress, string, suggest, suggestAsync, suggestSync, suggestWithDependency, text, transformsDependencyValue, transformsDependencyValueMarker, tuple, url, uuid, value, valueSet, values, withDefault, wrappedDependencySourceMarker, zsh };
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import { annotationKey, getAnnotations } from "./annotations.js";
2
2
  import { commandLine, envVar, formatMessage, lineBreak, link, message, metavar, optionName, optionNames, text, value, valueSet, values } from "./message.js";
3
3
  import { bash, fish, nu, pwsh, zsh } from "./completion.js";
4
4
  import { DependencyRegistry, createDeferredParseState, createDependencySourceState, createPendingDependencySourceState, defaultValues, deferredParseMarker, dependency, dependencyId, dependencyIds, dependencySourceMarker, dependencySourceStateMarker, deriveFrom, deriveFromAsync, deriveFromSync, derivedValueParserMarker, formatDependencyError, getDefaultValuesFunction, getDependencyIds, isDeferredParseState, isDependencySource, isDependencySourceState, isDerivedValueParser, isPendingDependencySourceState, isWrappedDependencySource, parseWithDependency, pendingDependencySourceStateMarker, suggestWithDependency, transformsDependencyValue, transformsDependencyValueMarker, wrappedDependencySourceMarker } from "./dependency.js";
5
- import { cloneUsage, cloneUsageTerm, extractArgumentMetavars, extractCommandNames, extractOptionNames, formatUsage, formatUsageTerm, isDocHidden, isSuggestionHidden, isUsageHidden, mergeHidden, normalizeUsage } from "./usage.js";
5
+ import { cloneUsage, cloneUsageTerm, extractArgumentMetavars, extractCommandNames, extractLeadingCommandNames, extractLeadingOptionNames, extractLiteralValues, extractOptionNames, formatUsage, formatUsageTerm, isDocHidden, isSuggestionHidden, isUsageHidden, mergeHidden, normalizeUsage } from "./usage.js";
6
6
  import { cloneDocEntry, deduplicateDocEntries, deduplicateDocFragments, formatDocPage, isDocEntryHidden } from "./doc.js";
7
7
  import { DuplicateOptionError, concat, conditional, group, longestMatch, merge, object, or, tuple } from "./constructs.js";
8
8
  import { WithDefaultError, map, multiple, nonEmpty, optional, withDefault } from "./modifiers.js";
@@ -12,4 +12,4 @@ import { argument, command, constant, fail, flag, option, passThrough } from "./
12
12
  import { getDocPage, getDocPageAsync, getDocPageSync, parse, parseAsync, parseSync, suggest, suggestAsync, suggestSync } from "./parser.js";
13
13
  import { RunParserError, runParser, runParserAsync, runParserSync, runWith, runWithAsync, runWithSync } from "./facade.js";
14
14
 
15
- export { DependencyRegistry, DuplicateOptionError, RunParserError, WithDefaultError, annotationKey, argument, bash, checkBooleanOption, checkEnumOption, choice, cidr, cloneDocEntry, cloneUsage, cloneUsageTerm, command, commandLine, concat, conditional, constant, createDeferredParseState, createDependencySourceState, createPendingDependencySourceState, deduplicateDocEntries, deduplicateDocFragments, defaultValues, deferredParseMarker, dependency, dependencyId, dependencyIds, dependencySourceMarker, dependencySourceStateMarker, deriveFrom, deriveFromAsync, deriveFromSync, derivedValueParserMarker, domain, email, ensureNonEmptyString, envVar, extractArgumentMetavars, extractCommandNames, extractOptionNames, fail, fish, flag, float, formatDependencyError, formatDocPage, formatMessage, formatUsage, formatUsageTerm, getAnnotations, getDefaultValuesFunction, getDependencyIds, getDocPage, getDocPageAsync, getDocPageSync, group, hostname, integer, ip, ipv4, ipv6, isDeferredParseState, isDependencySource, isDependencySourceState, isDerivedValueParser, isDocEntryHidden, isDocHidden, isNonEmptyString, isPendingDependencySourceState, isSuggestionHidden, isUsageHidden, isValueParser, isWrappedDependencySource, lineBreak, link, locale, longestMatch, macAddress, map, merge, mergeHidden, message, metavar, multiple, nonEmpty, normalizeUsage, nu, object, option, optionName, optionNames, optional, or, parse, parseAsync, parseSync, parseWithDependency, passThrough, pendingDependencySourceStateMarker, port, portRange, pwsh, runParser, runParserAsync, runParserSync, runWith, runWithAsync, runWithSync, socketAddress, string, suggest, suggestAsync, suggestSync, suggestWithDependency, text, transformsDependencyValue, transformsDependencyValueMarker, tuple, url, uuid, value, valueSet, values, withDefault, wrappedDependencySourceMarker, zsh };
15
+ export { DependencyRegistry, DuplicateOptionError, RunParserError, WithDefaultError, annotationKey, argument, bash, checkBooleanOption, checkEnumOption, choice, cidr, cloneDocEntry, cloneUsage, cloneUsageTerm, command, commandLine, concat, conditional, constant, createDeferredParseState, createDependencySourceState, createPendingDependencySourceState, deduplicateDocEntries, deduplicateDocFragments, defaultValues, deferredParseMarker, dependency, dependencyId, dependencyIds, dependencySourceMarker, dependencySourceStateMarker, deriveFrom, deriveFromAsync, deriveFromSync, derivedValueParserMarker, domain, email, ensureNonEmptyString, envVar, extractArgumentMetavars, extractCommandNames, extractLeadingCommandNames, extractLeadingOptionNames, extractLiteralValues, extractOptionNames, fail, fish, flag, float, formatDependencyError, formatDocPage, formatMessage, formatUsage, formatUsageTerm, getAnnotations, getDefaultValuesFunction, getDependencyIds, getDocPage, getDocPageAsync, getDocPageSync, group, hostname, integer, ip, ipv4, ipv6, isDeferredParseState, isDependencySource, isDependencySourceState, isDerivedValueParser, isDocEntryHidden, isDocHidden, isNonEmptyString, isPendingDependencySourceState, isSuggestionHidden, isUsageHidden, isValueParser, isWrappedDependencySource, lineBreak, link, locale, longestMatch, macAddress, map, merge, mergeHidden, message, metavar, multiple, nonEmpty, normalizeUsage, nu, object, option, optionName, optionNames, optional, or, parse, parseAsync, parseSync, parseWithDependency, passThrough, pendingDependencySourceStateMarker, port, portRange, pwsh, runParser, runParserAsync, runParserSync, runWith, runWithAsync, runWithSync, socketAddress, string, suggest, suggestAsync, suggestSync, suggestWithDependency, text, transformsDependencyValue, transformsDependencyValueMarker, tuple, url, uuid, value, valueSet, values, withDefault, wrappedDependencySourceMarker, zsh };
package/dist/usage.cjs CHANGED
@@ -41,6 +41,8 @@ function mergeHidden(a, b) {
41
41
  * multiple, and exclusive terms.
42
42
  *
43
43
  * @param usage The usage description to extract option names from.
44
+ * @param includeHidden Whether to include fully hidden options (`hidden: true`)
45
+ * in the result. Defaults to `false`.
44
46
  * @returns A set containing all option names found in the usage description.
45
47
  *
46
48
  * @example
@@ -53,12 +55,12 @@ function mergeHidden(a, b) {
53
55
  * // names = Set(["--verbose", "-v", "--quiet", "-q"])
54
56
  * ```
55
57
  */
56
- function extractOptionNames(usage) {
58
+ function extractOptionNames(usage, includeHidden) {
57
59
  const names = /* @__PURE__ */ new Set();
58
60
  function traverseUsage(terms) {
59
61
  if (!terms || !Array.isArray(terms)) return;
60
62
  for (const term of terms) if (term.type === "option") {
61
- if (isSuggestionHidden(term.hidden)) continue;
63
+ if (!includeHidden && isSuggestionHidden(term.hidden)) continue;
62
64
  for (const name of term.names) names.add(name);
63
65
  } else if (term.type === "optional" || term.type === "multiple") traverseUsage(term.terms);
64
66
  else if (term.type === "exclusive") for (const exclusiveUsage of term.terms) traverseUsage(exclusiveUsage);
@@ -72,8 +74,10 @@ function extractOptionNames(usage) {
72
74
  * This function recursively traverses the usage structure and collects
73
75
  * all command names, similar to {@link extractOptionNames}.
74
76
  *
75
- * @param usage The usage structure to extract command names from
76
- * @returns A Set of all command names found in the usage structure
77
+ * @param usage The usage structure to extract command names from.
78
+ * @param includeHidden Whether to include fully hidden commands
79
+ * (`hidden: true`) in the result. Defaults to `false`.
80
+ * @returns A set of all command names found in the usage structure.
77
81
  *
78
82
  * @example
79
83
  * ```typescript
@@ -86,12 +90,12 @@ function extractOptionNames(usage) {
86
90
  * ```
87
91
  * @since 0.7.0
88
92
  */
89
- function extractCommandNames(usage) {
93
+ function extractCommandNames(usage, includeHidden) {
90
94
  const names = /* @__PURE__ */ new Set();
91
95
  function traverseUsage(terms) {
92
96
  if (!terms || !Array.isArray(terms)) return;
93
97
  for (const term of terms) if (term.type === "command") {
94
- if (isSuggestionHidden(term.hidden)) continue;
98
+ if (!includeHidden && isSuggestionHidden(term.hidden)) continue;
95
99
  names.add(term.name);
96
100
  } else if (term.type === "optional" || term.type === "multiple") traverseUsage(term.terms);
97
101
  else if (term.type === "exclusive") for (const exclusiveUsage of term.terms) traverseUsage(exclusiveUsage);
@@ -100,6 +104,164 @@ function extractCommandNames(usage) {
100
104
  return names;
101
105
  }
102
106
  /**
107
+ * Extracts option names that are reachable at the leading token position
108
+ * (before any command or argument gate).
109
+ *
110
+ * Unlike {@link extractOptionNames}, which traverses the entire usage tree,
111
+ * this function stops scanning a terms array after encountering a `command`,
112
+ * `argument`, or `literal` term, because subsequent terms are scoped under
113
+ * that positional token. It still recurses into `optional`, `multiple`,
114
+ * and `exclusive` containers, since they represent alternatives or wrappers
115
+ * at the same position.
116
+ *
117
+ * Known limitation: this function infers token positions from the `usage`
118
+ * tree, which is a display-oriented structure. Combinators like `tuple()`
119
+ * and `object()` sort or flatten usage by priority rather than token order,
120
+ * so the results can be inaccurate—both false positives (e.g., options
121
+ * appearing before commands due to priority sorting) and false negatives
122
+ * (e.g., options after commands that are actually parallel peers).
123
+ * The proper fix is to use `Parser.leadingNames` instead of usage-tree
124
+ * analysis. This also cannot detect `conditional(argument(...))`
125
+ * discriminator values, which never appear in the usage tree.
126
+ * See https://github.com/dahlia/optique/issues/734 and
127
+ * https://github.com/dahlia/optique/issues/735
128
+ *
129
+ * @param usage The usage description to extract leading option names from.
130
+ * @param includeHidden Whether to include fully hidden options
131
+ * (`hidden: true`) in the result. Defaults to `false`.
132
+ * @returns A set of option names reachable at the leading token position.
133
+ * @since 1.0.0
134
+ */
135
+ function extractLeadingOptionNames(usage, includeHidden) {
136
+ const names = /* @__PURE__ */ new Set();
137
+ function collectLeading(terms) {
138
+ if (!terms || !Array.isArray(terms)) return;
139
+ for (const term of terms) switch (term.type) {
140
+ case "option":
141
+ if (!includeHidden && isSuggestionHidden(term.hidden)) break;
142
+ for (const name of term.names) names.add(name);
143
+ break;
144
+ case "command": return;
145
+ case "argument":
146
+ case "literal": return;
147
+ case "optional":
148
+ collectLeading(term.terms);
149
+ break;
150
+ case "multiple":
151
+ collectLeading(term.terms);
152
+ if (term.min > 0 && branchConsumesToken(term.terms)) return;
153
+ break;
154
+ case "exclusive":
155
+ for (const branch of term.terms) collectLeading(branch);
156
+ if (exclusiveConsumesToken(term.terms)) return;
157
+ break;
158
+ default: break;
159
+ }
160
+ }
161
+ collectLeading(usage);
162
+ return names;
163
+ }
164
+ /**
165
+ * Extracts command names that could match as the first positional token.
166
+ *
167
+ * Unlike {@link extractCommandNames}, which traverses the entire usage tree,
168
+ * this function stops scanning a terms array after encountering a `command`,
169
+ * `argument`, or `literal` term, because subsequent terms in that array are
170
+ * scoped under that positional token (reachable only after the leading term
171
+ * is consumed). It still recurses into `optional`, `multiple`, and
172
+ * `exclusive` containers, since they represent alternatives or optional
173
+ * wrappers at the same token position.
174
+ *
175
+ * Known limitation: this function has the same usage-tree ordering and
176
+ * `conditional(argument(...))` caveats as {@link extractLeadingOptionNames}.
177
+ * See https://github.com/dahlia/optique/issues/734 and
178
+ * https://github.com/dahlia/optique/issues/735
179
+ *
180
+ * @param usage The usage description to extract leading command names from.
181
+ * @param includeHidden Whether to include fully hidden commands
182
+ * (`hidden: true`) in the result. Defaults to `false`.
183
+ * @returns A set of command names that could match at the first token position.
184
+ * @since 1.0.0
185
+ */
186
+ function extractLeadingCommandNames(usage, includeHidden) {
187
+ const names = /* @__PURE__ */ new Set();
188
+ function collectLeading(terms) {
189
+ if (!terms || !Array.isArray(terms)) return;
190
+ for (const term of terms) switch (term.type) {
191
+ case "command":
192
+ if (!includeHidden && isSuggestionHidden(term.hidden)) return;
193
+ names.add(term.name);
194
+ return;
195
+ case "argument":
196
+ case "literal": return;
197
+ case "optional":
198
+ collectLeading(term.terms);
199
+ break;
200
+ case "multiple":
201
+ collectLeading(term.terms);
202
+ if (term.min > 0 && branchConsumesToken(term.terms)) return;
203
+ break;
204
+ case "exclusive":
205
+ for (const branch of term.terms) collectLeading(branch);
206
+ if (exclusiveConsumesToken(term.terms)) return;
207
+ break;
208
+ default: break;
209
+ }
210
+ }
211
+ collectLeading(usage);
212
+ return names;
213
+ }
214
+ /**
215
+ * Checks whether every branch of an exclusive term must consume a positional
216
+ * token. When true, terms after the exclusive are at position N+1 and should
217
+ * not be considered "leading".
218
+ */
219
+ function exclusiveConsumesToken(branches) {
220
+ if (branches.length === 0) return false;
221
+ return branches.every((branch) => branchConsumesToken(branch));
222
+ }
223
+ function branchConsumesToken(terms) {
224
+ if (!terms || !Array.isArray(terms)) return false;
225
+ for (const term of terms) switch (term.type) {
226
+ case "command":
227
+ case "argument":
228
+ case "literal": return true;
229
+ case "option": break;
230
+ case "optional": break;
231
+ case "multiple":
232
+ if (term.min > 0 && branchConsumesToken(term.terms)) return true;
233
+ break;
234
+ case "exclusive":
235
+ if (exclusiveConsumesToken(term.terms)) return true;
236
+ break;
237
+ default: break;
238
+ }
239
+ return false;
240
+ }
241
+ /**
242
+ * Extracts all literal values from a usage description.
243
+ *
244
+ * This function recursively traverses the usage tree and collects all
245
+ * `literal` term values. Literal values represent fixed strings that
246
+ * the user must type (e.g., conditional discriminator values like
247
+ * `"server"` in `conditional(option("--mode", string()), { server: ... })`).
248
+ *
249
+ * @param usage The usage description to extract literal values from.
250
+ * @returns A set of all literal values found in the usage description.
251
+ * @since 1.0.0
252
+ */
253
+ function extractLiteralValues(usage) {
254
+ const values = /* @__PURE__ */ new Set();
255
+ function traverseUsage(terms) {
256
+ if (!terms || !Array.isArray(terms)) return;
257
+ for (const term of terms) if (term.type === "literal") values.add(term.value);
258
+ else if (term.type === "optional" || term.type === "multiple") traverseUsage(term.terms);
259
+ else if (term.type === "exclusive") for (const branch of term.terms) traverseUsage(branch);
260
+ }
261
+ traverseUsage(usage);
262
+ return values;
263
+ }
264
+ /**
103
265
  * Extracts all argument metavars from a Usage array.
104
266
  *
105
267
  * This function recursively traverses the usage structure and collects
@@ -551,6 +713,9 @@ exports.cloneUsage = cloneUsage;
551
713
  exports.cloneUsageTerm = cloneUsageTerm;
552
714
  exports.extractArgumentMetavars = extractArgumentMetavars;
553
715
  exports.extractCommandNames = extractCommandNames;
716
+ exports.extractLeadingCommandNames = extractLeadingCommandNames;
717
+ exports.extractLeadingOptionNames = extractLeadingOptionNames;
718
+ exports.extractLiteralValues = extractLiteralValues;
554
719
  exports.extractOptionNames = extractOptionNames;
555
720
  exports.formatUsage = formatUsage;
556
721
  exports.formatUsageTerm = formatUsageTerm;
package/dist/usage.d.cts CHANGED
@@ -220,6 +220,8 @@ type Usage = readonly UsageTerm[];
220
220
  * multiple, and exclusive terms.
221
221
  *
222
222
  * @param usage The usage description to extract option names from.
223
+ * @param includeHidden Whether to include fully hidden options (`hidden: true`)
224
+ * in the result. Defaults to `false`.
223
225
  * @returns A set containing all option names found in the usage description.
224
226
  *
225
227
  * @example
@@ -232,15 +234,17 @@ type Usage = readonly UsageTerm[];
232
234
  * // names = Set(["--verbose", "-v", "--quiet", "-q"])
233
235
  * ```
234
236
  */
235
- declare function extractOptionNames(usage: Usage): Set<string>;
237
+ declare function extractOptionNames(usage: Usage, includeHidden?: boolean): Set<string>;
236
238
  /**
237
239
  * Extracts all command names from a Usage array.
238
240
  *
239
241
  * This function recursively traverses the usage structure and collects
240
242
  * all command names, similar to {@link extractOptionNames}.
241
243
  *
242
- * @param usage The usage structure to extract command names from
243
- * @returns A Set of all command names found in the usage structure
244
+ * @param usage The usage structure to extract command names from.
245
+ * @param includeHidden Whether to include fully hidden commands
246
+ * (`hidden: true`) in the result. Defaults to `false`.
247
+ * @returns A set of all command names found in the usage structure.
244
248
  *
245
249
  * @example
246
250
  * ```typescript
@@ -253,7 +257,73 @@ declare function extractOptionNames(usage: Usage): Set<string>;
253
257
  * ```
254
258
  * @since 0.7.0
255
259
  */
256
- declare function extractCommandNames(usage: Usage): Set<string>;
260
+ declare function extractCommandNames(usage: Usage, includeHidden?: boolean): Set<string>;
261
+ /**
262
+ * Extracts option names that are reachable at the leading token position
263
+ * (before any command or argument gate).
264
+ *
265
+ * Unlike {@link extractOptionNames}, which traverses the entire usage tree,
266
+ * this function stops scanning a terms array after encountering a `command`,
267
+ * `argument`, or `literal` term, because subsequent terms are scoped under
268
+ * that positional token. It still recurses into `optional`, `multiple`,
269
+ * and `exclusive` containers, since they represent alternatives or wrappers
270
+ * at the same position.
271
+ *
272
+ * Known limitation: this function infers token positions from the `usage`
273
+ * tree, which is a display-oriented structure. Combinators like `tuple()`
274
+ * and `object()` sort or flatten usage by priority rather than token order,
275
+ * so the results can be inaccurate—both false positives (e.g., options
276
+ * appearing before commands due to priority sorting) and false negatives
277
+ * (e.g., options after commands that are actually parallel peers).
278
+ * The proper fix is to use `Parser.leadingNames` instead of usage-tree
279
+ * analysis. This also cannot detect `conditional(argument(...))`
280
+ * discriminator values, which never appear in the usage tree.
281
+ * See https://github.com/dahlia/optique/issues/734 and
282
+ * https://github.com/dahlia/optique/issues/735
283
+ *
284
+ * @param usage The usage description to extract leading option names from.
285
+ * @param includeHidden Whether to include fully hidden options
286
+ * (`hidden: true`) in the result. Defaults to `false`.
287
+ * @returns A set of option names reachable at the leading token position.
288
+ * @since 1.0.0
289
+ */
290
+ declare function extractLeadingOptionNames(usage: Usage, includeHidden?: boolean): Set<string>;
291
+ /**
292
+ * Extracts command names that could match as the first positional token.
293
+ *
294
+ * Unlike {@link extractCommandNames}, which traverses the entire usage tree,
295
+ * this function stops scanning a terms array after encountering a `command`,
296
+ * `argument`, or `literal` term, because subsequent terms in that array are
297
+ * scoped under that positional token (reachable only after the leading term
298
+ * is consumed). It still recurses into `optional`, `multiple`, and
299
+ * `exclusive` containers, since they represent alternatives or optional
300
+ * wrappers at the same token position.
301
+ *
302
+ * Known limitation: this function has the same usage-tree ordering and
303
+ * `conditional(argument(...))` caveats as {@link extractLeadingOptionNames}.
304
+ * See https://github.com/dahlia/optique/issues/734 and
305
+ * https://github.com/dahlia/optique/issues/735
306
+ *
307
+ * @param usage The usage description to extract leading command names from.
308
+ * @param includeHidden Whether to include fully hidden commands
309
+ * (`hidden: true`) in the result. Defaults to `false`.
310
+ * @returns A set of command names that could match at the first token position.
311
+ * @since 1.0.0
312
+ */
313
+ declare function extractLeadingCommandNames(usage: Usage, includeHidden?: boolean): Set<string>;
314
+ /**
315
+ * Extracts all literal values from a usage description.
316
+ *
317
+ * This function recursively traverses the usage tree and collects all
318
+ * `literal` term values. Literal values represent fixed strings that
319
+ * the user must type (e.g., conditional discriminator values like
320
+ * `"server"` in `conditional(option("--mode", string()), { server: ... })`).
321
+ *
322
+ * @param usage The usage description to extract literal values from.
323
+ * @returns A set of all literal values found in the usage description.
324
+ * @since 1.0.0
325
+ */
326
+ declare function extractLiteralValues(usage: Usage): Set<string>;
257
327
  /**
258
328
  * Extracts all argument metavars from a Usage array.
259
329
  *
@@ -417,4 +487,4 @@ interface UsageTermFormatOptions extends UsageFormatOptions {
417
487
  */
418
488
  declare function formatUsageTerm(term: UsageTerm, options?: UsageTermFormatOptions): string;
419
489
  //#endregion
420
- export { HiddenVisibility, OptionName, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, cloneUsage, cloneUsageTerm, extractArgumentMetavars, extractCommandNames, extractOptionNames, formatUsage, formatUsageTerm, isDocHidden, isSuggestionHidden, isUsageHidden, mergeHidden, normalizeUsage };
490
+ export { HiddenVisibility, OptionName, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, cloneUsage, cloneUsageTerm, extractArgumentMetavars, extractCommandNames, extractLeadingCommandNames, extractLeadingOptionNames, extractLiteralValues, extractOptionNames, formatUsage, formatUsageTerm, isDocHidden, isSuggestionHidden, isUsageHidden, mergeHidden, normalizeUsage };
package/dist/usage.d.ts CHANGED
@@ -220,6 +220,8 @@ type Usage = readonly UsageTerm[];
220
220
  * multiple, and exclusive terms.
221
221
  *
222
222
  * @param usage The usage description to extract option names from.
223
+ * @param includeHidden Whether to include fully hidden options (`hidden: true`)
224
+ * in the result. Defaults to `false`.
223
225
  * @returns A set containing all option names found in the usage description.
224
226
  *
225
227
  * @example
@@ -232,15 +234,17 @@ type Usage = readonly UsageTerm[];
232
234
  * // names = Set(["--verbose", "-v", "--quiet", "-q"])
233
235
  * ```
234
236
  */
235
- declare function extractOptionNames(usage: Usage): Set<string>;
237
+ declare function extractOptionNames(usage: Usage, includeHidden?: boolean): Set<string>;
236
238
  /**
237
239
  * Extracts all command names from a Usage array.
238
240
  *
239
241
  * This function recursively traverses the usage structure and collects
240
242
  * all command names, similar to {@link extractOptionNames}.
241
243
  *
242
- * @param usage The usage structure to extract command names from
243
- * @returns A Set of all command names found in the usage structure
244
+ * @param usage The usage structure to extract command names from.
245
+ * @param includeHidden Whether to include fully hidden commands
246
+ * (`hidden: true`) in the result. Defaults to `false`.
247
+ * @returns A set of all command names found in the usage structure.
244
248
  *
245
249
  * @example
246
250
  * ```typescript
@@ -253,7 +257,73 @@ declare function extractOptionNames(usage: Usage): Set<string>;
253
257
  * ```
254
258
  * @since 0.7.0
255
259
  */
256
- declare function extractCommandNames(usage: Usage): Set<string>;
260
+ declare function extractCommandNames(usage: Usage, includeHidden?: boolean): Set<string>;
261
+ /**
262
+ * Extracts option names that are reachable at the leading token position
263
+ * (before any command or argument gate).
264
+ *
265
+ * Unlike {@link extractOptionNames}, which traverses the entire usage tree,
266
+ * this function stops scanning a terms array after encountering a `command`,
267
+ * `argument`, or `literal` term, because subsequent terms are scoped under
268
+ * that positional token. It still recurses into `optional`, `multiple`,
269
+ * and `exclusive` containers, since they represent alternatives or wrappers
270
+ * at the same position.
271
+ *
272
+ * Known limitation: this function infers token positions from the `usage`
273
+ * tree, which is a display-oriented structure. Combinators like `tuple()`
274
+ * and `object()` sort or flatten usage by priority rather than token order,
275
+ * so the results can be inaccurate—both false positives (e.g., options
276
+ * appearing before commands due to priority sorting) and false negatives
277
+ * (e.g., options after commands that are actually parallel peers).
278
+ * The proper fix is to use `Parser.leadingNames` instead of usage-tree
279
+ * analysis. This also cannot detect `conditional(argument(...))`
280
+ * discriminator values, which never appear in the usage tree.
281
+ * See https://github.com/dahlia/optique/issues/734 and
282
+ * https://github.com/dahlia/optique/issues/735
283
+ *
284
+ * @param usage The usage description to extract leading option names from.
285
+ * @param includeHidden Whether to include fully hidden options
286
+ * (`hidden: true`) in the result. Defaults to `false`.
287
+ * @returns A set of option names reachable at the leading token position.
288
+ * @since 1.0.0
289
+ */
290
+ declare function extractLeadingOptionNames(usage: Usage, includeHidden?: boolean): Set<string>;
291
+ /**
292
+ * Extracts command names that could match as the first positional token.
293
+ *
294
+ * Unlike {@link extractCommandNames}, which traverses the entire usage tree,
295
+ * this function stops scanning a terms array after encountering a `command`,
296
+ * `argument`, or `literal` term, because subsequent terms in that array are
297
+ * scoped under that positional token (reachable only after the leading term
298
+ * is consumed). It still recurses into `optional`, `multiple`, and
299
+ * `exclusive` containers, since they represent alternatives or optional
300
+ * wrappers at the same token position.
301
+ *
302
+ * Known limitation: this function has the same usage-tree ordering and
303
+ * `conditional(argument(...))` caveats as {@link extractLeadingOptionNames}.
304
+ * See https://github.com/dahlia/optique/issues/734 and
305
+ * https://github.com/dahlia/optique/issues/735
306
+ *
307
+ * @param usage The usage description to extract leading command names from.
308
+ * @param includeHidden Whether to include fully hidden commands
309
+ * (`hidden: true`) in the result. Defaults to `false`.
310
+ * @returns A set of command names that could match at the first token position.
311
+ * @since 1.0.0
312
+ */
313
+ declare function extractLeadingCommandNames(usage: Usage, includeHidden?: boolean): Set<string>;
314
+ /**
315
+ * Extracts all literal values from a usage description.
316
+ *
317
+ * This function recursively traverses the usage tree and collects all
318
+ * `literal` term values. Literal values represent fixed strings that
319
+ * the user must type (e.g., conditional discriminator values like
320
+ * `"server"` in `conditional(option("--mode", string()), { server: ... })`).
321
+ *
322
+ * @param usage The usage description to extract literal values from.
323
+ * @returns A set of all literal values found in the usage description.
324
+ * @since 1.0.0
325
+ */
326
+ declare function extractLiteralValues(usage: Usage): Set<string>;
257
327
  /**
258
328
  * Extracts all argument metavars from a Usage array.
259
329
  *
@@ -417,4 +487,4 @@ interface UsageTermFormatOptions extends UsageFormatOptions {
417
487
  */
418
488
  declare function formatUsageTerm(term: UsageTerm, options?: UsageTermFormatOptions): string;
419
489
  //#endregion
420
- export { HiddenVisibility, OptionName, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, cloneUsage, cloneUsageTerm, extractArgumentMetavars, extractCommandNames, extractOptionNames, formatUsage, formatUsageTerm, isDocHidden, isSuggestionHidden, isUsageHidden, mergeHidden, normalizeUsage };
490
+ export { HiddenVisibility, OptionName, Usage, UsageFormatOptions, UsageTerm, UsageTermFormatOptions, cloneUsage, cloneUsageTerm, extractArgumentMetavars, extractCommandNames, extractLeadingCommandNames, extractLeadingOptionNames, extractLiteralValues, extractOptionNames, formatUsage, formatUsageTerm, isDocHidden, isSuggestionHidden, isUsageHidden, mergeHidden, normalizeUsage };
package/dist/usage.js CHANGED
@@ -41,6 +41,8 @@ function mergeHidden(a, b) {
41
41
  * multiple, and exclusive terms.
42
42
  *
43
43
  * @param usage The usage description to extract option names from.
44
+ * @param includeHidden Whether to include fully hidden options (`hidden: true`)
45
+ * in the result. Defaults to `false`.
44
46
  * @returns A set containing all option names found in the usage description.
45
47
  *
46
48
  * @example
@@ -53,12 +55,12 @@ function mergeHidden(a, b) {
53
55
  * // names = Set(["--verbose", "-v", "--quiet", "-q"])
54
56
  * ```
55
57
  */
56
- function extractOptionNames(usage) {
58
+ function extractOptionNames(usage, includeHidden) {
57
59
  const names = /* @__PURE__ */ new Set();
58
60
  function traverseUsage(terms) {
59
61
  if (!terms || !Array.isArray(terms)) return;
60
62
  for (const term of terms) if (term.type === "option") {
61
- if (isSuggestionHidden(term.hidden)) continue;
63
+ if (!includeHidden && isSuggestionHidden(term.hidden)) continue;
62
64
  for (const name of term.names) names.add(name);
63
65
  } else if (term.type === "optional" || term.type === "multiple") traverseUsage(term.terms);
64
66
  else if (term.type === "exclusive") for (const exclusiveUsage of term.terms) traverseUsage(exclusiveUsage);
@@ -72,8 +74,10 @@ function extractOptionNames(usage) {
72
74
  * This function recursively traverses the usage structure and collects
73
75
  * all command names, similar to {@link extractOptionNames}.
74
76
  *
75
- * @param usage The usage structure to extract command names from
76
- * @returns A Set of all command names found in the usage structure
77
+ * @param usage The usage structure to extract command names from.
78
+ * @param includeHidden Whether to include fully hidden commands
79
+ * (`hidden: true`) in the result. Defaults to `false`.
80
+ * @returns A set of all command names found in the usage structure.
77
81
  *
78
82
  * @example
79
83
  * ```typescript
@@ -86,12 +90,12 @@ function extractOptionNames(usage) {
86
90
  * ```
87
91
  * @since 0.7.0
88
92
  */
89
- function extractCommandNames(usage) {
93
+ function extractCommandNames(usage, includeHidden) {
90
94
  const names = /* @__PURE__ */ new Set();
91
95
  function traverseUsage(terms) {
92
96
  if (!terms || !Array.isArray(terms)) return;
93
97
  for (const term of terms) if (term.type === "command") {
94
- if (isSuggestionHidden(term.hidden)) continue;
98
+ if (!includeHidden && isSuggestionHidden(term.hidden)) continue;
95
99
  names.add(term.name);
96
100
  } else if (term.type === "optional" || term.type === "multiple") traverseUsage(term.terms);
97
101
  else if (term.type === "exclusive") for (const exclusiveUsage of term.terms) traverseUsage(exclusiveUsage);
@@ -100,6 +104,164 @@ function extractCommandNames(usage) {
100
104
  return names;
101
105
  }
102
106
  /**
107
+ * Extracts option names that are reachable at the leading token position
108
+ * (before any command or argument gate).
109
+ *
110
+ * Unlike {@link extractOptionNames}, which traverses the entire usage tree,
111
+ * this function stops scanning a terms array after encountering a `command`,
112
+ * `argument`, or `literal` term, because subsequent terms are scoped under
113
+ * that positional token. It still recurses into `optional`, `multiple`,
114
+ * and `exclusive` containers, since they represent alternatives or wrappers
115
+ * at the same position.
116
+ *
117
+ * Known limitation: this function infers token positions from the `usage`
118
+ * tree, which is a display-oriented structure. Combinators like `tuple()`
119
+ * and `object()` sort or flatten usage by priority rather than token order,
120
+ * so the results can be inaccurate—both false positives (e.g., options
121
+ * appearing before commands due to priority sorting) and false negatives
122
+ * (e.g., options after commands that are actually parallel peers).
123
+ * The proper fix is to use `Parser.leadingNames` instead of usage-tree
124
+ * analysis. This also cannot detect `conditional(argument(...))`
125
+ * discriminator values, which never appear in the usage tree.
126
+ * See https://github.com/dahlia/optique/issues/734 and
127
+ * https://github.com/dahlia/optique/issues/735
128
+ *
129
+ * @param usage The usage description to extract leading option names from.
130
+ * @param includeHidden Whether to include fully hidden options
131
+ * (`hidden: true`) in the result. Defaults to `false`.
132
+ * @returns A set of option names reachable at the leading token position.
133
+ * @since 1.0.0
134
+ */
135
+ function extractLeadingOptionNames(usage, includeHidden) {
136
+ const names = /* @__PURE__ */ new Set();
137
+ function collectLeading(terms) {
138
+ if (!terms || !Array.isArray(terms)) return;
139
+ for (const term of terms) switch (term.type) {
140
+ case "option":
141
+ if (!includeHidden && isSuggestionHidden(term.hidden)) break;
142
+ for (const name of term.names) names.add(name);
143
+ break;
144
+ case "command": return;
145
+ case "argument":
146
+ case "literal": return;
147
+ case "optional":
148
+ collectLeading(term.terms);
149
+ break;
150
+ case "multiple":
151
+ collectLeading(term.terms);
152
+ if (term.min > 0 && branchConsumesToken(term.terms)) return;
153
+ break;
154
+ case "exclusive":
155
+ for (const branch of term.terms) collectLeading(branch);
156
+ if (exclusiveConsumesToken(term.terms)) return;
157
+ break;
158
+ default: break;
159
+ }
160
+ }
161
+ collectLeading(usage);
162
+ return names;
163
+ }
164
+ /**
165
+ * Extracts command names that could match as the first positional token.
166
+ *
167
+ * Unlike {@link extractCommandNames}, which traverses the entire usage tree,
168
+ * this function stops scanning a terms array after encountering a `command`,
169
+ * `argument`, or `literal` term, because subsequent terms in that array are
170
+ * scoped under that positional token (reachable only after the leading term
171
+ * is consumed). It still recurses into `optional`, `multiple`, and
172
+ * `exclusive` containers, since they represent alternatives or optional
173
+ * wrappers at the same token position.
174
+ *
175
+ * Known limitation: this function has the same usage-tree ordering and
176
+ * `conditional(argument(...))` caveats as {@link extractLeadingOptionNames}.
177
+ * See https://github.com/dahlia/optique/issues/734 and
178
+ * https://github.com/dahlia/optique/issues/735
179
+ *
180
+ * @param usage The usage description to extract leading command names from.
181
+ * @param includeHidden Whether to include fully hidden commands
182
+ * (`hidden: true`) in the result. Defaults to `false`.
183
+ * @returns A set of command names that could match at the first token position.
184
+ * @since 1.0.0
185
+ */
186
+ function extractLeadingCommandNames(usage, includeHidden) {
187
+ const names = /* @__PURE__ */ new Set();
188
+ function collectLeading(terms) {
189
+ if (!terms || !Array.isArray(terms)) return;
190
+ for (const term of terms) switch (term.type) {
191
+ case "command":
192
+ if (!includeHidden && isSuggestionHidden(term.hidden)) return;
193
+ names.add(term.name);
194
+ return;
195
+ case "argument":
196
+ case "literal": return;
197
+ case "optional":
198
+ collectLeading(term.terms);
199
+ break;
200
+ case "multiple":
201
+ collectLeading(term.terms);
202
+ if (term.min > 0 && branchConsumesToken(term.terms)) return;
203
+ break;
204
+ case "exclusive":
205
+ for (const branch of term.terms) collectLeading(branch);
206
+ if (exclusiveConsumesToken(term.terms)) return;
207
+ break;
208
+ default: break;
209
+ }
210
+ }
211
+ collectLeading(usage);
212
+ return names;
213
+ }
214
+ /**
215
+ * Checks whether every branch of an exclusive term must consume a positional
216
+ * token. When true, terms after the exclusive are at position N+1 and should
217
+ * not be considered "leading".
218
+ */
219
+ function exclusiveConsumesToken(branches) {
220
+ if (branches.length === 0) return false;
221
+ return branches.every((branch) => branchConsumesToken(branch));
222
+ }
223
+ function branchConsumesToken(terms) {
224
+ if (!terms || !Array.isArray(terms)) return false;
225
+ for (const term of terms) switch (term.type) {
226
+ case "command":
227
+ case "argument":
228
+ case "literal": return true;
229
+ case "option": break;
230
+ case "optional": break;
231
+ case "multiple":
232
+ if (term.min > 0 && branchConsumesToken(term.terms)) return true;
233
+ break;
234
+ case "exclusive":
235
+ if (exclusiveConsumesToken(term.terms)) return true;
236
+ break;
237
+ default: break;
238
+ }
239
+ return false;
240
+ }
241
+ /**
242
+ * Extracts all literal values from a usage description.
243
+ *
244
+ * This function recursively traverses the usage tree and collects all
245
+ * `literal` term values. Literal values represent fixed strings that
246
+ * the user must type (e.g., conditional discriminator values like
247
+ * `"server"` in `conditional(option("--mode", string()), { server: ... })`).
248
+ *
249
+ * @param usage The usage description to extract literal values from.
250
+ * @returns A set of all literal values found in the usage description.
251
+ * @since 1.0.0
252
+ */
253
+ function extractLiteralValues(usage) {
254
+ const values = /* @__PURE__ */ new Set();
255
+ function traverseUsage(terms) {
256
+ if (!terms || !Array.isArray(terms)) return;
257
+ for (const term of terms) if (term.type === "literal") values.add(term.value);
258
+ else if (term.type === "optional" || term.type === "multiple") traverseUsage(term.terms);
259
+ else if (term.type === "exclusive") for (const branch of term.terms) traverseUsage(branch);
260
+ }
261
+ traverseUsage(usage);
262
+ return values;
263
+ }
264
+ /**
103
265
  * Extracts all argument metavars from a Usage array.
104
266
  *
105
267
  * This function recursively traverses the usage structure and collects
@@ -547,4 +709,4 @@ function* formatUsageTermInternal(term, options) {
547
709
  }
548
710
 
549
711
  //#endregion
550
- export { cloneUsage, cloneUsageTerm, extractArgumentMetavars, extractCommandNames, extractOptionNames, formatUsage, formatUsageTerm, isDocHidden, isSuggestionHidden, isUsageHidden, mergeHidden, normalizeUsage };
712
+ export { cloneUsage, cloneUsageTerm, extractArgumentMetavars, extractCommandNames, extractLeadingCommandNames, extractLeadingOptionNames, extractLiteralValues, extractOptionNames, formatUsage, formatUsageTerm, isDocHidden, isSuggestionHidden, isUsageHidden, mergeHidden, normalizeUsage };
package/dist/validate.cjs CHANGED
@@ -63,6 +63,75 @@ function validateCommandNames(names, label) {
63
63
  }
64
64
  }
65
65
  /**
66
+ * Validates that there are no name collisions among meta features
67
+ * (help, version, completion) and between meta features and user parsers.
68
+ *
69
+ * The collision check is *position-aware*:
70
+ *
71
+ * - Meta **command** entries match at `args[0]` only, so they are checked
72
+ * against *leading* user names (those reachable before any positional gate).
73
+ * - Meta **option** entries use lenient scanners that match anywhere in
74
+ * `argv`, so they are checked against *all* user names at every depth,
75
+ * including literal values from conditional discriminators.
76
+ *
77
+ * Meta-vs-meta collisions are always checked in a unified namespace,
78
+ * because a meta command named `"--help"` and a meta option named
79
+ * `"--help"` both compete for the same token.
80
+ *
81
+ * @param userNames User parser names extracted at different scopes.
82
+ * @param metaEntries Active meta feature entries annotated with their kind.
83
+ * @throws {TypeError} If any collision or duplicate is detected.
84
+ * @since 1.0.0
85
+ */
86
+ function validateMetaNameCollisions(userNames, metaEntries) {
87
+ for (const [, label, names] of metaEntries) {
88
+ const seen = /* @__PURE__ */ new Set();
89
+ for (const name of names) {
90
+ if (seen.has(name)) throw new TypeError(`${capitalize(label)} has a duplicate name: "${name}"`);
91
+ seen.add(name);
92
+ }
93
+ }
94
+ const nameToLabel = /* @__PURE__ */ new Map();
95
+ for (const [, label, names] of metaEntries) for (const name of names) {
96
+ const existingLabel = nameToLabel.get(name);
97
+ if (existingLabel != null) throw new TypeError(`Name "${name}" is used by both ${existingLabel} and ${label}.`);
98
+ nameToLabel.set(name, label);
99
+ }
100
+ for (let i = 0; i < metaEntries.length; i++) {
101
+ const [, label, names, prefixMatch] = metaEntries[i];
102
+ if (!prefixMatch) continue;
103
+ for (const name of names) {
104
+ const prefix = name + "=";
105
+ for (let j = 0; j < metaEntries.length; j++) {
106
+ const [, otherLabel, otherNames] = metaEntries[j];
107
+ for (const otherName of otherNames) {
108
+ if (i === j && otherName === name) continue;
109
+ if (!otherName.startsWith(prefix)) continue;
110
+ throw new TypeError("The prefix form of name \"" + name + "\" in " + label + " shadows \"" + otherName + "\" in " + otherLabel + ".");
111
+ }
112
+ }
113
+ }
114
+ }
115
+ for (const [kind, label, names, prefixMatch] of metaEntries) {
116
+ const optionNames = kind === "command" ? userNames.leadingOptions : userNames.allOptions;
117
+ const commandNames = kind === "command" ? userNames.leadingCommands : userNames.allCommands;
118
+ for (const name of names) {
119
+ if (optionNames.has(name)) throw new TypeError(`User-defined option "${name}" conflicts with the built-in ${label}.`);
120
+ if (commandNames.has(name)) throw new TypeError(`User-defined command "${name}" conflicts with the built-in ${label}.`);
121
+ if (kind === "option" && userNames.allLiterals.has(name)) throw new TypeError(`Literal value "${name}" conflicts with the built-in ${label}.`);
122
+ if (prefixMatch) {
123
+ const prefix = name + "=";
124
+ for (const userName of optionNames) if (userName.startsWith(prefix)) throw new TypeError(`User-defined option "${userName}" conflicts with the built-in ${label} (prefix "${prefix}").`);
125
+ for (const userName of commandNames) if (userName.startsWith(prefix)) throw new TypeError(`User-defined command "${userName}" conflicts with the built-in ${label} (prefix "${prefix}").`);
126
+ for (const literal of userNames.allLiterals) if (literal.startsWith(prefix)) throw new TypeError(`Literal value "${literal}" conflicts with the built-in ${label} (prefix "${prefix}").`);
127
+ }
128
+ }
129
+ }
130
+ }
131
+ function capitalize(s) {
132
+ return s.charAt(0).toUpperCase() + s.slice(1);
133
+ }
134
+ /**
66
135
  * Validates a program name at runtime.
67
136
  *
68
137
  * Program names may contain spaces (e.g., file paths), but must not be empty,
@@ -81,5 +150,6 @@ function validateProgramName(programName) {
81
150
 
82
151
  //#endregion
83
152
  exports.validateCommandNames = validateCommandNames;
153
+ exports.validateMetaNameCollisions = validateMetaNameCollisions;
84
154
  exports.validateOptionNames = validateOptionNames;
85
155
  exports.validateProgramName = validateProgramName;
package/dist/validate.js CHANGED
@@ -62,6 +62,75 @@ function validateCommandNames(names, label) {
62
62
  }
63
63
  }
64
64
  /**
65
+ * Validates that there are no name collisions among meta features
66
+ * (help, version, completion) and between meta features and user parsers.
67
+ *
68
+ * The collision check is *position-aware*:
69
+ *
70
+ * - Meta **command** entries match at `args[0]` only, so they are checked
71
+ * against *leading* user names (those reachable before any positional gate).
72
+ * - Meta **option** entries use lenient scanners that match anywhere in
73
+ * `argv`, so they are checked against *all* user names at every depth,
74
+ * including literal values from conditional discriminators.
75
+ *
76
+ * Meta-vs-meta collisions are always checked in a unified namespace,
77
+ * because a meta command named `"--help"` and a meta option named
78
+ * `"--help"` both compete for the same token.
79
+ *
80
+ * @param userNames User parser names extracted at different scopes.
81
+ * @param metaEntries Active meta feature entries annotated with their kind.
82
+ * @throws {TypeError} If any collision or duplicate is detected.
83
+ * @since 1.0.0
84
+ */
85
+ function validateMetaNameCollisions(userNames, metaEntries) {
86
+ for (const [, label, names] of metaEntries) {
87
+ const seen = /* @__PURE__ */ new Set();
88
+ for (const name of names) {
89
+ if (seen.has(name)) throw new TypeError(`${capitalize(label)} has a duplicate name: "${name}"`);
90
+ seen.add(name);
91
+ }
92
+ }
93
+ const nameToLabel = /* @__PURE__ */ new Map();
94
+ for (const [, label, names] of metaEntries) for (const name of names) {
95
+ const existingLabel = nameToLabel.get(name);
96
+ if (existingLabel != null) throw new TypeError(`Name "${name}" is used by both ${existingLabel} and ${label}.`);
97
+ nameToLabel.set(name, label);
98
+ }
99
+ for (let i = 0; i < metaEntries.length; i++) {
100
+ const [, label, names, prefixMatch] = metaEntries[i];
101
+ if (!prefixMatch) continue;
102
+ for (const name of names) {
103
+ const prefix = name + "=";
104
+ for (let j = 0; j < metaEntries.length; j++) {
105
+ const [, otherLabel, otherNames] = metaEntries[j];
106
+ for (const otherName of otherNames) {
107
+ if (i === j && otherName === name) continue;
108
+ if (!otherName.startsWith(prefix)) continue;
109
+ throw new TypeError("The prefix form of name \"" + name + "\" in " + label + " shadows \"" + otherName + "\" in " + otherLabel + ".");
110
+ }
111
+ }
112
+ }
113
+ }
114
+ for (const [kind, label, names, prefixMatch] of metaEntries) {
115
+ const optionNames = kind === "command" ? userNames.leadingOptions : userNames.allOptions;
116
+ const commandNames = kind === "command" ? userNames.leadingCommands : userNames.allCommands;
117
+ for (const name of names) {
118
+ if (optionNames.has(name)) throw new TypeError(`User-defined option "${name}" conflicts with the built-in ${label}.`);
119
+ if (commandNames.has(name)) throw new TypeError(`User-defined command "${name}" conflicts with the built-in ${label}.`);
120
+ if (kind === "option" && userNames.allLiterals.has(name)) throw new TypeError(`Literal value "${name}" conflicts with the built-in ${label}.`);
121
+ if (prefixMatch) {
122
+ const prefix = name + "=";
123
+ for (const userName of optionNames) if (userName.startsWith(prefix)) throw new TypeError(`User-defined option "${userName}" conflicts with the built-in ${label} (prefix "${prefix}").`);
124
+ for (const userName of commandNames) if (userName.startsWith(prefix)) throw new TypeError(`User-defined command "${userName}" conflicts with the built-in ${label} (prefix "${prefix}").`);
125
+ for (const literal of userNames.allLiterals) if (literal.startsWith(prefix)) throw new TypeError(`Literal value "${literal}" conflicts with the built-in ${label} (prefix "${prefix}").`);
126
+ }
127
+ }
128
+ }
129
+ }
130
+ function capitalize(s) {
131
+ return s.charAt(0).toUpperCase() + s.slice(1);
132
+ }
133
+ /**
65
134
  * Validates a program name at runtime.
66
135
  *
67
136
  * Program names may contain spaces (e.g., file paths), but must not be empty,
@@ -79,4 +148,4 @@ function validateProgramName(programName) {
79
148
  }
80
149
 
81
150
  //#endregion
82
- export { validateCommandNames, validateOptionNames, validateProgramName };
151
+ export { validateCommandNames, validateMetaNameCollisions, validateOptionNames, validateProgramName };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.0.0-dev.1510+f054ad59",
3
+ "version": "1.0.0-dev.1519+797fd62b",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",