[READ-ONLY] Mirror of https://github.com/bombshell-dev/tab. shell autocompletions for javascript CLIs
4

Configure Feed

Select the types of activity you want to include in your feed.

chore: cleaner comments (#90)

authored by

AmirHossein Sakhravi and committed by
GitHub
(Dec 10, 2025, 9:56 PM +0330) 9f36ff28 0c9d7186

+34 -77
+13 -18
src/cac.ts
··· 26 26 instance: CAC, 27 27 completionConfig?: CompletionConfig 28 28 ): Promise<RootCommand> { 29 - // Add all commands and their options 30 29 for (const cmd of [instance.globalCommand, ...instance.commands]) { 31 - if (cmd.name === 'complete') continue; // Skip completion command 30 + if (cmd.name === 'complete') continue; 32 31 33 - // Get positional args info from command usage 34 32 const args = (cmd.rawName.match(/\[.*?\]|<.*?>/g) || []).map((arg) => 35 33 arg.startsWith('[') 36 34 ); // true if optional (wrapped in []) ··· 40 38 ? completionConfig 41 39 : completionConfig?.subCommands?.[cmd.name]; 42 40 43 - // Add command to completion using t.ts API 41 + // command 44 42 const commandName = isRootCommand ? '' : cmd.name; 45 43 const command = isRootCommand 46 44 ? t 47 45 : t.command(commandName, cmd.description || ''); 48 46 49 - // Set args for the command 47 + // args (if has positional arguments) 50 48 if (command) { 51 - // Extract argument names from command usage 52 49 const argMatches = 53 50 cmd.rawName.match(/<([^>]+)>|\[\.\.\.([^\]]+)\]/g) || []; 54 51 const argNames = argMatches.map((match) => { 55 52 if (match.startsWith('<') && match.endsWith('>')) { 56 - return match.slice(1, -1); // Remove < > 53 + return match.slice(1, -1); 57 54 } else if (match.startsWith('[...') && match.endsWith(']')) { 58 - return match.slice(4, -1); // Remove [... ] 55 + return match.slice(4, -1); 59 56 } 60 57 return match; 61 58 }); ··· 71 68 }); 72 69 } 73 70 74 - // Add command options 71 + // options 75 72 for (const option of [...instance.globalCommand.options, ...cmd.options]) { 76 - // Extract short flag from the rawName if it exists (e.g., "-c, --config" -> "c") 73 + // short flag (if exists) 77 74 const shortFlag = option.rawName.match(/^-([a-zA-Z]), --/)?.[1]; 78 - const argName = option.name; // option.name is already clean (e.g., "config") 75 + const argName = option.name; 79 76 80 - // Add option using t.ts API 81 77 const targetCommand = isRootCommand ? t : command; 82 78 if (targetCommand) { 83 79 const handler = commandCompletionConfig?.options?.[argName]; 84 80 85 - // Check if option takes a value (has <> or [] in rawName, or is marked as required) 81 + // takes value (if has <> or [] in rawName, or is marked as required) 86 82 const takesValue = 87 83 option.required || VALUE_OPTION_RE.test(option.rawName); 88 84 ··· 98 94 targetCommand.option(argName, option.description || '', handler); 99 95 } 100 96 } else if (takesValue) { 101 - // Takes value but no custom handler = value option with no completions 97 + // value option (if takes value but no custom handler) 102 98 if (shortFlag) { 103 99 targetCommand.option( 104 100 argName, 105 101 option.description || '', 106 - async () => [], // Empty completions 102 + async () => [], 107 103 shortFlag 108 104 ); 109 105 } else { ··· 114 110 ); 115 111 } 116 112 } else { 117 - // No custom handler and doesn't take value = boolean flag 113 + // boolean flag (if no custom handler and doesn't take value) 118 114 if (shortFlag) { 119 115 targetCommand.option(argName, option.description || '', shortFlag); 120 116 } else { ··· 153 149 const args: string[] = extra['--'] || []; 154 150 instance.showHelpOnExit = false; 155 151 156 - // Parse current command context 152 + // command context 157 153 instance.unsetMatchedCommand(); 158 154 instance.parse([execPath, processArgs[0], ...args], { 159 155 run: false, 160 156 }); 161 157 162 - // Use t.ts parse method instead of completion.parse 163 158 return t.parse(args); 164 159 } 165 160 }
+7 -15
src/citty.ts
··· 47 47 } 48 48 const isPositional = isConfigPositional(config); 49 49 50 - // Add command using t.ts API 51 50 const commandName = parentCmd ? `${parentCmd} ${cmd}` : cmd; 52 51 const command = t.command(commandName, meta.description); 53 52 54 53 // Set args for the command if it has positional arguments 55 54 if (isPositional && config.args) { 56 - // Add arguments with completion handlers from subCompletionConfig args 57 55 for (const [argName, argConfig] of Object.entries(config.args)) { 58 56 const conf = argConfig as ArgDef; 59 57 if (conf.type === 'positional') { 60 - // Check if this is a variadic argument (required: false for variadic in citty) 61 58 const isVariadic = conf.required === false; 62 59 const argHandler = subCompletionConfig?.args?.[argName]; 63 60 if (argHandler) { ··· 69 66 } 70 67 } 71 68 72 - // Handle nested subcommands recursively 69 + // subcommands (recursive) 73 70 if (subCommands) { 74 71 await handleSubCommands( 75 72 subCommands, ··· 78 75 ); 79 76 } 80 77 81 - // Handle arguments 78 + // args 82 79 if (config.args) { 83 80 for (const [argName, argConfig] of Object.entries(config.args)) { 84 81 const conf = argConfig as ArgDef; 85 - // Extract alias from the config if it exists 82 + // alias (if exists) 86 83 const shortFlag = 87 84 typeof conf === 'object' && 'alias' in conf 88 85 ? Array.isArray(conf.alias) ··· 90 87 : conf.alias 91 88 : undefined; 92 89 93 - // Add option using t.ts API - store without -- prefix 90 + // option (without -- prefix) 94 91 const handler = subCompletionConfig?.options?.[argName]; 95 92 if (handler) { 96 - // Has custom handler → value option 93 + // value option (if has custom handler) 97 94 if (shortFlag) { 98 95 command.option(argName, conf.description ?? '', handler, shortFlag); 99 96 } else { 100 97 command.option(argName, conf.description ?? '', handler); 101 98 } 102 99 } else { 103 - // No custom handler → boolean flag 100 + // boolean flag (if no custom handler) 104 101 if (shortFlag) { 105 102 command.option(argName, conf.description ?? '', shortFlag); 106 103 } else { ··· 130 127 131 128 const isPositional = isConfigPositional(instance); 132 129 133 - // Set args for the root command if it has positional arguments 130 + // args (if has positional arguments) 134 131 if (isPositional && instance.args) { 135 132 for (const [argName, argConfig] of Object.entries(instance.args)) { 136 133 const conf = argConfig as PositionalArgDef; ··· 158 155 for (const [argName, argConfig] of Object.entries(instance.args)) { 159 156 const conf = argConfig as ArgDef; 160 157 161 - // Extract alias (same logic as subcommands) 162 158 const shortFlag = 163 159 typeof conf === 'object' && 'alias' in conf 164 160 ? Array.isArray(conf.alias) ··· 166 162 : conf.alias 167 163 : undefined; 168 164 169 - // Add option using t.ts API - store without -- prefix 170 165 const handler = completionConfig?.options?.[argName]; 171 166 if (handler) { 172 - // Has custom handler → value option 173 167 if (shortFlag) { 174 168 t.option(argName, conf.description ?? '', handler, shortFlag); 175 169 } else { 176 170 t.option(argName, conf.description ?? '', handler); 177 171 } 178 172 } else { 179 - // No custom handler → boolean flag 180 173 if (shortFlag) { 181 174 t.option(argName, conf.description ?? '', shortFlag); 182 175 } else { ··· 235 228 assertDoubleDashes(name); 236 229 237 230 const extra = ctx.rawArgs.slice(ctx.rawArgs.indexOf('--') + 1); 238 - // Use t.ts parse method instead of completion.parse 239 231 return t.parse(extra); 240 232 } 241 233 }
+14 -44
src/t.ts
··· 20 20 options: OptionsMap 21 21 ) => void; 22 22 23 - // Completion result types 24 23 export type Completion = { 25 24 description?: string; 26 25 value: string; ··· 88 87 this.description = description; 89 88 } 90 89 91 - // Function overloads for better UX - combined into single signature with optional parameters 92 90 option( 93 91 value: string, 94 92 description: string, ··· 99 97 let aliasStr: string | undefined; 100 98 let isBoolean: boolean; 101 99 102 - // Parse arguments based on types 103 100 if (typeof handlerOrAlias === 'function') { 104 101 handler = handlerOrAlias; 105 102 aliasStr = alias; ··· 154 151 return c; 155 152 } 156 153 157 - // Utility method to strip options from args for command matching 158 154 private stripOptions(args: string[]): string[] { 159 155 const parts: string[] = []; 160 156 let i = 0; ··· 163 159 const arg = args[i]; 164 160 165 161 if (arg.startsWith('-')) { 166 - i++; // Skip the option 162 + i++; 167 163 168 - // Check if this option expects a value (not boolean) 169 - // We need to check across all commands since we don't know which command context we're in yet 170 164 let isBoolean = false; 171 165 172 - // Check root command options 173 166 const rootOption = this.findOption(this, arg); 174 167 if (rootOption) { 175 168 isBoolean = rootOption.isBoolean ?? false; 176 169 } else { 177 - // Check all subcommand options 170 + // subcommand options 178 171 for (const [, command] of this.commands) { 179 172 const option = this.findOption(command, arg); 180 173 if (option) { ··· 184 177 } 185 178 } 186 179 187 - // Only skip the next argument if this is not a boolean option and the next arg doesn't start with - 180 + // skip the next argument if this is not a boolean option and the next arg doesn't start with - 188 181 if (!isBoolean && i < args.length && !args[i].startsWith('-')) { 189 - i++; // Skip the option value 182 + i++; 190 183 } 191 184 } else { 192 185 parts.push(arg); ··· 197 190 return parts; 198 191 } 199 192 200 - // Find the appropriate command based on args 201 193 private matchCommand(args: string[]): [Command, string[]] { 202 194 args = this.stripOptions(args); 203 195 const parts: string[] = []; ··· 217 209 } 218 210 } 219 211 220 - // If no command was matched, use the root command (this) 221 212 return [matchedCommand || this, remaining]; 222 213 } 223 214 224 - // Determine if we should complete flags 225 215 private shouldCompleteFlags( 226 216 lastPrevArg: string | undefined, 227 217 toComplete: string, 228 218 endsWithSpace: boolean 229 219 ): boolean { 230 - // Always complete if the current token starts with a dash 231 220 if (toComplete.startsWith('-')) { 232 221 return true; 233 222 } 234 223 235 - // If the previous argument was an option, check if it expects a value 224 + // previous argument was an option, check if it expects a value 236 225 if (lastPrevArg?.startsWith('-')) { 237 - // Find the option to check if it's boolean 238 226 let option = this.findOption(this, lastPrevArg); 239 227 if (!option) { 240 - // Check all subcommand options 228 + // subcommand options 241 229 for (const [, command] of this.commands) { 242 230 option = this.findOption(command, lastPrevArg); 243 231 if (option) break; 244 232 } 245 233 } 246 234 247 - // If it's a boolean option, don't try to complete its value 235 + // boolean option, don't try to complete its value 248 236 if (option && option.isBoolean) { 249 237 return false; 250 238 } 251 239 252 - // Non-boolean options expect values 240 + // non-boolean options expect values 253 241 return true; 254 242 } 255 243 256 244 return false; 257 245 } 258 246 259 - // Determine if we should complete commands 260 247 private shouldCompleteCommands( 261 248 toComplete: string, 262 249 endsWithSpace: boolean ··· 264 251 return !toComplete.startsWith('-'); 265 252 } 266 253 267 - // Handle flag completion (names and values) 254 + // flag completion (names and values) 268 255 private handleFlagCompletion( 269 256 command: Command, 270 257 previousArgs: string[], ··· 298 285 return; 299 286 } 300 287 301 - // Handle flag name completion 302 288 if (toComplete.startsWith('-')) { 303 289 const isShortFlag = 304 290 toComplete.startsWith('-') && !toComplete.startsWith('--'); ··· 324 310 } 325 311 } 326 312 327 - // Helper method to find an option by name or alias 313 + // find option by name or alias 328 314 private findOption(command: Command, optionName: string): Option | undefined { 329 - // Try direct match (with dashes) 330 315 let option = command.options.get(optionName); 331 316 if (option) return option; 332 317 333 - // Try without dashes (the actual storage format) 334 318 option = command.options.get(optionName.replace(/^-+/, '')); 335 319 if (option) return option; 336 320 337 - // Try by short alias 321 + // short alias 338 322 for (const [_name, opt] of command.options) { 339 323 if (opt.alias && `-${opt.alias}` === optionName) { 340 324 return opt; ··· 344 328 return undefined; 345 329 } 346 330 347 - // Handle command completion 331 + // command completion 348 332 private handleCommandCompletion(previousArgs: string[], toComplete: string) { 349 333 const commandParts = this.stripOptions(previousArgs); 350 334 ··· 365 349 } 366 350 } 367 351 368 - // Handle positional argument completion 352 + // positional argument completion 369 353 private handlePositionalCompletion( 370 354 command: Command, 371 355 previousArgs: string[], 372 356 toComplete: string, 373 357 endsWithSpace: boolean 374 358 ) { 375 - // Get the current argument position (subtract command name) 359 + // current argument position (subtract command name) 376 360 const commandParts = command.value.split(' ').length; 377 361 const currentArgIndex = Math.max(0, previousArgs.length - commandParts); 378 362 const argumentEntries = Array.from(command.arguments.entries()); 379 363 380 - // If we have arguments defined 381 364 if (argumentEntries.length > 0) { 382 - // Find the appropriate argument for the current position 383 365 let targetArgument: Argument | undefined; 384 366 385 367 if (currentArgIndex < argumentEntries.length) { 386 - // We're within the defined arguments 387 368 const [_argName, argument] = argumentEntries[currentArgIndex]; 388 369 targetArgument = argument; 389 370 } else { 390 - // We're beyond the defined arguments, check if the last argument is variadic 391 371 const lastArgument = argumentEntries[argumentEntries.length - 1][1]; 392 372 if (lastArgument.variadic) { 393 373 targetArgument = lastArgument; 394 374 } 395 375 } 396 376 397 - // If we found a target argument with a handler, use it 398 377 if ( 399 378 targetArgument && 400 379 targetArgument.handler && ··· 412 391 } 413 392 } 414 393 415 - // Format and output completion results 416 394 private complete(toComplete: string) { 417 395 this.directive = ShellCompDirective.ShellCompDirectiveNoFileComp; 418 396 ··· 459 437 const [matchedCommand] = this.matchCommand(previousArgs); 460 438 const lastPrevArg = previousArgs[previousArgs.length - 1]; 461 439 462 - // 1. Handle flag/option completion 463 440 if (this.shouldCompleteFlags(lastPrevArg, toComplete, endsWithSpace)) { 464 441 this.handleFlagCompletion( 465 442 matchedCommand, ··· 469 446 lastPrevArg 470 447 ); 471 448 } else { 472 - // Check if we just finished a boolean option with no value expected 473 - // In this case, don't complete anything 474 449 if (lastPrevArg?.startsWith('-') && toComplete === '' && endsWithSpace) { 475 450 let option = this.findOption(this, lastPrevArg); 476 451 if (!option) { 477 - // Check all subcommand options 478 452 for (const [, command] of this.commands) { 479 453 option = this.findOption(command, lastPrevArg); 480 454 if (option) break; 481 455 } 482 456 } 483 457 484 - // If it's a boolean option followed by empty space, don't complete anything 485 458 if (option && option.isBoolean) { 486 - // Don't add any completions, just output the directive 487 459 this.complete(toComplete); 488 460 return; 489 461 } 490 462 } 491 463 492 - // 2. Handle command/subcommand completion 493 464 if (this.shouldCompleteCommands(toComplete, endsWithSpace)) { 494 465 this.handleCommandCompletion(previousArgs, toComplete); 495 466 } 496 - // 3. Handle positional arguments - always check for root command arguments 497 467 if (matchedCommand && matchedCommand.arguments.size > 0) { 498 468 this.handlePositionalCompletion( 499 469 matchedCommand,