From d84858ea21810c07eb4bb8001983955b281c1d11 Mon Sep 17 00:00:00 2001 From: neverland Date: Tue, 4 Aug 2026 22:44:36 +0800 Subject: [PATCH] feat(fmt): add --no-error-on-unmatched-pattern --- packages/rstack/src/fmt/cli.ts | 34 ++++++++++++++----- packages/rstack/tests/cli/fmt/index.test.ts | 13 +++++++ .../tests/fmt/__snapshots__/cli.test.ts.snap | 15 ++++---- packages/rstack/tests/fmt/cli.test.ts | 14 ++++++++ website/docs/en/guide/cli/fmt.mdx | 20 ++++++++--- website/docs/zh/guide/cli/fmt.mdx | 20 ++++++++--- 6 files changed, 91 insertions(+), 25 deletions(-) diff --git a/packages/rstack/src/fmt/cli.ts b/packages/rstack/src/fmt/cli.ts index 9abd0de..22e3ae8 100644 --- a/packages/rstack/src/fmt/cli.ts +++ b/packages/rstack/src/fmt/cli.ts @@ -12,6 +12,7 @@ interface ParsedFmtCLIArgs { mode: FmtMode; patterns: string[]; ignorePaths: string[]; + noErrorOnUnmatchedPattern: boolean; maxWorkers?: number; help: boolean; /** Path the stdin content is formatted as; it need not exist on disk. */ @@ -26,13 +27,14 @@ ${color.yellow(' $ rs fmt [options] [files/globs...]')} Format files with Prettier. ${color.cyan('Options')}: - --write Write formatted files in place (default) - --check Check whether files are formatted - --list-different Print paths of unformatted files - --ignore-path Path to an additional ignore file (repeatable) - --parallel-workers Number of parallel workers - --stdin-filepath Format stdin as if it were saved at - -h, --help Display this help message`; + --write Write formatted files in place (default) + --check Check whether files are formatted + --list-different Print paths of unformatted files + --ignore-path Path to an additional ignore file (repeatable) + --no-error-on-unmatched-pattern Do not error when no files match + --parallel-workers Number of parallel workers + --stdin-filepath Format stdin as if it were saved at + -h, --help Display this help message`; const parseMaxWorkers = ( kebabValue: string | undefined, @@ -60,6 +62,8 @@ const parseFmtCLIArgs = (args: string[]): ParsedFmtCLIArgs => { 'list-different': { type: 'boolean' }, listDifferent: { type: 'boolean' }, 'ignore-path': { type: 'string', multiple: true }, + 'no-error-on-unmatched-pattern': { type: 'boolean' }, + noErrorOnUnmatchedPattern: { type: 'boolean' }, 'parallel-workers': { type: 'string' }, parallelWorkers: { type: 'string' }, 'stdin-filepath': { type: 'string' }, @@ -77,6 +81,8 @@ const parseFmtCLIArgs = (args: string[]): ParsedFmtCLIArgs => { } const mode = values.check ? 'check' : listDifferent ? 'list-different' : 'write'; + const noErrorOnUnmatchedPattern = + values['no-error-on-unmatched-pattern'] ?? values.noErrorOnUnmatchedPattern ?? false; const maxWorkers = parseMaxWorkers(values['parallel-workers'], values.parallelWorkers); const stdinFilepath = values['stdin-filepath'] ?? values.stdinFilepath; @@ -96,6 +102,7 @@ const parseFmtCLIArgs = (args: string[]): ParsedFmtCLIArgs => { mode, patterns: positionals, ignorePaths: values['ignore-path'] ?? [], + noErrorOnUnmatchedPattern, maxWorkers, help: values.help ?? false, stdinFilepath, @@ -214,7 +221,15 @@ const runFmtCLI = async (args: string[]): Promise => { // Argument errors are reported like every other failure so that a single // exit code identifies "rs fmt refused to run". try { - const { help, ignorePaths, maxWorkers, mode, patterns, stdinFilepath } = parseFmtCLIArgs(args); + const { + help, + ignorePaths, + maxWorkers, + mode, + noErrorOnUnmatchedPattern, + patterns, + stdinFilepath, + } = parseFmtCLIArgs(args); if (help) { logger.log(fmtHelpMessage); return; @@ -243,6 +258,9 @@ const runFmtCLI = async (args: string[]): Promise => { }); if (files.length === 0) { + if (noErrorOnUnmatchedPattern) { + return; + } const targets = (patterns.length ? patterns : ['.']) .map((pattern) => color.cyan(JSON.stringify(pattern))) .join(', '); diff --git a/packages/rstack/tests/cli/fmt/index.test.ts b/packages/rstack/tests/cli/fmt/index.test.ts index 285b5d2..e048934 100644 --- a/packages/rstack/tests/cli/fmt/index.test.ts +++ b/packages/rstack/tests/cli/fmt/index.test.ts @@ -597,3 +597,16 @@ test('returns exit code 2 when no files match', () => { expect(result.stderr).not.toContain('\n at '); } }); + +test.each(['--no-error-on-unmatched-pattern', '--noErrorOnUnmatchedPattern'])( + 'allows no files to match with %s', + (option) => { + for (const modeArgs of [[], ['--check'], ['--list-different']]) { + const result = runFmt([...modeArgs, option, 'missing/**/*.ts']); + + expect(result.status).toBe(0); + expect(result.stdout).toBe(''); + expect(result.stderr).toBe(''); + } + }, +); diff --git a/packages/rstack/tests/fmt/__snapshots__/cli.test.ts.snap b/packages/rstack/tests/fmt/__snapshots__/cli.test.ts.snap index b22c256..e794c9d 100644 --- a/packages/rstack/tests/fmt/__snapshots__/cli.test.ts.snap +++ b/packages/rstack/tests/fmt/__snapshots__/cli.test.ts.snap @@ -7,11 +7,12 @@ exports[`provides command help 1`] = ` Format files with Prettier. Options: - --write Write formatted files in place (default) - --check Check whether files are formatted - --list-different Print paths of unformatted files - --ignore-path Path to an additional ignore file (repeatable) - --parallel-workers Number of parallel workers - --stdin-filepath Format stdin as if it were saved at - -h, --help Display this help message" + --write Write formatted files in place (default) + --check Check whether files are formatted + --list-different Print paths of unformatted files + --ignore-path Path to an additional ignore file (repeatable) + --no-error-on-unmatched-pattern Do not error when no files match + --parallel-workers Number of parallel workers + --stdin-filepath Format stdin as if it were saved at + -h, --help Display this help message" `; diff --git a/packages/rstack/tests/fmt/cli.test.ts b/packages/rstack/tests/fmt/cli.test.ts index 61cf2f4..bd6339e 100644 --- a/packages/rstack/tests/fmt/cli.test.ts +++ b/packages/rstack/tests/fmt/cli.test.ts @@ -22,6 +22,7 @@ test('uses write mode by default', () => { mode: 'write', patterns: [], ignorePaths: [], + noErrorOnUnmatchedPattern: false, maxWorkers: undefined, help: false, }); @@ -37,6 +38,7 @@ test.each([ mode, patterns: [], ignorePaths: [], + noErrorOnUnmatchedPattern: false, maxWorkers: undefined, help: false, }); @@ -49,6 +51,7 @@ test.each(['--parallel-workers', '--parallelWorkers'])( mode: 'write', patterns: [], ignorePaths: [], + noErrorOnUnmatchedPattern: false, maxWorkers: 3, help: false, }); @@ -75,6 +78,7 @@ test('preserves file paths and globs', () => { mode: 'check', patterns, ignorePaths: [], + noErrorOnUnmatchedPattern: false, maxWorkers: undefined, help: false, }); @@ -85,6 +89,7 @@ test('treats arguments after the terminator as paths', () => { mode: 'check', patterns: ['--write', '--help'], ignorePaths: [], + noErrorOnUnmatchedPattern: false, maxWorkers: undefined, help: false, }); @@ -101,11 +106,19 @@ test('collects repeated ignore paths', () => { ).toEqual(['.prettierignore', 'config/format.ignore']); }); +test.each(['--no-error-on-unmatched-pattern', '--noErrorOnUnmatchedPattern'])( + 'parses %s', + (option) => { + expect(parseFmtCLIArgs([option]).noErrorOnUnmatchedPattern).toBe(true); + }, +); + test.each(['--stdin-filepath', '--stdinFilepath'])('parses %s', (option) => { expect(parseFmtCLIArgs([option, 'src/index.ts'])).toEqual({ mode: 'write', patterns: [], ignorePaths: [], + noErrorOnUnmatchedPattern: false, maxWorkers: undefined, help: false, stdinFilepath: 'src/index.ts', @@ -117,6 +130,7 @@ test('accepts a worker count with --stdin-filepath', () => { mode: 'write', patterns: [], ignorePaths: [], + noErrorOnUnmatchedPattern: false, maxWorkers: 2, help: false, stdinFilepath: 'index.ts', diff --git a/website/docs/en/guide/cli/fmt.mdx b/website/docs/en/guide/cli/fmt.mdx index 4276ae2..43a9975 100644 --- a/website/docs/en/guide/cli/fmt.mdx +++ b/website/docs/en/guide/cli/fmt.mdx @@ -43,11 +43,11 @@ rs fmt . --check The command uses the following exit codes: -| Code | Meaning | -| ---- | --------------------------------------------------------- | -| `0` | All matched files are formatted. | -| `1` | One or more matched files have formatting issues. | -| `2` | `rs fmt` could not run or encountered a formatting error. | +| Code | Meaning | +| ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `0` | All matched files are formatted; or no supported files matched, but [`--no-error-on-unmatched-pattern`](#--no-error-on-unmatched-pattern) was specified. | +| `1` | One or more matched files have formatting issues. | +| `2` | `rs fmt` could not run or encountered a formatting error. | ### `-h, --help` @@ -97,6 +97,16 @@ rs fmt . --list-different The option uses the same exit codes as `--check` and cannot be combined with `--write` or `--check`. +### `--no-error-on-unmatched-pattern` + +Exit successfully without diagnostics when no supported files match the provided paths or globs, including when all matching files are ignored: + +```bash +rs fmt --no-error-on-unmatched-pattern 'src/**/*.ts' +``` + +For example, a pre-commit script may always run `rs fmt`, even when the staged changes contain no supported files. This option lets the command exit successfully in that case instead of blocking the commit. + ### `--parallel-workers ` Set the maximum number of formatting workers to a positive integer: diff --git a/website/docs/zh/guide/cli/fmt.mdx b/website/docs/zh/guide/cli/fmt.mdx index a6c06a4..f3bd105 100644 --- a/website/docs/zh/guide/cli/fmt.mdx +++ b/website/docs/zh/guide/cli/fmt.mdx @@ -43,11 +43,11 @@ rs fmt . --check 该命令使用以下退出状态码: -| 状态码 | 含义 | -| ------ | ------------------------------------------- | -| `0` | 所有匹配的文件均已格式化。 | -| `1` | 一个或多个匹配的文件存在格式问题。 | -| `2` | `rs fmt` 无法运行或在格式化过程中遇到错误。 | +| 状态码 | 含义 | +| ------ | -------------------------------------------------------------------------------------------------------------------------------- | +| `0` | 所有匹配的文件均已格式化;或未匹配到支持的文件,但指定了 [`--no-error-on-unmatched-pattern`](#--no-error-on-unmatched-pattern)。 | +| `1` | 一个或多个匹配的文件存在格式问题。 | +| `2` | `rs fmt` 无法运行或在格式化过程中遇到错误。 | ### `-h, --help` @@ -97,6 +97,16 @@ rs fmt . --list-different 此选项与 `--check` 使用相同的退出状态码,且不能与 `--write` 或 `--check` 同时使用。 +### `--no-error-on-unmatched-pattern` + +如果传入的路径或 glob 没有匹配任何支持的文件(包括所有匹配文件均被忽略的情况),则不输出诊断信息并成功退出: + +```bash +rs fmt --no-error-on-unmatched-pattern 'src/**/*.ts' +``` + +例如,pre-commit 脚本可能会始终运行 `rs fmt`,即使暂存的改动中没有支持的文件。此选项可让命令在这种情况下成功退出,避免阻止提交。 + ### `--parallel-workers ` 将格式化 worker 的最大数量设置为正整数: