[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.

refactor: add eslint rules and refactor based on that (#91)

authored by

AmirHossein Sakhravi and committed by
GitHub
(Dec 10, 2025, 11:18 PM +0330) 56ead324 5832a091

+73 -60
+8 -2
bin/package-manager-completion.ts
··· 4 4 } from 'child_process'; 5 5 import { RootCommand } from '../src/t.js'; 6 6 7 + const noop = () => {}; 8 + 7 9 function debugLog(...args: unknown[]) { 8 10 if (process.env.DEBUG) { 9 11 console.error('[DEBUG]', ...args); ··· 47 49 try { 48 50 const result = runCompletionCommand(cliName, [], []); 49 51 if (result) return true; 50 - } catch {} 52 + } catch { 53 + noop(); 54 + } 51 55 52 56 try { 53 57 const result = runCompletionCommand(packageManager, [cliName], []); ··· 67 71 if (result) { 68 72 return result.split('\n').filter(Boolean); 69 73 } 70 - } catch {} 74 + } catch { 75 + noop(); 76 + } 71 77 72 78 try { 73 79 const result = runCompletionCommand(packageManager, [cliName], args);
+33 -2
eslint.config.js
··· 3 3 import prettierConfig from 'eslint-config-prettier'; 4 4 5 5 const { configs: eslintConfigs } = eslintjs; 6 + const completionFileGlobs = [ 7 + 'src/**/*.ts', 8 + 'bin/**/*.ts', 9 + 'benchmarks/**/*.ts', 10 + '*.ts', 11 + ]; 6 12 7 13 export default [ 14 + { 15 + ignores: ['dist/**'], 16 + }, 8 17 { 9 18 ...eslintConfigs.recommended, 10 - files: ['src/**/*.ts'], 19 + files: completionFileGlobs, 11 20 }, 12 21 ...tseslintConfigs.strict, 13 22 { 23 + files: completionFileGlobs, 14 24 rules: { 15 - '@typescript-eslint/no-unused-vars': 'off', 25 + '@typescript-eslint/no-unused-vars': [ 26 + 'error', 27 + { 28 + argsIgnorePattern: '^_', 29 + varsIgnorePattern: '^_', 30 + caughtErrorsIgnorePattern: '^ignore', 31 + ignoreRestSiblings: true, 32 + }, 33 + ], 34 + '@typescript-eslint/consistent-type-imports': [ 35 + 'warn', 36 + { 37 + prefer: 'type-imports', 38 + fixStyle: 'separate-type-imports', 39 + }, 40 + ], 41 + '@typescript-eslint/consistent-type-definitions': ['error', 'interface'], 42 + 'default-case-last': 'error', 43 + eqeqeq: ['error', 'smart'], 44 + 'no-fallthrough': ['error', { allowEmptyCase: true }], 45 + 'prefer-template': 'error', 46 + 'no-console': 'off', 16 47 }, 17 48 }, 18 49 prettierConfig,
+2 -2
src/cac.ts
··· 4 4 import * as powershell from './powershell'; 5 5 import type { CAC } from 'cac'; 6 6 import { assertDoubleDashes } from './shared'; 7 - import { CompletionConfig } from './shared'; 8 - import t, { RootCommand } from './t'; 7 + import type { CompletionConfig } from './shared'; 8 + import t, { type RootCommand } from './t'; 9 9 10 10 const execPath = process.execPath; 11 11 const processArgs = process.argv.slice(1);
+5 -3
src/citty.ts
··· 1 - import { ArgDef, defineCommand } from 'citty'; 1 + import { defineCommand } from 'citty'; 2 + import type { ArgDef } from 'citty'; 2 3 import * as zsh from './zsh'; 3 4 import * as bash from './bash'; 4 5 import * as fish from './fish'; ··· 10 11 SubCommandsDef, 11 12 } from 'citty'; 12 13 import { generateFigSpec } from './fig'; 13 - import { CompletionConfig, assertDoubleDashes } from './shared'; 14 - import t, { RootCommand } from './t'; 14 + import { assertDoubleDashes } from './shared'; 15 + import type { CompletionConfig } from './shared'; 16 + import t, { type RootCommand } from './t'; 15 17 16 18 function quoteIfNeeded(path: string) { 17 19 return path.includes(' ') ? `'${path}'` : path;
+5 -11
src/commander.ts
··· 3 3 import * as fish from './fish'; 4 4 import * as powershell from './powershell'; 5 5 import type { Command as CommanderCommand, ParseOptions } from 'commander'; 6 - import t, { RootCommand } from './t'; 6 + import t, { type RootCommand } from './t'; 7 7 import { assertDoubleDashes } from './shared'; 8 8 9 9 const execPath = process.execPath; ··· 22 22 const programName = instance.name(); 23 23 24 24 // Process the root command 25 - processRootCommand(instance, programName); 25 + processRootCommand(instance); 26 26 27 27 // Process all subcommands 28 - processSubcommands(instance, programName); 28 + processSubcommands(instance); 29 29 30 30 // Add the complete command for normal shell script generation 31 31 instance ··· 101 101 return t; 102 102 } 103 103 104 - function processRootCommand( 105 - command: CommanderCommand, 106 - programName: string 107 - ): void { 104 + function processRootCommand(command: CommanderCommand): void { 108 105 // Add root command options to the root t instance 109 106 for (const option of command.options) { 110 107 // Extract short flag from the name if it exists (e.g., "-c, --config" -> "c") ··· 122 119 } 123 120 } 124 121 125 - function processSubcommands( 126 - rootCommand: CommanderCommand, 127 - programName: string 128 - ): void { 122 + function processSubcommands(rootCommand: CommanderCommand): void { 129 123 // Build a map of command paths 130 124 const commandMap = new Map<string, CommanderCommand>(); 131 125
+10 -10
src/fig.ts
··· 1 1 import type { CommandDef, ArgsDef, PositionalArgDef, CommandMeta } from 'citty'; 2 2 3 - type FigSpec = { 3 + interface FigSpec { 4 4 name: string; 5 5 description: string; 6 6 options?: FigOption[]; 7 7 subcommands?: FigSubcommand[]; 8 8 args?: FigArg[]; 9 - }; 9 + } 10 10 11 - type FigOption = { 11 + interface FigOption { 12 12 name: string; 13 13 description: string; 14 14 args?: FigArg[]; 15 15 isRequired?: boolean; 16 - }; 16 + } 17 17 18 - type FigSubcommand = { 18 + interface FigSubcommand { 19 19 name: string; 20 20 description: string; 21 21 options?: FigOption[]; 22 22 subcommands?: FigSubcommand[]; 23 23 args?: FigArg[]; 24 - }; 24 + } 25 25 26 - type FigArg = { 26 + interface FigArg { 27 27 name: string; 28 28 description?: string; 29 29 isOptional?: boolean; 30 30 isVariadic?: boolean; 31 31 suggestions?: FigSuggestion[]; 32 - }; 32 + } 33 33 34 - type FigSuggestion = { 34 + interface FigSuggestion { 35 35 name: string; 36 36 description?: string; 37 - }; 37 + } 38 38 39 39 async function processArgs<T extends ArgsDef>( 40 40 args: T
+1 -5
src/powershell.ts
··· 2 2 3 3 // TODO: issue with -- -- completions 4 4 5 - export function generate( 6 - name: string, 7 - exec: string, 8 - includeDesc = false 9 - ): string { 5 + export function generate(name: string, exec: string): string { 10 6 // Replace '-' and ':' with '_' for variable names 11 7 const nameForVar = name.replace(/[-:]/g, '_'); 12 8
+1 -1
src/shared.ts
··· 1 - import { OptionHandler, ArgumentHandler } from './t'; 1 + import type { OptionHandler, ArgumentHandler } from './t'; 2 2 3 3 export const noopHandler: OptionHandler = function () { 4 4 // No-op handler for options
+8 -24
src/t.ts
··· 20 20 options: OptionsMap 21 21 ) => void; 22 22 23 - export type Completion = { 23 + export interface Completion { 24 24 description?: string; 25 25 value: string; 26 - }; 26 + } 27 27 28 28 export type ArgumentHandler = ( 29 29 this: Argument, ··· 214 214 215 215 private shouldCompleteFlags( 216 216 lastPrevArg: string | undefined, 217 - toComplete: string, 218 - endsWithSpace: boolean 217 + toComplete: string 219 218 ): boolean { 220 219 if (toComplete.startsWith('-')) { 221 220 return true; ··· 244 243 return false; 245 244 } 246 245 247 - private shouldCompleteCommands( 248 - toComplete: string, 249 - endsWithSpace: boolean 250 - ): boolean { 246 + private shouldCompleteCommands(toComplete: string): boolean { 251 247 return !toComplete.startsWith('-'); 252 248 } 253 249 ··· 256 252 command: Command, 257 253 previousArgs: string[], 258 254 toComplete: string, 259 - endsWithSpace: boolean, 260 255 lastPrevArg: string | undefined 261 256 ) { 262 257 // Handle flag value completion ··· 350 345 } 351 346 352 347 // positional argument completion 353 - private handlePositionalCompletion( 354 - command: Command, 355 - previousArgs: string[], 356 - toComplete: string, 357 - endsWithSpace: boolean 358 - ) { 348 + private handlePositionalCompletion(command: Command, previousArgs: string[]) { 359 349 // current argument position (subtract command name) 360 350 const commandParts = command.value.split(' ').length; 361 351 const currentArgIndex = Math.max(0, previousArgs.length - commandParts); ··· 437 427 const [matchedCommand] = this.matchCommand(previousArgs); 438 428 const lastPrevArg = previousArgs[previousArgs.length - 1]; 439 429 440 - if (this.shouldCompleteFlags(lastPrevArg, toComplete, endsWithSpace)) { 430 + if (this.shouldCompleteFlags(lastPrevArg, toComplete)) { 441 431 this.handleFlagCompletion( 442 432 matchedCommand, 443 433 previousArgs, 444 434 toComplete, 445 - endsWithSpace, 446 435 lastPrevArg 447 436 ); 448 437 } else { ··· 461 450 } 462 451 } 463 452 464 - if (this.shouldCompleteCommands(toComplete, endsWithSpace)) { 453 + if (this.shouldCompleteCommands(toComplete)) { 465 454 this.handleCommandCompletion(previousArgs, toComplete); 466 455 } 467 456 if (matchedCommand && matchedCommand.arguments.size > 0) { 468 - this.handlePositionalCompletion( 469 - matchedCommand, 470 - previousArgs, 471 - toComplete, 472 - endsWithSpace 473 - ); 457 + this.handlePositionalCompletion(matchedCommand, previousArgs); 474 458 } 475 459 } 476 460