@optique/core 1.0.0-dev.1611 → 1.0.0-dev.1658

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/parser.js CHANGED
@@ -1,14 +1,29 @@
1
1
  import { injectAnnotations, isInjectedAnnotationWrapper, unwrapInjectedAnnotationWrapper } from "./annotations.js";
2
2
  import { cloneMessage, message } from "./message.js";
3
- import { dispatchByMode } from "./mode-dispatch.js";
4
3
  import { cloneUsage, normalizeUsage } from "./usage.js";
5
4
  import { cloneDocEntry, isDocEntryHidden } from "./doc.js";
6
- import { DuplicateOptionError, concat, conditional, group, longestMatch, merge, object, or, tuple } from "./constructs.js";
5
+ import { dispatchByMode } from "./mode-dispatch.js";
6
+ import { createInputTrace } from "./input-trace.js";
7
7
  import { WithDefaultError, map, multiple, nonEmpty, optional, withDefault } from "./modifiers.js";
8
8
  import { argument, command, constant, fail, flag, option, passThrough } from "./primitives.js";
9
+ import { collectExplicitSourceValues, collectExplicitSourceValuesAsync, createDependencyRuntimeContext } from "./dependency-runtime.js";
10
+ import { DuplicateOptionError, concat, conditional, group, longestMatch, merge, object, or, tuple } from "./constructs.js";
9
11
 
10
12
  //#region src/parser.ts
11
13
  /**
14
+ * Internal marker for wrappers whose `{ hasCliValue: false }` states should
15
+ * be treated as unmatched dependency-source states during completion-time
16
+ * Phase 1.
17
+ *
18
+ * Wrappers like `bindEnv()` and `bindConfig()` opt in because their missing
19
+ * CLI states still carry enough fallback context to pre-complete exactly
20
+ * once. Wrappers like `prompt()` intentionally do not opt in because
21
+ * prompted values are not yet registered as dependency sources.
22
+ *
23
+ * @internal
24
+ */
25
+ const unmatchedNonCliDependencySourceStateMarker = Symbol.for("@optique/core/parser/unmatchedNonCliDependencySourceStateMarker");
26
+ /**
12
27
  * Creates a {@link ParserContext} from a {@link ParseFrame} and an
13
28
  * {@link ExecutionContext}. The returned object provides both structured
14
29
  * access (`frame`, `exec`) and flat access (`buffer`, `state`, etc.)
@@ -23,6 +38,7 @@ import { argument, command, constant, fail, flag, option, passThrough } from "./
23
38
  function createParserContext(frame, exec) {
24
39
  return {
25
40
  exec,
41
+ trace: exec.trace,
26
42
  buffer: frame.buffer,
27
43
  state: frame.state,
28
44
  optionsTerminated: frame.optionsTerminated,
@@ -53,6 +69,8 @@ function injectAnnotationsIntoState(state, options) {
53
69
  * successful or not. If successful, it contains the parsed value of
54
70
  * type `T`. If not, it contains an error message describing the
55
71
  * failure.
72
+ * @throws {TypeError} When a synchronous dependency source extractor returns a
73
+ * thenable during completion-time dependency seeding.
56
74
  * @since 0.9.0 Renamed from the original `parse` function which now delegates
57
75
  * to this for sync parsers.
58
76
  * @since 0.10.0 Added optional `options` parameter for annotations support.
@@ -63,7 +81,8 @@ function parseSync(parser, args, options) {
63
81
  const exec = {
64
82
  usage: parser.usage,
65
83
  phase: "parse",
66
- path: []
84
+ path: [],
85
+ trace: createInputTrace()
67
86
  };
68
87
  let context = createParserContext({
69
88
  buffer: args,
@@ -83,9 +102,13 @@ function parseSync(parser, args, options) {
83
102
  error: message`Unexpected option or argument: ${context.buffer[0]}.`
84
103
  };
85
104
  } while (context.buffer.length > 0);
105
+ const runtime = createDependencyRuntimeContext();
86
106
  const completeExec = {
87
107
  ...exec,
88
- phase: "complete"
108
+ phase: "complete",
109
+ dependencyRuntime: runtime,
110
+ dependencyRegistry: runtime.registry,
111
+ trace: context.exec?.trace ?? context.trace ?? exec.trace
89
112
  };
90
113
  const endResult = parser.complete(context.state, completeExec);
91
114
  return endResult.success ? {
@@ -130,7 +153,8 @@ async function parseAsync(parser, args, options) {
130
153
  const exec = {
131
154
  usage: parser.usage,
132
155
  phase: "parse",
133
- path: []
156
+ path: [],
157
+ trace: createInputTrace()
134
158
  };
135
159
  let context = createParserContext({
136
160
  buffer: args,
@@ -150,9 +174,13 @@ async function parseAsync(parser, args, options) {
150
174
  error: message`Unexpected option or argument: ${context.buffer[0]}.`
151
175
  };
152
176
  } while (context.buffer.length > 0);
177
+ const runtime = createDependencyRuntimeContext();
153
178
  const completeExec = {
154
179
  ...exec,
155
- phase: "complete"
180
+ phase: "complete",
181
+ dependencyRuntime: runtime,
182
+ dependencyRegistry: runtime.registry,
183
+ trace: context.exec?.trace ?? context.trace ?? exec.trace
156
184
  };
157
185
  const endResult = await parser.complete(context.state, completeExec);
158
186
  return endResult.success ? {
@@ -185,6 +213,8 @@ async function parseAsync(parser, args, options) {
185
213
  * @param options Optional {@link ParseOptions} for customizing parsing behavior.
186
214
  * @returns A {@link Result} object (for sync) or Promise thereof (for async)
187
215
  * indicating whether the parsing was successful or not.
216
+ * @throws {TypeError} When a synchronous dependency source extractor returns a
217
+ * thenable during completion-time dependency seeding.
188
218
  * @since 0.10.0 Added optional `options` parameter for annotations support.
189
219
  */
190
220
  function parse(parser, args, options) {
@@ -222,6 +252,8 @@ function parse(parser, args, options) {
222
252
  * const suggestions2 = suggestSync(parser, ["-v", "--format="]);
223
253
  * // Returns: [{ text: "--format=json" }, { text: "--format=yaml" }]
224
254
  * ```
255
+ * @throws {TypeError} When a synchronous dependency source extractor returns a
256
+ * thenable during suggestion seeding.
225
257
  * @since 0.6.0
226
258
  * @since 0.9.0 Renamed from the original `suggest` function.
227
259
  * @since 0.10.0 Added optional `options` parameter for annotations support.
@@ -237,16 +269,71 @@ function suggestSync(parser, args, options) {
237
269
  }, {
238
270
  usage: parser.usage,
239
271
  phase: "suggest",
240
- path: []
272
+ path: [],
273
+ trace: createInputTrace()
241
274
  });
242
275
  while (context.buffer.length > 0) {
243
276
  const result = parser.parse(context);
244
- if (!result.success) return Array.from(parser.suggest(context, prefix));
277
+ if (!result.success) return Array.from(parser.suggest(withSuggestRuntime(parser, context), prefix));
245
278
  const previousBuffer = context.buffer;
246
279
  context = result.next;
247
280
  if (isBufferUnchanged(previousBuffer, context.buffer)) return [];
248
281
  }
249
- return Array.from(parser.suggest(context, prefix));
282
+ return Array.from(parser.suggest(withSuggestRuntime(parser, context), prefix));
283
+ }
284
+ /**
285
+ * Creates a dependency runtime from the current parser state and returns
286
+ * a context with the populated registry. Used by top-level suggest
287
+ * functions to mirror the construct-owned model where suggest() receives
288
+ * a context with a dependency registry.
289
+ * @internal
290
+ */
291
+ function withSuggestRuntime(parser, context) {
292
+ const runtime = createDependencyRuntimeContext();
293
+ const nodes = getParserSuggestRuntimeNodes(parser, context.state, context.exec?.path ?? []);
294
+ if (nodes.length > 0) collectExplicitSourceValues(nodes, runtime);
295
+ return {
296
+ ...context,
297
+ dependencyRegistry: runtime.registry,
298
+ exec: context.exec ? {
299
+ ...context.exec,
300
+ dependencyRuntime: runtime,
301
+ dependencyRegistry: runtime.registry
302
+ } : void 0
303
+ };
304
+ }
305
+ async function withSuggestRuntimeAsync(parser, context) {
306
+ const runtime = createDependencyRuntimeContext();
307
+ const nodes = getParserSuggestRuntimeNodes(parser, context.state, context.exec?.path ?? []);
308
+ if (nodes.length > 0) await collectExplicitSourceValuesAsync(nodes, runtime);
309
+ return {
310
+ ...context,
311
+ dependencyRegistry: runtime.registry,
312
+ exec: context.exec ? {
313
+ ...context.exec,
314
+ dependencyRuntime: runtime,
315
+ dependencyRegistry: runtime.registry
316
+ } : void 0
317
+ };
318
+ }
319
+ /**
320
+ * Returns suggest-time runtime nodes for a parser, falling back to the
321
+ * parser's own source metadata when it does not expose a custom hook.
322
+ *
323
+ * @param parser The parser whose suggest-time runtime nodes should be resolved.
324
+ * @param state The current parser state.
325
+ * @param path The path to this parser within the parse tree.
326
+ * @returns The runtime nodes used to seed suggest-time dependency resolution.
327
+ * @internal
328
+ */
329
+ function getParserSuggestRuntimeNodes(parser, state, path) {
330
+ if (typeof parser.getSuggestRuntimeNodes === "function") return parser.getSuggestRuntimeNodes(state, path);
331
+ if (parser.dependencyMetadata?.source == null) return [];
332
+ return [{
333
+ path,
334
+ parser,
335
+ state
336
+ }];
250
337
  }
251
338
  /**
252
339
  * Generates command-line suggestions based on current parsing state.
@@ -279,21 +366,24 @@ async function suggestAsync(parser, args, options) {
279
366
  }, {
280
367
  usage: parser.usage,
281
368
  phase: "suggest",
282
- path: []
369
+ path: [],
370
+ trace: createInputTrace()
283
371
  });
284
372
  while (context.buffer.length > 0) {
285
373
  const result = await parser.parse(context);
286
374
  if (!result.success) {
375
+ const ctx$1 = await withSuggestRuntimeAsync(parser, context);
287
376
  const suggestions$1 = [];
288
- for await (const suggestion of parser.suggest(context, prefix)) suggestions$1.push(suggestion);
377
+ for await (const suggestion of parser.suggest(ctx$1, prefix)) suggestions$1.push(suggestion);
289
378
  return suggestions$1;
290
379
  }
291
380
  const previousBuffer = context.buffer;
292
381
  context = result.next;
293
382
  if (isBufferUnchanged(previousBuffer, context.buffer)) return [];
294
383
  }
384
+ const ctx = await withSuggestRuntimeAsync(parser, context);
295
385
  const suggestions = [];
296
- for await (const suggestion of parser.suggest(context, prefix)) suggestions.push(suggestion);
386
+ for await (const suggestion of parser.suggest(ctx, prefix)) suggestions.push(suggestion);
297
387
  return suggestions;
298
388
  }
299
389
  /**
@@ -316,6 +406,8 @@ async function suggestAsync(parser, args, options) {
316
406
  * @param options Optional {@link ParseOptions} for customizing parsing behavior.
317
407
  * @returns An array of {@link Suggestion} objects (for sync) or Promise thereof
318
408
  * (for async) containing completion candidates.
409
+ * @throws {TypeError} When a synchronous dependency source extractor returns a
410
+ * thenable during suggestion seeding.
319
411
  * @since 0.6.0
320
412
  * @since 0.10.0 Added optional `options` parameter for annotations support.
321
413
  */
@@ -530,4 +622,4 @@ function buildDocPage(parser, context, args) {
530
622
  }
531
623
 
532
624
  //#endregion
533
- export { DuplicateOptionError, WithDefaultError, argument, command, concat, conditional, constant, createParserContext, fail, flag, getDocPage, getDocPageAsync, getDocPageSync, group, longestMatch, map, merge, multiple, nonEmpty, object, option, optional, or, parse, parseAsync, parseSync, passThrough, suggest, suggestAsync, suggestSync, tuple, withDefault };
625
+ export { DuplicateOptionError, WithDefaultError, argument, command, concat, conditional, constant, createParserContext, fail, flag, getDocPage, getDocPageAsync, getDocPageSync, getParserSuggestRuntimeNodes, group, longestMatch, map, merge, multiple, nonEmpty, object, option, optional, or, parse, parseAsync, parseSync, passThrough, suggest, suggestAsync, suggestSync, tuple, unmatchedNonCliDependencySourceStateMarker, withDefault };