@optique/core 1.0.0-dev.1808 → 1.0.0-dev.1818

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.d.ts CHANGED
@@ -378,6 +378,16 @@ interface ExecutionContext {
378
378
  * resolution and completion.
379
379
  */
380
380
  readonly path: readonly PropertyKey[];
381
+ /**
382
+ * Matched command names in parse order.
383
+ *
384
+ * This is tracked separately from {@link path} because parse-tree paths also
385
+ * include object fields and other wrapper segments. Runners use it to
386
+ * recover subcommand help context from partial parses.
387
+ *
388
+ * @internal
389
+ */
390
+ readonly commandPath?: readonly string[];
381
391
  /**
382
392
  * Immutable trace of raw primitive inputs recorded during parsing.
383
393
  *
@@ -33,6 +33,7 @@ function mergeChildExec(parent, child) {
33
33
  trace: child.trace ?? parent.trace,
34
34
  dependencyRuntime: child.dependencyRuntime ?? parent.dependencyRuntime,
35
35
  dependencyRegistry: child.dependencyRegistry ?? parent.dependencyRegistry,
36
+ commandPath: child.commandPath ?? parent.commandPath,
36
37
  preCompletedByParser: child.preCompletedByParser ?? parent.preCompletedByParser,
37
38
  excludedSourceFields: child.excludedSourceFields ?? parent.excludedSourceFields
38
39
  };
@@ -1180,6 +1181,13 @@ function getCommandChildState(commandState, childState, parser) {
1180
1181
  function createCommandState(sourceState, state) {
1181
1182
  return require_annotations.annotateFreshArray(sourceState, state);
1182
1183
  }
1184
+ function appendCommandPath(exec, name) {
1185
+ if (exec == null) return void 0;
1186
+ return {
1187
+ ...exec,
1188
+ commandPath: [...exec.commandPath ?? [], name]
1189
+ };
1190
+ }
1183
1191
  function* suggestCommandSync(context, prefix, name, parser, options) {
1184
1192
  if (require_usage.isSuggestionHidden(options.hidden)) return;
1185
1193
  const state = normalizeCommandState(context.state);
@@ -1293,7 +1301,8 @@ function command(name, parser, options = {}) {
1293
1301
  next: {
1294
1302
  ...context,
1295
1303
  buffer: context.buffer.slice(1),
1296
- state: createCommandState(context.state, ["matched", name])
1304
+ state: createCommandState(context.state, ["matched", name]),
1305
+ ...context.exec != null ? { exec: appendCommandPath(context.exec, name) } : {}
1297
1306
  },
1298
1307
  consumed: context.buffer.slice(0, 1)
1299
1308
  };
@@ -33,6 +33,7 @@ function mergeChildExec(parent, child) {
33
33
  trace: child.trace ?? parent.trace,
34
34
  dependencyRuntime: child.dependencyRuntime ?? parent.dependencyRuntime,
35
35
  dependencyRegistry: child.dependencyRegistry ?? parent.dependencyRegistry,
36
+ commandPath: child.commandPath ?? parent.commandPath,
36
37
  preCompletedByParser: child.preCompletedByParser ?? parent.preCompletedByParser,
37
38
  excludedSourceFields: child.excludedSourceFields ?? parent.excludedSourceFields
38
39
  };
@@ -1180,6 +1181,13 @@ function getCommandChildState(commandState, childState, parser) {
1180
1181
  function createCommandState(sourceState, state) {
1181
1182
  return annotateFreshArray(sourceState, state);
1182
1183
  }
1184
+ function appendCommandPath(exec, name) {
1185
+ if (exec == null) return void 0;
1186
+ return {
1187
+ ...exec,
1188
+ commandPath: [...exec.commandPath ?? [], name]
1189
+ };
1190
+ }
1183
1191
  function* suggestCommandSync(context, prefix, name, parser, options) {
1184
1192
  if (isSuggestionHidden(options.hidden)) return;
1185
1193
  const state = normalizeCommandState(context.state);
@@ -1293,7 +1301,8 @@ function command(name, parser, options = {}) {
1293
1301
  next: {
1294
1302
  ...context,
1295
1303
  buffer: context.buffer.slice(1),
1296
- state: createCommandState(context.state, ["matched", name])
1304
+ state: createCommandState(context.state, ["matched", name]),
1305
+ ...context.exec != null ? { exec: appendCommandPath(context.exec, name) } : {}
1297
1306
  },
1298
1307
  consumed: context.buffer.slice(0, 1)
1299
1308
  };
package/dist/validate.cjs CHANGED
@@ -63,27 +63,22 @@ 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.
66
+ * Validates that there are no name collisions among active meta features
67
+ * (help, version, completion).
68
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.
69
+ * User parser names are accepted even when they overlap with meta names.
70
+ * Runtime parsing resolves those cases parser-first so ordinary parser data
71
+ * can shadow built-in meta behavior.
76
72
  *
77
73
  * Meta-vs-meta collisions are always checked in a unified namespace,
78
74
  * because a meta command named `"--help"` and a meta option named
79
75
  * `"--help"` both compete for the same token.
80
76
  *
81
- * @param userNames User parser names extracted at different scopes.
82
77
  * @param metaEntries Active meta feature entries annotated with their kind.
83
- * @throws {TypeError} If any collision or duplicate is detected.
78
+ * @throws {TypeError} If any meta/meta collision or duplicate is detected.
84
79
  * @since 1.0.0
85
80
  */
86
- function validateMetaNameCollisions(userNames, metaEntries) {
81
+ function validateMetaNameCollisions(metaEntries) {
87
82
  for (const [, label, names] of metaEntries) {
88
83
  const seen = /* @__PURE__ */ new Set();
89
84
  for (const name of names) {
@@ -112,33 +107,6 @@ function validateMetaNameCollisions(userNames, metaEntries) {
112
107
  }
113
108
  }
114
109
  }
115
- for (const [kind, label, names, prefixMatch] of metaEntries) for (const name of names) {
116
- if (kind === "command") {
117
- if (userNames.leadingNames.has(name)) if (userNames.allOptions.has(name)) throw new TypeError(`User-defined option "${name}" conflicts with the built-in ${label}.`);
118
- else if (userNames.allCommands.has(name)) throw new TypeError(`User-defined command "${name}" conflicts with the built-in ${label}.`);
119
- else if (userNames.allLiterals.has(name)) throw new TypeError(`Literal value "${name}" conflicts with the built-in ${label}.`);
120
- else throw new TypeError(`User-defined name "${name}" conflicts with the built-in ${label}.`);
121
- } else {
122
- if (userNames.allOptions.has(name)) throw new TypeError(`User-defined option "${name}" conflicts with the built-in ${label}.`);
123
- if (userNames.allCommands.has(name)) throw new TypeError(`User-defined command "${name}" conflicts with the built-in ${label}.`);
124
- if (userNames.allLiterals.has(name)) throw new TypeError(`Literal value "${name}" conflicts with the built-in ${label}.`);
125
- }
126
- if (prefixMatch) {
127
- const prefix = name + "=";
128
- const checkSets = kind === "command" ? [userNames.leadingNames] : [
129
- userNames.allOptions,
130
- userNames.allCommands,
131
- userNames.allLiterals
132
- ];
133
- for (const nameSet of checkSets) for (const userName of nameSet) {
134
- if (!userName.startsWith(prefix)) continue;
135
- if (userNames.allOptions.has(userName)) throw new TypeError(`User-defined option "${userName}" conflicts with the built-in ${label} (prefix "${prefix}").`);
136
- else if (userNames.allCommands.has(userName)) throw new TypeError(`User-defined command "${userName}" conflicts with the built-in ${label} (prefix "${prefix}").`);
137
- else if (userNames.allLiterals.has(userName)) throw new TypeError(`Literal value "${userName}" conflicts with the built-in ${label} (prefix "${prefix}").`);
138
- else throw new TypeError(`User-defined name "${userName}" conflicts with the built-in ${label} (prefix "${prefix}").`);
139
- }
140
- }
141
- }
142
110
  }
143
111
  function capitalize(s) {
144
112
  return s.charAt(0).toUpperCase() + s.slice(1);
package/dist/validate.js CHANGED
@@ -62,27 +62,22 @@ 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.
65
+ * Validates that there are no name collisions among active meta features
66
+ * (help, version, completion).
67
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.
68
+ * User parser names are accepted even when they overlap with meta names.
69
+ * Runtime parsing resolves those cases parser-first so ordinary parser data
70
+ * can shadow built-in meta behavior.
75
71
  *
76
72
  * Meta-vs-meta collisions are always checked in a unified namespace,
77
73
  * because a meta command named `"--help"` and a meta option named
78
74
  * `"--help"` both compete for the same token.
79
75
  *
80
- * @param userNames User parser names extracted at different scopes.
81
76
  * @param metaEntries Active meta feature entries annotated with their kind.
82
- * @throws {TypeError} If any collision or duplicate is detected.
77
+ * @throws {TypeError} If any meta/meta collision or duplicate is detected.
83
78
  * @since 1.0.0
84
79
  */
85
- function validateMetaNameCollisions(userNames, metaEntries) {
80
+ function validateMetaNameCollisions(metaEntries) {
86
81
  for (const [, label, names] of metaEntries) {
87
82
  const seen = /* @__PURE__ */ new Set();
88
83
  for (const name of names) {
@@ -111,33 +106,6 @@ function validateMetaNameCollisions(userNames, metaEntries) {
111
106
  }
112
107
  }
113
108
  }
114
- for (const [kind, label, names, prefixMatch] of metaEntries) for (const name of names) {
115
- if (kind === "command") {
116
- if (userNames.leadingNames.has(name)) if (userNames.allOptions.has(name)) throw new TypeError(`User-defined option "${name}" conflicts with the built-in ${label}.`);
117
- else if (userNames.allCommands.has(name)) throw new TypeError(`User-defined command "${name}" conflicts with the built-in ${label}.`);
118
- else if (userNames.allLiterals.has(name)) throw new TypeError(`Literal value "${name}" conflicts with the built-in ${label}.`);
119
- else throw new TypeError(`User-defined name "${name}" conflicts with the built-in ${label}.`);
120
- } else {
121
- if (userNames.allOptions.has(name)) throw new TypeError(`User-defined option "${name}" conflicts with the built-in ${label}.`);
122
- if (userNames.allCommands.has(name)) throw new TypeError(`User-defined command "${name}" conflicts with the built-in ${label}.`);
123
- if (userNames.allLiterals.has(name)) throw new TypeError(`Literal value "${name}" conflicts with the built-in ${label}.`);
124
- }
125
- if (prefixMatch) {
126
- const prefix = name + "=";
127
- const checkSets = kind === "command" ? [userNames.leadingNames] : [
128
- userNames.allOptions,
129
- userNames.allCommands,
130
- userNames.allLiterals
131
- ];
132
- for (const nameSet of checkSets) for (const userName of nameSet) {
133
- if (!userName.startsWith(prefix)) continue;
134
- if (userNames.allOptions.has(userName)) throw new TypeError(`User-defined option "${userName}" conflicts with the built-in ${label} (prefix "${prefix}").`);
135
- else if (userNames.allCommands.has(userName)) throw new TypeError(`User-defined command "${userName}" conflicts with the built-in ${label} (prefix "${prefix}").`);
136
- else if (userNames.allLiterals.has(userName)) throw new TypeError(`Literal value "${userName}" conflicts with the built-in ${label} (prefix "${prefix}").`);
137
- else throw new TypeError(`User-defined name "${userName}" conflicts with the built-in ${label} (prefix "${prefix}").`);
138
- }
139
- }
140
- }
141
109
  }
142
110
  function capitalize(s) {
143
111
  return s.charAt(0).toUpperCase() + s.slice(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optique/core",
3
- "version": "1.0.0-dev.1808+e840dccd",
3
+ "version": "1.0.0-dev.1818+1aa43bd4",
4
4
  "description": "Type-safe combinatorial command-line interface parser",
5
5
  "keywords": [
6
6
  "CLI",