@optique/core 1.1.0-dev.2087 → 1.1.0-dev.2146

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 (74) hide show
  1. package/dist/annotation-state.cjs +26 -26
  2. package/dist/annotation-state.d.cts +133 -1
  3. package/dist/annotation-state.d.ts +133 -1
  4. package/dist/annotations.cjs +2 -2
  5. package/dist/constructs.cjs +873 -73
  6. package/dist/constructs.d.cts +72 -1
  7. package/dist/constructs.d.ts +72 -1
  8. package/dist/constructs.js +808 -9
  9. package/dist/dependency-metadata.cjs +12 -12
  10. package/dist/dependency-metadata.d.cts +34 -3
  11. package/dist/dependency-metadata.d.ts +34 -3
  12. package/dist/dependency-runtime.cjs +37 -13
  13. package/dist/dependency-runtime.d.cts +197 -2
  14. package/dist/dependency-runtime.d.ts +197 -2
  15. package/dist/dependency-runtime.js +22 -1
  16. package/dist/dependency.cjs +7 -7
  17. package/dist/displaywidth.d.cts +12 -0
  18. package/dist/displaywidth.d.ts +12 -0
  19. package/dist/doc.cjs +3 -0
  20. package/dist/doc.js +3 -0
  21. package/dist/execution-context.d.cts +23 -0
  22. package/dist/execution-context.d.ts +23 -0
  23. package/dist/extension.cjs +14 -14
  24. package/dist/facade.cjs +49 -37
  25. package/dist/facade.js +34 -22
  26. package/dist/index.cjs +23 -21
  27. package/dist/index.d.cts +3 -3
  28. package/dist/index.d.ts +3 -3
  29. package/dist/index.js +4 -4
  30. package/dist/input-trace.d.cts +2 -1
  31. package/dist/input-trace.d.ts +2 -1
  32. package/dist/internal/annotations.cjs +3 -0
  33. package/dist/internal/annotations.d.cts +47 -5
  34. package/dist/internal/annotations.d.ts +47 -5
  35. package/dist/internal/annotations.js +1 -1
  36. package/dist/internal/command-alias.cjs +16 -0
  37. package/dist/internal/command-alias.js +14 -0
  38. package/dist/internal/dependency.cjs +131 -0
  39. package/dist/internal/dependency.d.cts +311 -2
  40. package/dist/internal/dependency.d.ts +311 -2
  41. package/dist/internal/dependency.js +119 -1
  42. package/dist/internal/parser.cjs +108 -23
  43. package/dist/internal/parser.d.cts +58 -3
  44. package/dist/internal/parser.d.ts +58 -3
  45. package/dist/internal/parser.js +101 -16
  46. package/dist/modifiers.cjs +74 -44
  47. package/dist/modifiers.js +34 -4
  48. package/dist/parser.cjs +11 -11
  49. package/dist/phase2-seed.cjs +2 -2
  50. package/dist/phase2-seed.d.cts +50 -0
  51. package/dist/phase2-seed.d.ts +50 -0
  52. package/dist/primitives.cjs +104 -33
  53. package/dist/primitives.d.cts +10 -0
  54. package/dist/primitives.d.ts +10 -0
  55. package/dist/primitives.js +84 -13
  56. package/dist/suggestion.cjs +72 -2
  57. package/dist/suggestion.d.cts +188 -0
  58. package/dist/suggestion.d.ts +188 -0
  59. package/dist/suggestion.js +71 -3
  60. package/dist/usage-internals.cjs +14 -6
  61. package/dist/usage-internals.js +14 -6
  62. package/dist/usage.cjs +33 -8
  63. package/dist/usage.d.cts +31 -0
  64. package/dist/usage.d.ts +31 -0
  65. package/dist/usage.js +33 -8
  66. package/dist/validate.cjs +1 -0
  67. package/dist/validate.d.cts +99 -0
  68. package/dist/validate.d.ts +99 -0
  69. package/dist/validate.js +1 -1
  70. package/dist/valueparser.cjs +333 -79
  71. package/dist/valueparser.d.cts +197 -1
  72. package/dist/valueparser.d.ts +197 -1
  73. package/dist/valueparser.js +334 -81
  74. package/package.json +19 -4
@@ -6,9 +6,10 @@ import { dispatchByMode, dispatchIterableByMode, wrapForMode } from "./internal/
6
6
  import { getDefaultValuesFunction, getDependencyIds, getSnapshottedDefaultDependencyValues, isDerivedValueParser, suggestWithDependency } from "./internal/dependency.js";
7
7
  import { replayDerivedParser, replayDerivedParserAsync } from "./dependency-runtime.js";
8
8
  import { getWrappedChildParseState, getWrappedChildState, isAnnotationWrappedInitialState, normalizeInjectedAnnotationState } from "./annotation-state.js";
9
+ import { hiddenCommandAliasesKey } from "./internal/command-alias.js";
9
10
  import { mergeChildExec, withChildContext, withChildExecPath } from "./execution-context.js";
10
11
  import { completeOrExtractPhase2Seed, extractPhase2SeedKey } from "./phase2-seed.js";
11
- import { DEFAULT_FIND_SIMILAR_OPTIONS, createErrorWithSuggestions, createSuggestionMessage, findSimilar } from "./suggestion.js";
12
+ import { DEFAULT_FIND_SIMILAR_OPTIONS, createErrorWithSuggestions, createSuggestionMessage, expandCommandAliasSuggestions, findSimilar } from "./suggestion.js";
12
13
  import { extractLeadingCommandNames } from "./usage-internals.js";
13
14
  import { extractDependencyMetadata } from "./dependency-metadata.js";
14
15
  import { isValueParser } from "./valueparser.js";
@@ -23,6 +24,9 @@ function hasParsedOptionValue(state, valueParser) {
23
24
  if (valueParser != null) return state != null && typeof state === "object" && "success" in state && typeof state.success === "boolean";
24
25
  return state != null && "success" in state && state.success && state.value === true;
25
26
  }
27
+ function isTerminalValueState(state) {
28
+ return state != null && typeof state === "object" && "success" in state && typeof state.success === "boolean";
29
+ }
26
30
  /**
27
31
  * Helper function to create the stored state for an option or argument value.
28
32
  *
@@ -117,6 +121,9 @@ function constant(value) {
117
121
  leadingNames: EMPTY_LEADING_NAMES,
118
122
  acceptingAnyToken: false,
119
123
  initialState: value,
124
+ canSkip() {
125
+ return true;
126
+ },
120
127
  parse(context) {
121
128
  return {
122
129
  success: true,
@@ -174,6 +181,9 @@ function fail() {
174
181
  leadingNames: EMPTY_LEADING_NAMES,
175
182
  acceptingAnyToken: false,
176
183
  initialState: void 0,
184
+ canSkip() {
185
+ return false;
186
+ },
177
187
  parse(_context) {
178
188
  return {
179
189
  success: false,
@@ -431,6 +441,9 @@ function option(...args) {
431
441
  success: true,
432
442
  value: false
433
443
  } : void 0,
444
+ canSkip(state) {
445
+ return valueParser == null || isTerminalValueState(state);
446
+ },
434
447
  parse(context) {
435
448
  if (context.optionsTerminated) return {
436
449
  success: false,
@@ -686,6 +699,7 @@ function option(...args) {
686
699
  };
687
700
  Object.defineProperty(result, "validateValue", {
688
701
  value(v) {
702
+ if (typeof vp.validate === "function") return wrapForMode(mode, wrapParseResult(vp.validate(v)));
689
703
  let stringified;
690
704
  try {
691
705
  stringified = vp.format(v);
@@ -782,6 +796,9 @@ function flag(...args) {
782
796
  leadingNames: new Set(optionNames$1),
783
797
  acceptingAnyToken: false,
784
798
  initialState: void 0,
799
+ canSkip(state) {
800
+ return isTerminalValueState(state);
801
+ },
785
802
  parse(context) {
786
803
  if (context.optionsTerminated) return {
787
804
  success: false,
@@ -1013,6 +1030,9 @@ function negatableFlag(names, options = {}) {
1013
1030
  leadingNames: new Set(optionNames$1),
1014
1031
  acceptingAnyToken: false,
1015
1032
  initialState: void 0,
1033
+ canSkip(state) {
1034
+ return state != null;
1035
+ },
1016
1036
  parse(context) {
1017
1037
  if (context.optionsTerminated) return {
1018
1038
  success: false,
@@ -1177,6 +1197,9 @@ function argument(valueParser, options = {}) {
1177
1197
  leadingNames: EMPTY_LEADING_NAMES,
1178
1198
  acceptingAnyToken: true,
1179
1199
  initialState: void 0,
1200
+ canSkip(state) {
1201
+ return isTerminalValueState(state);
1202
+ },
1180
1203
  parse(context) {
1181
1204
  const localState = normalizeInjectedAnnotationState(context.state);
1182
1205
  if (context.buffer.length < 1) return {
@@ -1312,6 +1335,7 @@ function argument(valueParser, options = {}) {
1312
1335
  };
1313
1336
  Object.defineProperty(result, "validateValue", {
1314
1337
  value(v) {
1338
+ if (typeof vp.validate === "function") return wrapForMode(vpMode, wrapParseResult(vp.validate(v)));
1315
1339
  let stringified;
1316
1340
  try {
1317
1341
  stringified = vp.format(v);
@@ -1368,25 +1392,56 @@ function appendCommandPath(exec, name) {
1368
1392
  commandPath: [...exec.commandPath ?? [], name]
1369
1393
  };
1370
1394
  }
1371
- function* suggestCommandSync(context, prefix, name, parser, options) {
1395
+ function getCommandNames(name, options) {
1396
+ return [
1397
+ name,
1398
+ ...getVisibleCommandAliases(options),
1399
+ ...getHiddenCommandAliases(options)
1400
+ ];
1401
+ }
1402
+ function getVisibleCommandAliases(options) {
1403
+ const aliases = options.aliases;
1404
+ if (aliases == null) return [];
1405
+ if (!isNonEmptyStringArray(aliases)) throw new TypeError("Command aliases must be a non-empty array of strings.");
1406
+ return aliases;
1407
+ }
1408
+ function getHiddenCommandAliases(options) {
1409
+ const hiddenAliases = options[hiddenCommandAliasesKey];
1410
+ if (hiddenAliases == null) return [];
1411
+ if (!isNonEmptyStringArray(hiddenAliases)) throw new TypeError("Hidden command aliases must be a non-empty array of strings.");
1412
+ return hiddenAliases;
1413
+ }
1414
+ function isNonEmptyStringArray(value) {
1415
+ if (!Array.isArray(value) || value.length === 0) return false;
1416
+ for (let i = 0; i < value.length; i++) if (typeof value[i] !== "string") return false;
1417
+ return true;
1418
+ }
1419
+ function validateUniqueCommandNames(names) {
1420
+ const seen = /* @__PURE__ */ new Set();
1421
+ for (const name of names) {
1422
+ if (seen.has(name)) throw new TypeError(`Command has a duplicate name: "${name}".`);
1423
+ seen.add(name);
1424
+ }
1425
+ }
1426
+ function* suggestCommandSync(context, prefix, name, aliases, parser, options) {
1372
1427
  if (isSuggestionHidden(options.hidden)) return;
1373
1428
  const state = normalizeCommandState(context.state);
1374
1429
  if (state === void 0) {
1375
- if (name.startsWith(prefix)) yield {
1430
+ for (const commandName of [name, ...aliases]) if (commandName.startsWith(prefix)) yield {
1376
1431
  kind: "literal",
1377
- text: name,
1432
+ text: commandName,
1378
1433
  ...options.description && { description: options.description }
1379
1434
  };
1380
1435
  } else if (state[0] === "matched") yield* parser.suggest(withChildContext(context, name, getCommandChildState(context.state, parser.initialState, parser), parser.usage), prefix);
1381
1436
  else if (state[0] === "parsing") yield* parser.suggest(withChildContext(context, name, getCommandChildState(context.state, state[1], parser), parser.usage), prefix);
1382
1437
  }
1383
- async function* suggestCommandAsync(context, prefix, name, parser, options) {
1438
+ async function* suggestCommandAsync(context, prefix, name, aliases, parser, options) {
1384
1439
  if (isSuggestionHidden(options.hidden)) return;
1385
1440
  const state = normalizeCommandState(context.state);
1386
1441
  if (state === void 0) {
1387
- if (name.startsWith(prefix)) yield {
1442
+ for (const commandName of [name, ...aliases]) if (commandName.startsWith(prefix)) yield {
1388
1443
  kind: "literal",
1389
- text: name,
1444
+ text: commandName,
1390
1445
  ...options.description && { description: options.description }
1391
1446
  };
1392
1447
  } else if (state[0] === "matched") {
@@ -1414,7 +1469,11 @@ async function* suggestCommandAsync(context, prefix, name, parser, options) {
1414
1469
  * embedded whitespace, or contains control characters.
1415
1470
  */
1416
1471
  function command(name, parser, options = {}) {
1417
- validateCommandNames([name], "Command");
1472
+ const commandNames = getCommandNames(name, options);
1473
+ const aliases = getVisibleCommandAliases(options);
1474
+ const hiddenAliases = getHiddenCommandAliases(options);
1475
+ validateCommandNames(commandNames, "Command");
1476
+ validateUniqueCommandNames(commandNames);
1418
1477
  const isAsync = parser.mode === "async";
1419
1478
  const syncInnerParser = parser;
1420
1479
  const asyncInnerParser = parser;
@@ -1427,12 +1486,20 @@ function command(name, parser, options = {}) {
1427
1486
  usage: [{
1428
1487
  type: "command",
1429
1488
  name,
1489
+ ...aliases.length > 0 && { aliases },
1490
+ ...hiddenAliases.length > 0 && { hiddenAliases },
1430
1491
  ...options.usageLine != null && { usageLine: options.usageLine },
1431
1492
  ...options.hidden != null && { hidden: options.hidden }
1432
1493
  }, ...parser.usage],
1433
- leadingNames: new Set([name]),
1494
+ leadingNames: new Set(commandNames),
1434
1495
  acceptingAnyToken: false,
1435
1496
  initialState: void 0,
1497
+ canSkip(state, exec) {
1498
+ const normalizedState = normalizeCommandState(state);
1499
+ if (normalizedState === void 0) return false;
1500
+ if (normalizedState[0] === "matched") return parser.canSkip?.(getCommandChildState(state, parser.initialState, parser), withChildExecPath(exec, name)) === true;
1501
+ return parser.canSkip?.(getCommandChildState(state, normalizedState[1], parser), withChildExecPath(exec, name)) === true;
1502
+ },
1436
1503
  getSuggestRuntimeNodes(state, path) {
1437
1504
  const normalizedState = normalizeCommandState(state);
1438
1505
  if (normalizedState === void 0) return [];
@@ -1447,10 +1514,11 @@ function command(name, parser, options = {}) {
1447
1514
  parse(context) {
1448
1515
  const state = normalizeCommandState(context.state);
1449
1516
  if (state === void 0) {
1450
- if (context.buffer.length < 1 || context.buffer[0] !== name) {
1517
+ if (context.buffer.length < 1 || !commandNames.includes(context.buffer[0])) {
1451
1518
  const actual = context.buffer.length > 0 ? context.buffer[0] : null;
1452
1519
  const leadingCmds = extractLeadingCommandNames(context.usage);
1453
- const suggestions = actual ? findSimilar(actual, leadingCmds, DEFAULT_FIND_SIMILAR_OPTIONS) : [];
1520
+ const rawSuggestions = actual ? findSimilar(actual, leadingCmds, DEFAULT_FIND_SIMILAR_OPTIONS) : [];
1521
+ const suggestions = expandCommandAliasSuggestions(context.usage, rawSuggestions);
1454
1522
  if (options.errors?.notMatched) {
1455
1523
  const errorMessage = options.errors.notMatched;
1456
1524
  return {
@@ -1581,8 +1649,8 @@ function command(name, parser, options = {}) {
1581
1649
  return wrapForMode(parser.mode, null);
1582
1650
  },
1583
1651
  suggest(context, prefix) {
1584
- if (isAsync) return suggestCommandAsync(context, prefix, name, parser, options);
1585
- return suggestCommandSync(context, prefix, name, parser, options);
1652
+ if (isAsync) return suggestCommandAsync(context, prefix, name, aliases, parser, options);
1653
+ return suggestCommandSync(context, prefix, name, aliases, parser, options);
1586
1654
  },
1587
1655
  getDocFragments(state, defaultValue) {
1588
1656
  const commandState = state.kind === "available" ? normalizeCommandState(state.state) : void 0;
@@ -1696,6 +1764,9 @@ function passThrough(options = {}) {
1696
1764
  leadingNames: EMPTY_LEADING_NAMES,
1697
1765
  acceptingAnyToken: false,
1698
1766
  initialState: [],
1767
+ canSkip() {
1768
+ return true;
1769
+ },
1699
1770
  parse(context) {
1700
1771
  if (context.buffer.length < 1) return {
1701
1772
  success: false,
@@ -141,6 +141,73 @@ function createSuggestionMessage(suggestions) {
141
141
  return messageParts;
142
142
  }
143
143
  /**
144
+ * Expands command alias suggestions so an alias typo can point at both the
145
+ * canonical command and the alias that matched.
146
+ *
147
+ * @param usage Usage terms that define command aliases.
148
+ * @param suggestions Candidate suggestions returned by {@link findSimilar}.
149
+ * @returns Suggestions with alias hits expanded to canonical name + alias.
150
+ * @internal
151
+ */
152
+ function expandCommandAliasSuggestions(usage, suggestions) {
153
+ if (suggestions.length === 0) return suggestions;
154
+ const commandAliasTargets = collectCommandAliasTargets(usage);
155
+ const expanded = [];
156
+ const seen = /* @__PURE__ */ new Set();
157
+ for (const suggestion of suggestions) {
158
+ const targets = commandAliasTargets.get(suggestion) ?? [suggestion];
159
+ for (const target of targets) {
160
+ if (seen.has(target)) continue;
161
+ seen.add(target);
162
+ expanded.push(target);
163
+ }
164
+ }
165
+ return expanded;
166
+ }
167
+ function collectCommandAliasTargets(usage) {
168
+ const targets = /* @__PURE__ */ new Map();
169
+ function traverse(terms) {
170
+ if (!terms || !Array.isArray(terms)) return true;
171
+ for (const term of terms) {
172
+ if (term.type === "option") continue;
173
+ if (term.type === "argument") return false;
174
+ if (term.type === "command") {
175
+ if (require_usage.isSuggestionHidden(term.hidden)) return false;
176
+ if (!targets.has(term.name)) targets.set(term.name, [term.name]);
177
+ for (const alias of term.aliases ?? []) if (!targets.has(alias)) targets.set(alias, [term.name, alias]);
178
+ for (const alias of term.hiddenAliases ?? []) if (!targets.has(alias)) targets.set(alias, [term.name]);
179
+ return false;
180
+ }
181
+ if (term.type === "optional") {
182
+ traverse(term.terms);
183
+ continue;
184
+ }
185
+ if (term.type === "multiple") {
186
+ const termsSkippable = traverse(term.terms);
187
+ if (term.min === 0 || termsSkippable) continue;
188
+ return false;
189
+ }
190
+ if (term.type === "sequence") {
191
+ if (traverse(term.terms)) continue;
192
+ return false;
193
+ }
194
+ if (term.type === "exclusive") {
195
+ let anySkippable = false;
196
+ for (const branch of term.terms) {
197
+ const branchSkippable = traverse(branch);
198
+ anySkippable = anySkippable || branchSkippable;
199
+ }
200
+ if (anySkippable) continue;
201
+ return false;
202
+ }
203
+ return false;
204
+ }
205
+ return true;
206
+ }
207
+ traverse(usage);
208
+ return targets;
209
+ }
210
+ /**
144
211
  * Creates an error message with suggestions for similar options or commands.
145
212
  *
146
213
  * This is a convenience function that combines the functionality of
@@ -175,7 +242,8 @@ function createErrorWithSuggestions(baseError, invalidInput, usage, type = "both
175
242
  if (type === "option" || type === "both") for (const name of require_usage.extractOptionNames(usage)) candidates.add(name);
176
243
  if (type === "command" || type === "both") for (const name of require_usage.extractCommandNames(usage)) candidates.add(name);
177
244
  const suggestions = findSimilar(invalidInput, candidates, DEFAULT_FIND_SIMILAR_OPTIONS);
178
- const suggestionMsg = customFormatter ? customFormatter(suggestions) : createSuggestionMessage(suggestions);
245
+ const displaySuggestions = type === "option" ? suggestions : expandCommandAliasSuggestions(usage, suggestions);
246
+ const suggestionMsg = customFormatter ? customFormatter(displaySuggestions) : createSuggestionMessage(displaySuggestions);
179
247
  return suggestionMsg.length > 0 ? [
180
248
  ...baseError,
181
249
  require_message.text("\n\n"),
@@ -248,4 +316,6 @@ exports.DEFAULT_FIND_SIMILAR_OPTIONS = DEFAULT_FIND_SIMILAR_OPTIONS;
248
316
  exports.createErrorWithSuggestions = createErrorWithSuggestions;
249
317
  exports.createSuggestionMessage = createSuggestionMessage;
250
318
  exports.deduplicateSuggestions = deduplicateSuggestions;
251
- exports.findSimilar = findSimilar;
319
+ exports.expandCommandAliasSuggestions = expandCommandAliasSuggestions;
320
+ exports.findSimilar = findSimilar;
321
+ exports.levenshteinDistance = levenshteinDistance;
@@ -0,0 +1,188 @@
1
+ import { Message } from "./message.cjs";
2
+ import { Usage } from "./usage.cjs";
3
+ import { Suggestion } from "./internal/parser.cjs";
4
+
5
+ //#region src/suggestion.d.ts
6
+
7
+ /**
8
+ * Calculates the Levenshtein distance between two strings.
9
+ *
10
+ * The Levenshtein distance is the minimum number of single-character edits
11
+ * (insertions, deletions, or substitutions) required to transform one string
12
+ * into another.
13
+ *
14
+ * @param source The source string
15
+ * @param target The target string
16
+ * @returns The edit distance (number of insertions, deletions, substitutions)
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * levenshteinDistance("kitten", "sitting"); // returns 3
21
+ * levenshteinDistance("--verbos", "--verbose"); // returns 1
22
+ * levenshteinDistance("hello", "hello"); // returns 0
23
+ * ```
24
+ */
25
+ declare function levenshteinDistance(source: string, target: string): number;
26
+ /**
27
+ * Options for finding similar strings.
28
+ */
29
+ interface FindSimilarOptions {
30
+ /**
31
+ * Maximum edit distance to consider a match.
32
+ * Strings with a distance greater than this value will not be suggested.
33
+ * @default 3
34
+ */
35
+ readonly maxDistance?: number;
36
+ /**
37
+ * Maximum distance ratio (distance / input length).
38
+ * Prevents suggesting long strings for very short inputs.
39
+ * For example, with maxDistanceRatio=0.5, an input of length 2
40
+ * will only suggest strings within distance 1.
41
+ * @default 0.5
42
+ */
43
+ readonly maxDistanceRatio?: number;
44
+ /**
45
+ * Maximum number of suggestions to return.
46
+ * @default 3
47
+ */
48
+ readonly maxSuggestions?: number;
49
+ /**
50
+ * Case-sensitive comparison.
51
+ * If false, strings are compared case-insensitively.
52
+ * @default false
53
+ */
54
+ readonly caseSensitive?: boolean;
55
+ }
56
+ /**
57
+ * Default options for finding similar strings.
58
+ * These values are optimized for command-line option/command name suggestions.
59
+ *
60
+ * @since 0.7.0
61
+ */
62
+ declare const DEFAULT_FIND_SIMILAR_OPTIONS: Required<FindSimilarOptions>;
63
+ /**
64
+ * Finds similar strings from a list of candidates.
65
+ *
66
+ * This function uses Levenshtein distance to find strings that are similar
67
+ * to the input string. Results are sorted by similarity (most similar first).
68
+ *
69
+ * @param input The input string to find matches for
70
+ * @param candidates List of candidate strings to compare against
71
+ * @param options Configuration options
72
+ * @returns Array of similar strings, sorted by similarity (most similar first)
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * const candidates = ["--verbose", "--version", "--verify", "--help"];
77
+ * findSimilar("--verbos", candidates);
78
+ * // returns ["--verbose"]
79
+ *
80
+ * findSimilar("--ver", candidates, { maxDistance: 5 });
81
+ * // returns ["--verify", "--version", "--verbose"]
82
+ *
83
+ * findSimilar("--xyz", candidates);
84
+ * // returns [] (no similar matches)
85
+ * ```
86
+ */
87
+ declare function findSimilar(input: string, candidates: Iterable<string>, options?: FindSimilarOptions): string[];
88
+ /**
89
+ * Creates a suggestion message for a mismatched option/command.
90
+ *
91
+ * This function formats suggestions in a user-friendly way:
92
+ * - No suggestions: returns empty message
93
+ * - One suggestion: "Did you mean `option`?"
94
+ * - Multiple suggestions: "Did you mean one of these?\n option1\n option2"
95
+ *
96
+ * @param suggestions List of similar valid options/commands
97
+ * @returns A Message array with suggestion text
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * createSuggestionMessage(["--verbose", "--version"]);
102
+ * // returns message parts for:
103
+ * // "Did you mean one of these?
104
+ * // --verbose
105
+ * // --version"
106
+ *
107
+ * createSuggestionMessage(["--verbose"]);
108
+ * // returns message parts for:
109
+ * // "Did you mean `--verbose`?"
110
+ *
111
+ * createSuggestionMessage([]);
112
+ * // returns []
113
+ * ```
114
+ */
115
+ declare function createSuggestionMessage(suggestions: readonly string[]): Message;
116
+ /**
117
+ * Expands command alias suggestions so an alias typo can point at both the
118
+ * canonical command and the alias that matched.
119
+ *
120
+ * @param usage Usage terms that define command aliases.
121
+ * @param suggestions Candidate suggestions returned by {@link findSimilar}.
122
+ * @returns Suggestions with alias hits expanded to canonical name + alias.
123
+ * @internal
124
+ */
125
+ declare function expandCommandAliasSuggestions(usage: Usage, suggestions: readonly string[]): readonly string[];
126
+ /**
127
+ * Creates an error message with suggestions for similar options or commands.
128
+ *
129
+ * This is a convenience function that combines the functionality of
130
+ * `findSimilar()` and `createSuggestionMessage()` to generate user-friendly
131
+ * error messages with "Did you mean?" suggestions.
132
+ *
133
+ * @param baseError The base error message to display
134
+ * @param invalidInput The invalid option or command name that the user typed
135
+ * @param usage The usage information to extract available options/commands from
136
+ * @param type What type of names to suggest ("option", "command", or "both")
137
+ * @param customFormatter Optional custom function to format suggestions instead
138
+ * of using the default "Did you mean?" formatting
139
+ * @returns A message combining the base error with suggestions, or just the
140
+ * base error if no similar names are found
141
+ *
142
+ * @example
143
+ * ```typescript
144
+ * const baseError = message`No matched option for ${optionName("--verbos")}.`;
145
+ * const error = createErrorWithSuggestions(
146
+ * baseError,
147
+ * "--verbos",
148
+ * context.usage,
149
+ * "option"
150
+ * );
151
+ * // Returns: "No matched option for `--verbos`.\nDid you mean `--verbose`?"
152
+ * ```
153
+ *
154
+ * @since 0.7.0
155
+ */
156
+ declare function createErrorWithSuggestions(baseError: Message, invalidInput: string, usage: Usage, type?: "option" | "command" | "both", customFormatter?: (suggestions: readonly string[]) => Message): Message;
157
+ /**
158
+ * Removes duplicate suggestions from an array while preserving order.
159
+ *
160
+ * Suggestions are considered duplicates if they have the same key:
161
+ * - Literal suggestions: same text
162
+ * - File suggestions: same type, extensions, and pattern
163
+ *
164
+ * The first occurrence of each unique suggestion is kept. For file
165
+ * suggestions, `includeHidden` is merged across duplicates: if any
166
+ * duplicate has `includeHidden: true`, the kept suggestion is upgraded
167
+ * to `includeHidden: true` because it is a superset of the non-hidden
168
+ * variant.
169
+ *
170
+ * @param suggestions Array of suggestions that may contain duplicates
171
+ * @returns A new array with duplicates removed, preserving order of first occurrences
172
+ *
173
+ * @example
174
+ * ```typescript
175
+ * const suggestions = [
176
+ * { kind: "literal", text: "--verbose" },
177
+ * { kind: "literal", text: "--help" },
178
+ * { kind: "literal", text: "--verbose" }, // duplicate
179
+ * ];
180
+ * deduplicateSuggestions(suggestions);
181
+ * // returns [{ kind: "literal", text: "--verbose" }, { kind: "literal", text: "--help" }]
182
+ * ```
183
+ *
184
+ * @since 0.9.0
185
+ */
186
+ declare function deduplicateSuggestions(suggestions: readonly Suggestion[]): Suggestion[];
187
+ //#endregion
188
+ export { DEFAULT_FIND_SIMILAR_OPTIONS, FindSimilarOptions, createErrorWithSuggestions, createSuggestionMessage, deduplicateSuggestions, expandCommandAliasSuggestions, findSimilar, levenshteinDistance };
@@ -0,0 +1,188 @@
1
+ import { Message } from "./message.js";
2
+ import { Usage } from "./usage.js";
3
+ import { Suggestion } from "./internal/parser.js";
4
+
5
+ //#region src/suggestion.d.ts
6
+
7
+ /**
8
+ * Calculates the Levenshtein distance between two strings.
9
+ *
10
+ * The Levenshtein distance is the minimum number of single-character edits
11
+ * (insertions, deletions, or substitutions) required to transform one string
12
+ * into another.
13
+ *
14
+ * @param source The source string
15
+ * @param target The target string
16
+ * @returns The edit distance (number of insertions, deletions, substitutions)
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * levenshteinDistance("kitten", "sitting"); // returns 3
21
+ * levenshteinDistance("--verbos", "--verbose"); // returns 1
22
+ * levenshteinDistance("hello", "hello"); // returns 0
23
+ * ```
24
+ */
25
+ declare function levenshteinDistance(source: string, target: string): number;
26
+ /**
27
+ * Options for finding similar strings.
28
+ */
29
+ interface FindSimilarOptions {
30
+ /**
31
+ * Maximum edit distance to consider a match.
32
+ * Strings with a distance greater than this value will not be suggested.
33
+ * @default 3
34
+ */
35
+ readonly maxDistance?: number;
36
+ /**
37
+ * Maximum distance ratio (distance / input length).
38
+ * Prevents suggesting long strings for very short inputs.
39
+ * For example, with maxDistanceRatio=0.5, an input of length 2
40
+ * will only suggest strings within distance 1.
41
+ * @default 0.5
42
+ */
43
+ readonly maxDistanceRatio?: number;
44
+ /**
45
+ * Maximum number of suggestions to return.
46
+ * @default 3
47
+ */
48
+ readonly maxSuggestions?: number;
49
+ /**
50
+ * Case-sensitive comparison.
51
+ * If false, strings are compared case-insensitively.
52
+ * @default false
53
+ */
54
+ readonly caseSensitive?: boolean;
55
+ }
56
+ /**
57
+ * Default options for finding similar strings.
58
+ * These values are optimized for command-line option/command name suggestions.
59
+ *
60
+ * @since 0.7.0
61
+ */
62
+ declare const DEFAULT_FIND_SIMILAR_OPTIONS: Required<FindSimilarOptions>;
63
+ /**
64
+ * Finds similar strings from a list of candidates.
65
+ *
66
+ * This function uses Levenshtein distance to find strings that are similar
67
+ * to the input string. Results are sorted by similarity (most similar first).
68
+ *
69
+ * @param input The input string to find matches for
70
+ * @param candidates List of candidate strings to compare against
71
+ * @param options Configuration options
72
+ * @returns Array of similar strings, sorted by similarity (most similar first)
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * const candidates = ["--verbose", "--version", "--verify", "--help"];
77
+ * findSimilar("--verbos", candidates);
78
+ * // returns ["--verbose"]
79
+ *
80
+ * findSimilar("--ver", candidates, { maxDistance: 5 });
81
+ * // returns ["--verify", "--version", "--verbose"]
82
+ *
83
+ * findSimilar("--xyz", candidates);
84
+ * // returns [] (no similar matches)
85
+ * ```
86
+ */
87
+ declare function findSimilar(input: string, candidates: Iterable<string>, options?: FindSimilarOptions): string[];
88
+ /**
89
+ * Creates a suggestion message for a mismatched option/command.
90
+ *
91
+ * This function formats suggestions in a user-friendly way:
92
+ * - No suggestions: returns empty message
93
+ * - One suggestion: "Did you mean `option`?"
94
+ * - Multiple suggestions: "Did you mean one of these?\n option1\n option2"
95
+ *
96
+ * @param suggestions List of similar valid options/commands
97
+ * @returns A Message array with suggestion text
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * createSuggestionMessage(["--verbose", "--version"]);
102
+ * // returns message parts for:
103
+ * // "Did you mean one of these?
104
+ * // --verbose
105
+ * // --version"
106
+ *
107
+ * createSuggestionMessage(["--verbose"]);
108
+ * // returns message parts for:
109
+ * // "Did you mean `--verbose`?"
110
+ *
111
+ * createSuggestionMessage([]);
112
+ * // returns []
113
+ * ```
114
+ */
115
+ declare function createSuggestionMessage(suggestions: readonly string[]): Message;
116
+ /**
117
+ * Expands command alias suggestions so an alias typo can point at both the
118
+ * canonical command and the alias that matched.
119
+ *
120
+ * @param usage Usage terms that define command aliases.
121
+ * @param suggestions Candidate suggestions returned by {@link findSimilar}.
122
+ * @returns Suggestions with alias hits expanded to canonical name + alias.
123
+ * @internal
124
+ */
125
+ declare function expandCommandAliasSuggestions(usage: Usage, suggestions: readonly string[]): readonly string[];
126
+ /**
127
+ * Creates an error message with suggestions for similar options or commands.
128
+ *
129
+ * This is a convenience function that combines the functionality of
130
+ * `findSimilar()` and `createSuggestionMessage()` to generate user-friendly
131
+ * error messages with "Did you mean?" suggestions.
132
+ *
133
+ * @param baseError The base error message to display
134
+ * @param invalidInput The invalid option or command name that the user typed
135
+ * @param usage The usage information to extract available options/commands from
136
+ * @param type What type of names to suggest ("option", "command", or "both")
137
+ * @param customFormatter Optional custom function to format suggestions instead
138
+ * of using the default "Did you mean?" formatting
139
+ * @returns A message combining the base error with suggestions, or just the
140
+ * base error if no similar names are found
141
+ *
142
+ * @example
143
+ * ```typescript
144
+ * const baseError = message`No matched option for ${optionName("--verbos")}.`;
145
+ * const error = createErrorWithSuggestions(
146
+ * baseError,
147
+ * "--verbos",
148
+ * context.usage,
149
+ * "option"
150
+ * );
151
+ * // Returns: "No matched option for `--verbos`.\nDid you mean `--verbose`?"
152
+ * ```
153
+ *
154
+ * @since 0.7.0
155
+ */
156
+ declare function createErrorWithSuggestions(baseError: Message, invalidInput: string, usage: Usage, type?: "option" | "command" | "both", customFormatter?: (suggestions: readonly string[]) => Message): Message;
157
+ /**
158
+ * Removes duplicate suggestions from an array while preserving order.
159
+ *
160
+ * Suggestions are considered duplicates if they have the same key:
161
+ * - Literal suggestions: same text
162
+ * - File suggestions: same type, extensions, and pattern
163
+ *
164
+ * The first occurrence of each unique suggestion is kept. For file
165
+ * suggestions, `includeHidden` is merged across duplicates: if any
166
+ * duplicate has `includeHidden: true`, the kept suggestion is upgraded
167
+ * to `includeHidden: true` because it is a superset of the non-hidden
168
+ * variant.
169
+ *
170
+ * @param suggestions Array of suggestions that may contain duplicates
171
+ * @returns A new array with duplicates removed, preserving order of first occurrences
172
+ *
173
+ * @example
174
+ * ```typescript
175
+ * const suggestions = [
176
+ * { kind: "literal", text: "--verbose" },
177
+ * { kind: "literal", text: "--help" },
178
+ * { kind: "literal", text: "--verbose" }, // duplicate
179
+ * ];
180
+ * deduplicateSuggestions(suggestions);
181
+ * // returns [{ kind: "literal", text: "--verbose" }, { kind: "literal", text: "--help" }]
182
+ * ```
183
+ *
184
+ * @since 0.9.0
185
+ */
186
+ declare function deduplicateSuggestions(suggestions: readonly Suggestion[]): Suggestion[];
187
+ //#endregion
188
+ export { DEFAULT_FIND_SIMILAR_OPTIONS, FindSimilarOptions, createErrorWithSuggestions, createSuggestionMessage, deduplicateSuggestions, expandCommandAliasSuggestions, findSimilar, levenshteinDistance };