@optique/core 0.10.7 → 1.0.0-dev.1116

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.
Files changed (56) hide show
  1. package/README.md +4 -6
  2. package/dist/annotations.cjs +209 -1
  3. package/dist/annotations.d.cts +78 -1
  4. package/dist/annotations.d.ts +78 -1
  5. package/dist/annotations.js +201 -1
  6. package/dist/completion.cjs +194 -52
  7. package/dist/completion.js +194 -52
  8. package/dist/constructs.cjs +310 -78
  9. package/dist/constructs.d.cts +525 -644
  10. package/dist/constructs.d.ts +525 -644
  11. package/dist/constructs.js +311 -79
  12. package/dist/context.cjs +43 -3
  13. package/dist/context.d.cts +113 -5
  14. package/dist/context.d.ts +113 -5
  15. package/dist/context.js +41 -3
  16. package/dist/dependency.cjs +172 -66
  17. package/dist/dependency.d.cts +22 -2
  18. package/dist/dependency.d.ts +22 -2
  19. package/dist/dependency.js +172 -66
  20. package/dist/doc.cjs +46 -1
  21. package/dist/doc.d.cts +24 -0
  22. package/dist/doc.d.ts +24 -0
  23. package/dist/doc.js +46 -1
  24. package/dist/facade.cjs +702 -322
  25. package/dist/facade.d.cts +124 -190
  26. package/dist/facade.d.ts +124 -190
  27. package/dist/facade.js +703 -323
  28. package/dist/index.cjs +5 -0
  29. package/dist/index.d.cts +5 -5
  30. package/dist/index.d.ts +5 -5
  31. package/dist/index.js +3 -3
  32. package/dist/message.cjs +7 -4
  33. package/dist/message.js +7 -4
  34. package/dist/mode-dispatch.cjs +23 -1
  35. package/dist/mode-dispatch.d.cts +55 -0
  36. package/dist/mode-dispatch.d.ts +55 -0
  37. package/dist/mode-dispatch.js +21 -1
  38. package/dist/modifiers.cjs +210 -55
  39. package/dist/modifiers.js +211 -56
  40. package/dist/parser.cjs +80 -47
  41. package/dist/parser.d.cts +18 -3
  42. package/dist/parser.d.ts +18 -3
  43. package/dist/parser.js +82 -50
  44. package/dist/primitives.cjs +102 -37
  45. package/dist/primitives.d.cts +81 -24
  46. package/dist/primitives.d.ts +81 -24
  47. package/dist/primitives.js +103 -39
  48. package/dist/usage.cjs +88 -6
  49. package/dist/usage.d.cts +51 -13
  50. package/dist/usage.d.ts +51 -13
  51. package/dist/usage.js +85 -7
  52. package/dist/valueparser.cjs +391 -106
  53. package/dist/valueparser.d.cts +62 -10
  54. package/dist/valueparser.d.ts +62 -10
  55. package/dist/valueparser.js +391 -106
  56. package/package.json +10 -1
@@ -1,11 +1,13 @@
1
+ import { getAnnotations, inheritAnnotations, injectAnnotations } from "./annotations.js";
1
2
  import { message, optionName, text, values } from "./message.js";
2
3
  import { DependencyRegistry, dependencyId, isDeferredParseState, isDependencySourceState, isPendingDependencySourceState, isWrappedDependencySource, parseWithDependency, wrappedDependencySourceMarker } from "./dependency.js";
3
4
  import { dispatchByMode, dispatchIterableByMode } from "./mode-dispatch.js";
4
- import { extractArgumentMetavars, extractCommandNames, extractOptionNames } from "./usage.js";
5
+ import { extractArgumentMetavars, extractCommandNames, extractOptionNames, isDocHidden, mergeHidden } from "./usage.js";
5
6
  import { DEFAULT_FIND_SIMILAR_OPTIONS, createErrorWithSuggestions, createSuggestionMessage, deduplicateSuggestions, findSimilar } from "./suggestion.js";
6
7
  import { collectLeadingCandidates } from "./usage-internals.js";
7
8
 
8
9
  //#region src/constructs.ts
10
+ const inheritParentAnnotationsKey = Symbol.for("@optique/core/inheritParentAnnotations");
9
11
  function createUnexpectedInputErrorWithScopedSuggestions(baseError, invalidInput, parsers, customFormatter) {
10
12
  const options = /* @__PURE__ */ new Set();
11
13
  const commands = /* @__PURE__ */ new Set();
@@ -19,6 +21,60 @@ function createUnexpectedInputErrorWithScopedSuggestions(baseError, invalidInput
19
21
  ...suggestionMsg
20
22
  ] : baseError;
21
23
  }
24
+ function applyHiddenToUsageTerm(term, hidden) {
25
+ if (hidden == null) return term;
26
+ if (term.type === "optional") return {
27
+ type: "optional",
28
+ terms: applyHiddenToUsage(term.terms, hidden)
29
+ };
30
+ if (term.type === "multiple") return {
31
+ type: "multiple",
32
+ terms: applyHiddenToUsage(term.terms, hidden),
33
+ min: term.min
34
+ };
35
+ if (term.type === "exclusive") return {
36
+ type: "exclusive",
37
+ terms: term.terms.map((u) => applyHiddenToUsage(u, hidden))
38
+ };
39
+ if (term.type === "argument" || term.type === "option" || term.type === "command" || term.type === "passthrough") return {
40
+ ...term,
41
+ hidden: mergeHidden(term.hidden, hidden)
42
+ };
43
+ return term;
44
+ }
45
+ function applyHiddenToUsage(usage, hidden) {
46
+ if (hidden == null) return usage;
47
+ return usage.map((term) => applyHiddenToUsageTerm(term, hidden));
48
+ }
49
+ function applyHiddenToDocEntry(entry, hidden) {
50
+ const mergedTerm = applyHiddenToUsageTerm(entry.term, hidden);
51
+ if ((mergedTerm.type === "argument" || mergedTerm.type === "option" || mergedTerm.type === "command" || mergedTerm.type === "passthrough") && isDocHidden(mergedTerm.hidden)) return void 0;
52
+ return {
53
+ ...entry,
54
+ term: mergedTerm
55
+ };
56
+ }
57
+ function applyHiddenToDocFragments(fragments, hidden) {
58
+ if (hidden == null) return fragments;
59
+ const result = [];
60
+ for (const fragment of fragments) {
61
+ if (fragment.type === "entry") {
62
+ const entry = applyHiddenToDocEntry(fragment, hidden);
63
+ if (entry != null) result.push({
64
+ ...entry,
65
+ type: "entry"
66
+ });
67
+ continue;
68
+ }
69
+ const entries = fragment.entries.map((entry) => applyHiddenToDocEntry(entry, hidden)).filter((entry) => entry != null);
70
+ result.push({
71
+ type: "section",
72
+ title: fragment.title,
73
+ entries
74
+ });
75
+ }
76
+ return result;
77
+ }
22
78
  /**
23
79
  * Checks if the given token is an option name that requires a value
24
80
  * (i.e., has a metavar) within the given usage terms.
@@ -554,8 +610,9 @@ function longestMatch(...args) {
554
610
  * @internal
555
611
  */
556
612
  function* suggestObjectSync(context, prefix, parserPairs) {
557
- const registry = context.dependencyRegistry instanceof DependencyRegistry ? context.dependencyRegistry : new DependencyRegistry();
613
+ const registry = context.dependencyRegistry ? context.dependencyRegistry.clone() : new DependencyRegistry();
558
614
  if (context.state && typeof context.state === "object") collectDependencies(context.state, registry);
615
+ completeDependencySourceDefaults(context, parserPairs, registry);
559
616
  const contextWithRegistry = {
560
617
  ...context,
561
618
  dependencyRegistry: registry
@@ -587,8 +644,9 @@ function* suggestObjectSync(context, prefix, parserPairs) {
587
644
  * @internal
588
645
  */
589
646
  async function* suggestObjectAsync(context, prefix, parserPairs) {
590
- const registry = context.dependencyRegistry instanceof DependencyRegistry ? context.dependencyRegistry : new DependencyRegistry();
647
+ const registry = context.dependencyRegistry ? context.dependencyRegistry.clone() : new DependencyRegistry();
591
648
  if (context.state && typeof context.state === "object") collectDependencies(context.state, registry);
649
+ await completeDependencySourceDefaultsAsync(context, parserPairs, registry);
592
650
  const contextWithRegistry = {
593
651
  ...context,
594
652
  dependencyRegistry: registry
@@ -617,17 +675,62 @@ async function* suggestObjectAsync(context, prefix, parserPairs) {
617
675
  yield* deduplicateSuggestions(suggestions);
618
676
  }
619
677
  /**
620
- * Resolves deferred parse states in an object's field states.
621
- * This function builds a dependency registry from DependencySourceState fields
622
- * and re-parses DeferredParseState fields using the actual dependency values.
678
+ * Registers an already-resolved dependency source value in the registry
679
+ * if it is a successful {@link DependencySourceState}.
623
680
  *
624
- * @param fieldStates A record of field names to their state values
625
- * @returns The field states with deferred states resolved to their actual values
626
681
  * @internal
627
682
  */
683
+ function registerCompletedDependency(completed, registry) {
684
+ if (isDependencySourceState(completed) && completed.result.success && !registry.has(completed[dependencyId])) registry.set(completed[dependencyId], completed.result.value);
685
+ }
686
+ /**
687
+ * Yields `(parser, state)` pairs for dependency source parsers whose field
688
+ * state is unpopulated, so callers can invoke `complete()` on them.
689
+ *
690
+ * @see https://github.com/dahlia/optique/issues/186
691
+ * @internal
692
+ */
693
+ function* pendingDependencyDefaults(context, parserPairs) {
694
+ for (const [field, fieldParser] of parserPairs) {
695
+ const fieldState = context.state != null && typeof context.state === "object" && field in context.state ? context.state[field] : void 0;
696
+ if (fieldState != null) continue;
697
+ if (isPendingDependencySourceState(fieldParser.initialState)) yield {
698
+ parser: fieldParser,
699
+ state: fieldParser.initialState
700
+ };
701
+ else if (isWrappedDependencySource(fieldParser)) yield {
702
+ parser: fieldParser,
703
+ state: [fieldParser[wrappedDependencySourceMarker]]
704
+ };
705
+ }
706
+ }
707
+ /**
708
+ * Pre-completes dependency source parsers wrapped in `withDefault()` whose
709
+ * field state is unpopulated, so that their default values are registered
710
+ * in the dependency registry. This mirrors Phase 1 of parse-time dependency
711
+ * resolution and ensures `suggest()` sees the same defaults as `parse()`.
712
+ *
713
+ * @see https://github.com/dahlia/optique/issues/186
714
+ * @internal
715
+ */
716
+ function completeDependencySourceDefaults(context, parserPairs, registry) {
717
+ for (const { parser, state } of pendingDependencyDefaults(context, parserPairs)) registerCompletedDependency(parser.complete(state), registry);
718
+ }
719
+ /**
720
+ * Async version of {@link completeDependencySourceDefaults} that awaits
721
+ * `complete()` results. Async parsers with `transformsDependencyValue`
722
+ * return a `Promise` from `complete()`, which the sync version cannot handle.
723
+ *
724
+ * @see https://github.com/dahlia/optique/issues/186
725
+ * @internal
726
+ */
727
+ async function completeDependencySourceDefaultsAsync(context, parserPairs, registry) {
728
+ for (const { parser, state } of pendingDependencyDefaults(context, parserPairs)) registerCompletedDependency(await parser.complete(state), registry);
729
+ }
628
730
  /**
629
731
  * Recursively collects dependency values from DependencySourceState objects
630
732
  * found anywhere in the state tree.
733
+ * @internal
631
734
  */
632
735
  function collectDependencies(state, registry, visited = /* @__PURE__ */ new WeakSet()) {
633
736
  if (state === null || state === void 0) return;
@@ -783,8 +886,36 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
783
886
  const parserKeys = Reflect.ownKeys(parsers);
784
887
  const parserPairs = parserKeys.map((k) => [k, parsers[k]]);
785
888
  parserPairs.sort(([_, parserA], [__, parserB]) => parserB.priority - parserA.priority);
786
- const initialState = {};
787
- for (const key of parserKeys) initialState[key] = parsers[key].initialState;
889
+ const createInitialState = () => {
890
+ const state = {};
891
+ for (const key of parserKeys) state[key] = parsers[key].initialState;
892
+ return state;
893
+ };
894
+ const inheritedFieldStateCache = /* @__PURE__ */ new WeakMap();
895
+ const createFieldStateGetter = (parentState) => {
896
+ return (field, parser) => {
897
+ const fieldKey = field;
898
+ const cache = parentState != null && typeof parentState === "object" ? inheritedFieldStateCache.get(parentState) ?? (() => {
899
+ const stateCache = /* @__PURE__ */ new Map();
900
+ inheritedFieldStateCache.set(parentState, stateCache);
901
+ return stateCache;
902
+ })() : void 0;
903
+ if (cache?.has(fieldKey)) return cache.get(fieldKey);
904
+ const sourceState = parentState != null && typeof parentState === "object" && fieldKey in parentState ? parentState[fieldKey] : parser.initialState;
905
+ if (sourceState == null || typeof sourceState !== "object") {
906
+ cache?.set(fieldKey, sourceState);
907
+ return sourceState;
908
+ }
909
+ const annotations = getAnnotations(parentState);
910
+ if (annotations === void 0 || getAnnotations(sourceState) === annotations) {
911
+ cache?.set(fieldKey, sourceState);
912
+ return sourceState;
913
+ }
914
+ const inheritedState = Reflect.get(parser, inheritParentAnnotationsKey) === true ? injectAnnotations(sourceState, annotations) : inheritAnnotations(parentState, sourceState);
915
+ cache?.set(fieldKey, inheritedState);
916
+ return inheritedState;
917
+ };
918
+ };
788
919
  if (!options.allowDuplicates) checkDuplicateOptionNames(parserPairs.map(([field, parser]) => [field, parser.usage]));
789
920
  const noMatchContext = analyzeNoMatchContext(parserKeys.map((k) => parsers[k]));
790
921
  const combinedMode = parserKeys.some((k) => parsers[k].$mode === "async") ? "async" : "sync";
@@ -809,10 +940,11 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
809
940
  let madeProgress = true;
810
941
  while (madeProgress && currentContext.buffer.length > 0) {
811
942
  madeProgress = false;
943
+ const getFieldState = createFieldStateGetter(currentContext.state);
812
944
  for (const [field, parser] of parserPairs) {
813
945
  const result = parser.parse({
814
946
  ...currentContext,
815
- state: currentContext.state && typeof currentContext.state === "object" && field in currentContext.state ? currentContext.state[field] : parser.initialState
947
+ state: getFieldState(field, parser)
816
948
  });
817
949
  if (result.success && result.consumed.length > 0) {
818
950
  currentContext = {
@@ -838,8 +970,9 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
838
970
  };
839
971
  if (context.buffer.length === 0) {
840
972
  let allCanComplete = true;
973
+ const getFieldState = createFieldStateGetter(context.state);
841
974
  for (const [field, parser] of parserPairs) {
842
- const fieldState = context.state && typeof context.state === "object" && field in context.state ? context.state[field] : parser.initialState;
975
+ const fieldState = getFieldState(field, parser);
843
976
  const completeResult = parser.complete(fieldState);
844
977
  if (!completeResult.success) {
845
978
  allCanComplete = false;
@@ -865,10 +998,11 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
865
998
  let madeProgress = true;
866
999
  while (madeProgress && currentContext.buffer.length > 0) {
867
1000
  madeProgress = false;
1001
+ const getFieldState = createFieldStateGetter(currentContext.state);
868
1002
  for (const [field, parser] of parserPairs) {
869
1003
  const resultOrPromise = parser.parse({
870
1004
  ...currentContext,
871
- state: currentContext.state && typeof currentContext.state === "object" && field in currentContext.state ? currentContext.state[field] : parser.initialState
1005
+ state: getFieldState(field, parser)
872
1006
  });
873
1007
  const result = await resultOrPromise;
874
1008
  if (result.success && result.consumed.length > 0) {
@@ -895,8 +1029,9 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
895
1029
  };
896
1030
  if (context.buffer.length === 0) {
897
1031
  let allCanComplete = true;
1032
+ const getFieldState = createFieldStateGetter(context.state);
898
1033
  for (const [field, parser] of parserPairs) {
899
- const fieldState = context.state && typeof context.state === "object" && field in context.state ? context.state[field] : parser.initialState;
1034
+ const fieldState = getFieldState(field, parser);
900
1035
  const completeResult = await parser.complete(fieldState);
901
1036
  if (!completeResult.success) {
902
1037
  allCanComplete = false;
@@ -919,8 +1054,10 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
919
1054
  $valueType: [],
920
1055
  $stateType: [],
921
1056
  priority: Math.max(...parserKeys.map((k) => parsers[k].priority)),
922
- usage: parserPairs.flatMap(([_, p]) => p.usage),
923
- initialState,
1057
+ usage: applyHiddenToUsage(parserPairs.flatMap(([_, p]) => p.usage), options.hidden),
1058
+ get initialState() {
1059
+ return createInitialState();
1060
+ },
924
1061
  parse(context) {
925
1062
  return dispatchByMode(combinedMode, () => parseSync(context), () => parseAsync(context));
926
1063
  },
@@ -928,6 +1065,7 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
928
1065
  return dispatchByMode(combinedMode, () => {
929
1066
  const preCompletedState = {};
930
1067
  const preCompletedKeys = /* @__PURE__ */ new Set();
1068
+ const getFieldState = createFieldStateGetter(state);
931
1069
  for (const field of parserKeys) {
932
1070
  const fieldKey = field;
933
1071
  const fieldState = state[fieldKey];
@@ -947,10 +1085,11 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
947
1085
  preCompletedState[fieldKey] = completed;
948
1086
  preCompletedKeys.add(fieldKey);
949
1087
  } else preCompletedState[fieldKey] = fieldState;
950
- } else preCompletedState[fieldKey] = fieldState;
1088
+ } else preCompletedState[fieldKey] = getFieldState(field, fieldParser);
951
1089
  }
952
1090
  const resolvedState = resolveDeferredParseStates(preCompletedState);
953
1091
  const result = {};
1092
+ const getCompletionFieldState = createFieldStateGetter(state);
954
1093
  for (const field of parserKeys) {
955
1094
  const fieldKey = field;
956
1095
  const fieldResolvedState = resolvedState[fieldKey];
@@ -964,7 +1103,8 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
964
1103
  };
965
1104
  continue;
966
1105
  }
967
- const valueResult = fieldParser.complete(fieldResolvedState);
1106
+ const completionState = fieldResolvedState === void 0 ? getCompletionFieldState(field, fieldParser) : fieldResolvedState;
1107
+ const valueResult = fieldParser.complete(completionState);
968
1108
  if (valueResult.success) result[fieldKey] = valueResult.value;
969
1109
  else return {
970
1110
  success: false,
@@ -978,6 +1118,7 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
978
1118
  }, async () => {
979
1119
  const preCompletedState = {};
980
1120
  const preCompletedKeys = /* @__PURE__ */ new Set();
1121
+ const getFieldState = createFieldStateGetter(state);
981
1122
  for (const field of parserKeys) {
982
1123
  const fieldKey = field;
983
1124
  const fieldState = state[fieldKey];
@@ -997,10 +1138,11 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
997
1138
  preCompletedState[fieldKey] = completed;
998
1139
  preCompletedKeys.add(fieldKey);
999
1140
  } else preCompletedState[fieldKey] = fieldState;
1000
- } else preCompletedState[fieldKey] = fieldState;
1141
+ } else preCompletedState[fieldKey] = getFieldState(field, fieldParser);
1001
1142
  }
1002
1143
  const resolvedState = await resolveDeferredParseStatesAsync(preCompletedState);
1003
1144
  const result = {};
1145
+ const getCompletionFieldState = createFieldStateGetter(state);
1004
1146
  for (const field of parserKeys) {
1005
1147
  const fieldKey = field;
1006
1148
  const fieldResolvedState = resolvedState[fieldKey];
@@ -1014,7 +1156,8 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
1014
1156
  };
1015
1157
  continue;
1016
1158
  }
1017
- const valueResult = await fieldParser.complete(fieldResolvedState);
1159
+ const completionState = fieldResolvedState === void 0 ? getCompletionFieldState(field, fieldParser) : fieldResolvedState;
1160
+ const valueResult = await fieldParser.complete(completionState);
1018
1161
  if (valueResult.success) result[fieldKey] = valueResult.value;
1019
1162
  else return {
1020
1163
  success: false,
@@ -1028,6 +1171,7 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
1028
1171
  });
1029
1172
  },
1030
1173
  suggest(context, prefix) {
1174
+ if (options.hidden === true) return dispatchIterableByMode(combinedMode, function* () {}, async function* () {});
1031
1175
  return dispatchIterableByMode(combinedMode, () => {
1032
1176
  const syncParserPairs = parserPairs;
1033
1177
  return suggestObjectSync(context, prefix, syncParserPairs);
@@ -1041,9 +1185,10 @@ function object(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
1041
1185
  };
1042
1186
  return p.getDocFragments(fieldState, defaultValue?.[field]).fragments;
1043
1187
  });
1044
- const entries = fragments.filter((d) => d.type === "entry");
1188
+ const hiddenAwareFragments = applyHiddenToDocFragments(fragments, options.hidden);
1189
+ const entries = hiddenAwareFragments.filter((d) => d.type === "entry");
1045
1190
  const sections = [];
1046
- for (const fragment of fragments) {
1191
+ for (const fragment of hiddenAwareFragments) {
1047
1192
  if (fragment.type !== "section") continue;
1048
1193
  if (fragment.title == null) entries.push(...fragment.entries);
1049
1194
  else sections.push(fragment);
@@ -1375,9 +1520,10 @@ function tuple(labelOrParsers, maybeParsersOrOptions, maybeOptions) {
1375
1520
  function merge(...args) {
1376
1521
  const label = typeof args[0] === "string" ? args[0] : void 0;
1377
1522
  const lastArg = args[args.length - 1];
1378
- const options = lastArg && typeof lastArg === "object" && !("parse" in lastArg) && !("complete" in lastArg) ? lastArg : {};
1523
+ const hasOptions = lastArg != null && typeof lastArg === "object" && !("$valueType" in lastArg);
1524
+ const options = hasOptions ? lastArg : {};
1379
1525
  const startIndex = typeof args[0] === "string" ? 1 : 0;
1380
- const endIndex = lastArg && typeof lastArg === "object" && !("parse" in lastArg) && !("complete" in lastArg) ? args.length - 1 : args.length;
1526
+ const endIndex = hasOptions ? args.length - 1 : args.length;
1381
1527
  const rawParsers = args.slice(startIndex, endIndex);
1382
1528
  const combinedMode = rawParsers.some((p) => p.$mode === "async") ? "async" : "sync";
1383
1529
  const isAsync = combinedMode === "async";
@@ -1518,7 +1664,7 @@ function merge(...args) {
1518
1664
  $valueType: [],
1519
1665
  $stateType: [],
1520
1666
  priority: Math.max(...parsers.map((p) => p.priority)),
1521
- usage: parsers.flatMap((p) => p.usage),
1667
+ usage: applyHiddenToUsage(parsers.flatMap((p) => p.usage), options.hidden),
1522
1668
  initialState,
1523
1669
  parse(context) {
1524
1670
  if (isAsync) return parseAsync(context);
@@ -1572,6 +1718,7 @@ function merge(...args) {
1572
1718
  })();
1573
1719
  },
1574
1720
  suggest(context, prefix) {
1721
+ if (options.hidden === true) return dispatchIterableByMode(combinedMode, function* () {}, async function* () {});
1575
1722
  const extractState = (p, i) => {
1576
1723
  if (p.initialState === void 0) {
1577
1724
  const key = `__parser_${i}`;
@@ -1587,13 +1734,19 @@ function merge(...args) {
1587
1734
  }
1588
1735
  return p.initialState;
1589
1736
  };
1737
+ const registry = context.dependencyRegistry ? context.dependencyRegistry.clone() : new DependencyRegistry();
1738
+ if (context.state && typeof context.state === "object") collectDependencies(context.state, registry);
1739
+ const contextWithRegistry = {
1740
+ ...context,
1741
+ dependencyRegistry: registry
1742
+ };
1590
1743
  if (isAsync) return async function* () {
1591
1744
  const suggestions = [];
1592
1745
  for (let i = 0; i < parsers.length; i++) {
1593
1746
  const parser = parsers[i];
1594
1747
  const parserState = extractState(parser, i);
1595
1748
  const parserSuggestions = parser.suggest({
1596
- ...context,
1749
+ ...contextWithRegistry,
1597
1750
  state: parserState
1598
1751
  }, prefix);
1599
1752
  if (parser.$mode === "async") for await (const s of parserSuggestions) suggestions.push(s);
@@ -1607,7 +1760,7 @@ function merge(...args) {
1607
1760
  const parser = syncParsers[i];
1608
1761
  const parserState = extractState(parser, i);
1609
1762
  const parserSuggestions = parser.suggest({
1610
- ...context,
1763
+ ...contextWithRegistry,
1611
1764
  state: parserState
1612
1765
  }, prefix);
1613
1766
  suggestions.push(...parserSuggestions);
@@ -1638,9 +1791,10 @@ function merge(...args) {
1638
1791
  footer ??= docFragments.footer;
1639
1792
  return docFragments.fragments;
1640
1793
  });
1641
- const entries = fragments.filter((f) => f.type === "entry");
1794
+ const hiddenAwareFragments = applyHiddenToDocFragments(fragments, options.hidden);
1795
+ const entries = hiddenAwareFragments.filter((f) => f.type === "entry");
1642
1796
  const sections = [];
1643
- for (const fragment of fragments) {
1797
+ for (const fragment of hiddenAwareFragments) {
1644
1798
  if (fragment.type !== "section") continue;
1645
1799
  if (fragment.title == null) entries.push(...fragment.entries);
1646
1800
  else sections.push(fragment);
@@ -1676,6 +1830,118 @@ function merge(...args) {
1676
1830
  }
1677
1831
  };
1678
1832
  }
1833
+ /**
1834
+ * Builds a dependency registry from the pre-parsed context state and returns
1835
+ * the enriched context alongside the state array. Used by both the sync and
1836
+ * async branches of `concat().suggest()`.
1837
+ * @internal
1838
+ */
1839
+ function buildSuggestRegistry(preParsedContext) {
1840
+ const stateArray = preParsedContext.state;
1841
+ const registry = preParsedContext.dependencyRegistry ? preParsedContext.dependencyRegistry.clone() : new DependencyRegistry();
1842
+ if (stateArray && Array.isArray(stateArray)) collectDependencies(stateArray, registry);
1843
+ return {
1844
+ context: {
1845
+ ...preParsedContext,
1846
+ dependencyRegistry: registry
1847
+ },
1848
+ stateArray
1849
+ };
1850
+ }
1851
+ /**
1852
+ * This helper replays child parsers in priority order (mirroring
1853
+ * `concat.parse()`) to accumulate dependency source values for suggestions.
1854
+ * @internal
1855
+ */
1856
+ function preParseSuggestContext(context, parsers) {
1857
+ if (context.buffer.length < 1 || !Array.isArray(context.state)) return context;
1858
+ return preParseSuggestLoop(context, context.state.slice(), parsers);
1859
+ }
1860
+ /**
1861
+ * Async variant of {@link preParseSuggestContext} that awaits async child
1862
+ * parsers so that dependency sources from async sub-parsers are resolved.
1863
+ * @internal
1864
+ */
1865
+ async function preParseSuggestContextAsync(context, parsers) {
1866
+ if (context.buffer.length < 1 || !Array.isArray(context.state)) return context;
1867
+ return await preParseSuggestLoop(context, context.state.slice(), parsers);
1868
+ }
1869
+ /**
1870
+ * Shared loop for sync and async concat suggest pre-parse. When a child
1871
+ * parser returns a Promise its result is awaited; sync results are used
1872
+ * directly. Parsers are tried in priority order, matching `concat.parse()`.
1873
+ * @internal
1874
+ */
1875
+ function preParseSuggestLoop(context, stateArray, parsers, matchedParsers = /* @__PURE__ */ new Set()) {
1876
+ const indexedParsers = parsers.map((parser, index) => [parser, index]);
1877
+ let currentContext = context;
1878
+ let changed = true;
1879
+ while (changed && currentContext.buffer.length > 0) {
1880
+ changed = false;
1881
+ const remaining = indexedParsers.filter(([_, index]) => !matchedParsers.has(index)).sort(([a], [b]) => b.priority - a.priority);
1882
+ const tried = tryParseSuggestList(currentContext, stateArray, parsers, matchedParsers, remaining);
1883
+ if (tried === null) continue;
1884
+ if (typeof tried === "object" && "then" in tried && typeof tried.then === "function") return tried;
1885
+ currentContext = tried;
1886
+ changed = true;
1887
+ }
1888
+ return {
1889
+ ...currentContext,
1890
+ state: stateArray
1891
+ };
1892
+ }
1893
+ /**
1894
+ * Tries each parser in `remaining` once. Returns the updated context if a
1895
+ * parser consumed input (sync), a Promise that resolves to a context if an
1896
+ * async parser was encountered, or `null` if no parser consumed.
1897
+ * @internal
1898
+ */
1899
+ function tryParseSuggestList(context, stateArray, parsers, matchedParsers, remaining) {
1900
+ for (let ri = 0; ri < remaining.length; ri++) {
1901
+ const [parser, index] = remaining[ri];
1902
+ const parserState = index < stateArray.length ? stateArray[index] : parser.initialState;
1903
+ const resultOrPromise = parser.parse({
1904
+ ...context,
1905
+ state: parserState
1906
+ });
1907
+ if (resultOrPromise != null && typeof resultOrPromise === "object" && "then" in resultOrPromise && typeof resultOrPromise.then === "function") {
1908
+ const tail = remaining.slice(ri + 1);
1909
+ return resultOrPromise.then((result$1) => {
1910
+ if (result$1.success && result$1.consumed.length > 0) {
1911
+ stateArray[index] = result$1.next.state;
1912
+ matchedParsers.add(index);
1913
+ return preParseSuggestLoop({
1914
+ ...context,
1915
+ buffer: result$1.next.buffer,
1916
+ optionsTerminated: result$1.next.optionsTerminated,
1917
+ state: stateArray
1918
+ }, stateArray, parsers, matchedParsers);
1919
+ }
1920
+ const next = tryParseSuggestList(context, stateArray, parsers, matchedParsers, tail);
1921
+ if (next === null) return {
1922
+ ...context,
1923
+ state: stateArray
1924
+ };
1925
+ if (typeof next === "object" && "then" in next && typeof next.then === "function") return next.then((ctx) => ctx.buffer.length < context.buffer.length ? preParseSuggestLoop(ctx, stateArray, parsers, matchedParsers) : ctx);
1926
+ const nextCtx = next;
1927
+ if (nextCtx.buffer.length < context.buffer.length) return preParseSuggestLoop(nextCtx, stateArray, parsers, matchedParsers);
1928
+ return nextCtx;
1929
+ });
1930
+ }
1931
+ const result = resultOrPromise;
1932
+ if (result.success && result.consumed.length > 0) {
1933
+ stateArray[index] = result.next.state;
1934
+ matchedParsers.add(index);
1935
+ return {
1936
+ ...context,
1937
+ buffer: result.next.buffer,
1938
+ optionsTerminated: result.next.optionsTerminated,
1939
+ state: stateArray
1940
+ };
1941
+ }
1942
+ }
1943
+ return null;
1944
+ }
1679
1945
  function concat(...parsers) {
1680
1946
  const combinedMode = parsers.some((p) => p.$mode === "async") ? "async" : "sync";
1681
1947
  const isAsync = combinedMode === "async";
@@ -1859,14 +2125,15 @@ function concat(...parsers) {
1859
2125
  return completeSync(state);
1860
2126
  },
1861
2127
  suggest(context, prefix) {
1862
- const stateArray = context.state;
1863
2128
  if (isAsync) return async function* () {
2129
+ const preParsedContext$1 = await preParseSuggestContextAsync(context, parsers);
2130
+ const { context: contextWithRegistry$1, stateArray: stateArray$1 } = buildSuggestRegistry(preParsedContext$1);
1864
2131
  const suggestions = [];
1865
2132
  for (let i = 0; i < parsers.length; i++) {
1866
2133
  const parser = parsers[i];
1867
- const parserState = stateArray && Array.isArray(stateArray) ? stateArray[i] : parser.initialState;
2134
+ const parserState = stateArray$1 && Array.isArray(stateArray$1) ? stateArray$1[i] : parser.initialState;
1868
2135
  const parserSuggestions = parser.suggest({
1869
- ...context,
2136
+ ...contextWithRegistry$1,
1870
2137
  state: parserState
1871
2138
  }, prefix);
1872
2139
  if (parser.$mode === "async") for await (const s of parserSuggestions) suggestions.push(s);
@@ -1874,13 +2141,15 @@ function concat(...parsers) {
1874
2141
  }
1875
2142
  yield* deduplicateSuggestions(suggestions);
1876
2143
  }();
2144
+ const preParsedContext = preParseSuggestContext(context, syncParsers);
2145
+ const { context: contextWithRegistry, stateArray } = buildSuggestRegistry(preParsedContext);
1877
2146
  return function* () {
1878
2147
  const suggestions = [];
1879
2148
  for (let i = 0; i < syncParsers.length; i++) {
1880
2149
  const parser = syncParsers[i];
1881
2150
  const parserState = stateArray && Array.isArray(stateArray) ? stateArray[i] : parser.initialState;
1882
2151
  const parserSuggestions = parser.suggest({
1883
- ...context,
2152
+ ...contextWithRegistry,
1884
2153
  state: parserState
1885
2154
  }, prefix);
1886
2155
  suggestions.push(...parserSuggestions);
@@ -1915,64 +2184,27 @@ function concat(...parsers) {
1915
2184
  }
1916
2185
  };
1917
2186
  }
1918
- /**
1919
- * Wraps a parser with a group label for documentation purposes.
1920
- *
1921
- * The `group()` function is a documentation-only wrapper that applies a label
1922
- * to any parser for help text organization. This allows you to use clean code
1923
- * structure with combinators like {@link merge} while maintaining well-organized
1924
- * help text through group labeling.
1925
- *
1926
- * The wrapped parser has identical parsing behavior but generates documentation
1927
- * fragments wrapped in a labeled section. This is particularly useful when
1928
- * combining parsers using {@link merge}—you can wrap the merged result with
1929
- * `group()` to add a section header in help output.
1930
- *
1931
- * @example
1932
- * ```typescript
1933
- * const apiOptions = merge(
1934
- * object({ endpoint: option("--endpoint", string()) }),
1935
- * object({ timeout: option("--timeout", integer()) })
1936
- * );
1937
- *
1938
- * const groupedApiOptions = group("API Options", apiOptions);
1939
- * // Now produces a labeled "API Options" section in help text
1940
- * ```
1941
- *
1942
- * @example
1943
- * ```typescript
1944
- * // Can be used with any parser, not just merge()
1945
- * const verboseGroup = group("Verbosity", object({
1946
- * verbose: option("-v", "--verbose"),
1947
- * quiet: option("-q", "--quiet")
1948
- * }));
1949
- * ```
1950
- *
1951
- * @template TValue The value type of the wrapped parser.
1952
- * @template TState The state type of the wrapped parser.
1953
- * @param label A descriptive label for this parser group, used for
1954
- * documentation and help text organization.
1955
- * @param parser The parser to wrap with a group label.
1956
- * @returns A new parser that behaves identically to the input parser
1957
- * but generates documentation within a labeled section.
1958
- * @since 0.4.0
1959
- */
1960
- function group(label, parser) {
2187
+ function group(label, parser, options = {}) {
1961
2188
  return {
1962
2189
  $mode: parser.$mode,
1963
2190
  $valueType: parser.$valueType,
1964
2191
  $stateType: parser.$stateType,
1965
2192
  priority: parser.priority,
1966
- usage: parser.usage,
2193
+ usage: applyHiddenToUsage(parser.usage, options.hidden),
1967
2194
  initialState: parser.initialState,
2195
+ ...typeof parser.shouldDeferCompletion === "function" ? { shouldDeferCompletion: parser.shouldDeferCompletion.bind(parser) } : {},
1968
2196
  parse: (context) => parser.parse(context),
1969
2197
  complete: (state) => parser.complete(state),
1970
- suggest: (context, prefix) => parser.suggest(context, prefix),
2198
+ suggest: (context, prefix) => {
2199
+ if (options.hidden === true) return dispatchIterableByMode(parser.$mode, function* () {}, async function* () {});
2200
+ return parser.suggest(context, prefix);
2201
+ },
1971
2202
  getDocFragments: (state, defaultValue) => {
1972
2203
  const { brief, description, footer, fragments } = parser.getDocFragments(state, defaultValue);
2204
+ const hiddenAwareFragments = applyHiddenToDocFragments(fragments, options.hidden);
1973
2205
  const allEntries = [];
1974
2206
  const titledSections = [];
1975
- for (const fragment of fragments) if (fragment.type === "entry") allEntries.push(fragment);
2207
+ for (const fragment of hiddenAwareFragments) if (fragment.type === "entry") allEntries.push(fragment);
1976
2208
  else if (fragment.type === "section") if (fragment.title) titledSections.push(fragment);
1977
2209
  else allEntries.push(...fragment.entries);
1978
2210
  const initialFragments = parser.getDocFragments({