From cca07464fa854a75e250769c372321b669ccfba9 Mon Sep 17 00:00:00 2001 From: neverland Date: Wed, 2 Sep 2026 19:40:35 +0800 Subject: [PATCH 1/3] feat(cli): support file arguments in rs check --- packages/rstack/src/cli/commandHelp.ts | 2 +- packages/rstack/src/cli/commands.ts | 17 +++++++++--- .../cli/__snapshots__/check.test.ts.snap | 2 +- packages/rstack/tests/cli/check.test.ts | 27 +++++++++++++++++++ website/docs/en/guide/cli/check.mdx | 14 +++++++++- website/docs/zh/guide/cli/check.mdx | 14 +++++++++- 6 files changed, 68 insertions(+), 8 deletions(-) diff --git a/packages/rstack/src/cli/commandHelp.ts b/packages/rstack/src/cli/commandHelp.ts index f178d0e1..6906eab1 100644 --- a/packages/rstack/src/cli/commandHelp.ts +++ b/packages/rstack/src/cli/commandHelp.ts @@ -148,7 +148,7 @@ const HELP_DEFINITIONS = { ], }, check: { - usage: 'rs check [options]', + usage: 'rs check [options] [files...]', description: 'Run static checks, including lint and format', sections: [ { diff --git a/packages/rstack/src/cli/commands.ts b/packages/rstack/src/cli/commands.ts index 0c4c1881..cd170249 100644 --- a/packages/rstack/src/cli/commands.ts +++ b/packages/rstack/src/cli/commands.ts @@ -157,13 +157,13 @@ async function runRslintCLI(args: string[]): Promise { } async function runCheckCLI(args: string[]): Promise { - const { values } = parseArgs({ + const { values, positionals } = parseArgs({ args, options: { 'type-check': { type: 'boolean' }, help: { type: 'boolean', short: 'h' }, }, - allowPositionals: false, + allowPositionals: true, strict: true, }); @@ -171,7 +171,13 @@ async function runCheckCLI(args: string[]): Promise { return printCommandHelp('check'); } - await runRslintCLI(values.typeCheck ? ['--type-check'] : []); + // Keep file arguments after `--` when forwarding them so names beginning + // with a hyphen are not reinterpreted as child-command options. + const fileArgs = positionals.length > 0 ? ['--', ...positionals] : []; + await runRslintCLI([ + ...(values.typeCheck ? ['--type-check'] : []), + ...fileArgs, + ]); if (process.exitCode) { return; } @@ -185,7 +191,10 @@ async function runCheckCLI(args: string[]): Promise { /* rspackChunkName: 'fmt' */ '../fmt/cli.ts' ); - await runFmtCLI(['--check'], { fixCommand: 'rs fmt', loadedConfig }); + await runFmtCLI(['--check', ...fileArgs], { + fixCommand: 'rs fmt', + loadedConfig, + }); } export async function setupCommands(): Promise { diff --git a/packages/rstack/tests/cli/__snapshots__/check.test.ts.snap b/packages/rstack/tests/cli/__snapshots__/check.test.ts.snap index c24f532b..6b7533b7 100644 --- a/packages/rstack/tests/cli/__snapshots__/check.test.ts.snap +++ b/packages/rstack/tests/cli/__snapshots__/check.test.ts.snap @@ -4,7 +4,7 @@ exports[`displays check help without loading config 1`] = ` "Rstack v Usage: - $ rs check [options] + $ rs check [options] [files...] Run static checks, including lint and format diff --git a/packages/rstack/tests/cli/check.test.ts b/packages/rstack/tests/cli/check.test.ts index f764457f..21c791c0 100644 --- a/packages/rstack/tests/cli/check.test.ts +++ b/packages/rstack/tests/cli/check.test.ts @@ -48,6 +48,33 @@ test('runs lint followed by a formatting check', () => { expect(formatted.stderr).toBe(''); }); +test('passes file arguments to lint and the formatting check', () => { + writeLintConfig(); + writeProjectFile('src/selected-a.ts', 'const selectedA = true;\n'); + writeProjectFile('src/selected-b.ts', 'const selectedB = true;\n'); + writeProjectFile('src/unselected-lint-error.ts', 'debugger;\n'); + writeProjectFile('src/unselected-format-error.ts', 'const value=true'); + + const result = runCheck(['src/selected-a.ts', 'src/selected-b.ts']); + + expect(result.status).toBe(0); + expect(result.stdout).toContain('Format check passed in'); + expect(result.stdout).toContain('(2 files)'); + expect(result.stderr).toBe(''); +}); + +test('supports file arguments after the option terminator', () => { + writeLintConfig(); + writeProjectFile('--selected.ts', 'const selected = true;\n'); + + const result = runCheck(['--', '--selected.ts']); + + expect(result.status).toBe(0); + expect(result.stdout).toContain('Format check passed in'); + expect(result.stdout).toContain('(1 file)'); + expect(result.stderr).toBe(''); +}); + test('enables type checking only with --type-check', () => { writeLintConfig(); writeProjectFile( diff --git a/website/docs/en/guide/cli/check.mdx b/website/docs/en/guide/cli/check.mdx index 59679821..7182461d 100644 --- a/website/docs/en/guide/cli/check.mdx +++ b/website/docs/en/guide/cli/check.mdx @@ -9,7 +9,19 @@ The `rs check` command combines linting, formatting, and optional TypeScript typ ## Usage ```bash -rs check [options] +rs check [options] [files...] +``` + +Pass files or directories to limit both linting and formatting to those paths: + +```bash +rs check src/index.ts packages/utils +``` + +The same file arguments are passed to both commands, so the example above is equivalent to: + +```bash +rs lint src/index.ts packages/utils && rs fmt --check src/index.ts packages/utils ``` ## Checks diff --git a/website/docs/zh/guide/cli/check.mdx b/website/docs/zh/guide/cli/check.mdx index d64c3c56..9d2ec5f4 100644 --- a/website/docs/zh/guide/cli/check.mdx +++ b/website/docs/zh/guide/cli/check.mdx @@ -9,7 +9,19 @@ description: '同时运行 lint 和格式检查,并可选启用 TypeScript 类 ## 用法 \{#usage} ```bash -rs check [options] +rs check [options] [files...] +``` + +传入文件或目录,可以将 lint 和格式检查都限制在这些路径中: + +```bash +rs check src/index.ts packages/utils +``` + +相同的文件参数会同时传给两个命令,因此上述示例等同于: + +```bash +rs lint src/index.ts packages/utils && rs fmt --check src/index.ts packages/utils ``` ## 检查内容 \{#checks} From b2a5d564c9e40ef72aa02af161e95d0015b66b36 Mon Sep 17 00:00:00 2001 From: neverland Date: Wed, 2 Sep 2026 19:52:34 +0800 Subject: [PATCH 2/3] fix(cli): preserve check file scope in fix hint --- packages/rstack/src/cli/commands.ts | 12 +++++++++++- packages/rstack/tests/cli/check.test.ts | 22 +++++++++++++++++----- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/packages/rstack/src/cli/commands.ts b/packages/rstack/src/cli/commands.ts index cd170249..a23887ac 100644 --- a/packages/rstack/src/cli/commands.ts +++ b/packages/rstack/src/cli/commands.ts @@ -141,6 +141,16 @@ async function runRspressCLI(args: string[]): Promise { const RSLINT_CONFIG_PATH = join(import.meta.dirname, 'rslintConfig.js'); +const SHELL_SAFE_ARGUMENT_REGEXP = /^[\w@%+=:,./-]+$/u; + +const quoteShellArgument = (argument: string): string => + SHELL_SAFE_ARGUMENT_REGEXP.test(argument) + ? argument + : `'${argument.replaceAll("'", "'\"'\"'")}'`; + +const formatCommand = (args: readonly string[]): string => + args.map(quoteShellArgument).join(' '); + async function runRslintCLI(args: string[]): Promise { if (hasHelpFlag(args)) { return printCommandHelp('lint'); @@ -192,7 +202,7 @@ async function runCheckCLI(args: string[]): Promise { '../fmt/cli.ts' ); await runFmtCLI(['--check', ...fileArgs], { - fixCommand: 'rs fmt', + fixCommand: formatCommand(['rs', 'fmt', ...fileArgs]), loadedConfig, }); } diff --git a/packages/rstack/tests/cli/check.test.ts b/packages/rstack/tests/cli/check.test.ts index 21c791c0..e2ac1033 100644 --- a/packages/rstack/tests/cli/check.test.ts +++ b/packages/rstack/tests/cli/check.test.ts @@ -63,16 +63,28 @@ test('passes file arguments to lint and the formatting check', () => { expect(result.stderr).toBe(''); }); +test('preserves file arguments in the formatting fix command', () => { + writeLintConfig(); + writeProjectFile("src/selected file's.ts", 'const selected=true'); + + const result = runCheck(["src/selected file's.ts"]); + + expect(result.status).toBe(1); + expect(result.stderr).toContain( + `Run rs fmt -- 'src/selected file'"'"'s.ts' to fix.`, + ); +}); + test('supports file arguments after the option terminator', () => { writeLintConfig(); - writeProjectFile('--selected.ts', 'const selected = true;\n'); + writeProjectFile('--selected.ts', 'const selected=true'); const result = runCheck(['--', '--selected.ts']); - expect(result.status).toBe(0); - expect(result.stdout).toContain('Format check passed in'); - expect(result.stdout).toContain('(1 file)'); - expect(result.stderr).toBe(''); + expect(result.status).toBe(1); + expect(result.stderr).toContain( + 'Formatting issues found in 1 file. Run rs fmt -- --selected.ts to fix.', + ); }); test('enables type checking only with --type-check', () => { From 380cbe528b26ec74020b711e62bc10e29b5ff864 Mon Sep 17 00:00:00 2001 From: neverland Date: Wed, 2 Sep 2026 20:01:40 +0800 Subject: [PATCH 3/3] Revert "fix(cli): preserve check file scope in fix hint" This reverts commit b2a5d564c9e40ef72aa02af161e95d0015b66b36. --- packages/rstack/src/cli/commands.ts | 12 +----------- packages/rstack/tests/cli/check.test.ts | 22 +++++----------------- 2 files changed, 6 insertions(+), 28 deletions(-) diff --git a/packages/rstack/src/cli/commands.ts b/packages/rstack/src/cli/commands.ts index a23887ac..cd170249 100644 --- a/packages/rstack/src/cli/commands.ts +++ b/packages/rstack/src/cli/commands.ts @@ -141,16 +141,6 @@ async function runRspressCLI(args: string[]): Promise { const RSLINT_CONFIG_PATH = join(import.meta.dirname, 'rslintConfig.js'); -const SHELL_SAFE_ARGUMENT_REGEXP = /^[\w@%+=:,./-]+$/u; - -const quoteShellArgument = (argument: string): string => - SHELL_SAFE_ARGUMENT_REGEXP.test(argument) - ? argument - : `'${argument.replaceAll("'", "'\"'\"'")}'`; - -const formatCommand = (args: readonly string[]): string => - args.map(quoteShellArgument).join(' '); - async function runRslintCLI(args: string[]): Promise { if (hasHelpFlag(args)) { return printCommandHelp('lint'); @@ -202,7 +192,7 @@ async function runCheckCLI(args: string[]): Promise { '../fmt/cli.ts' ); await runFmtCLI(['--check', ...fileArgs], { - fixCommand: formatCommand(['rs', 'fmt', ...fileArgs]), + fixCommand: 'rs fmt', loadedConfig, }); } diff --git a/packages/rstack/tests/cli/check.test.ts b/packages/rstack/tests/cli/check.test.ts index e2ac1033..21c791c0 100644 --- a/packages/rstack/tests/cli/check.test.ts +++ b/packages/rstack/tests/cli/check.test.ts @@ -63,28 +63,16 @@ test('passes file arguments to lint and the formatting check', () => { expect(result.stderr).toBe(''); }); -test('preserves file arguments in the formatting fix command', () => { - writeLintConfig(); - writeProjectFile("src/selected file's.ts", 'const selected=true'); - - const result = runCheck(["src/selected file's.ts"]); - - expect(result.status).toBe(1); - expect(result.stderr).toContain( - `Run rs fmt -- 'src/selected file'"'"'s.ts' to fix.`, - ); -}); - test('supports file arguments after the option terminator', () => { writeLintConfig(); - writeProjectFile('--selected.ts', 'const selected=true'); + writeProjectFile('--selected.ts', 'const selected = true;\n'); const result = runCheck(['--', '--selected.ts']); - expect(result.status).toBe(1); - expect(result.stderr).toContain( - 'Formatting issues found in 1 file. Run rs fmt -- --selected.ts to fix.', - ); + expect(result.status).toBe(0); + expect(result.stdout).toContain('Format check passed in'); + expect(result.stdout).toContain('(1 file)'); + expect(result.stderr).toBe(''); }); test('enables type checking only with --type-check', () => {