Skip to content

Commit 3525ebc

Browse files
authored
feat(staged): allow unmatched rs fmt tasks (#188)
1 parent 83e3d7d commit 3525ebc

6 files changed

Lines changed: 50 additions & 2 deletions

File tree

packages/rstack/src/fmt/cli.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,9 @@ const runFmtCLI = async (args: string[]): Promise<void> => {
262262
});
263263

264264
if (files.length === 0) {
265-
if (noErrorOnUnmatchedPattern) {
265+
// Staged tasks may pass only paths excluded by formatter ignore rules.
266+
const allowUnmatched = noErrorOnUnmatchedPattern || process.env.RSTACK_STAGED === '1';
267+
if (allowUnmatched) {
266268
return;
267269
}
268270
reportNoSupportedFiles(patterns);

packages/rstack/src/staged.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ export async function runStagedCLI(args: string[]): Promise<void> {
7070
);
7171
}
7272

73+
// Let child commands detect that they are running through `rs staged`.
74+
process.env.RSTACK_STAGED = '1';
75+
7376
const success = await lintStaged({
7477
allowEmpty: values.allowEmpty,
7578
concurrent: values.concurrent === undefined ? undefined : JSON.parse(values.concurrent),

packages/rstack/tests/cli/staged/fmt.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,29 @@ test('formats staged files with rs fmt and applies ignore rules', () => {
8181
expect(git(['show', ':ignored-by-git.ts'])).toBe('const gitIgnored = "git ignored";\n');
8282
});
8383

84+
test('allows rs fmt when all staged files are ignored', () => {
85+
const source = 'const fmtIgnored="fmt ignored"';
86+
writeProjectFile('ignored-by-fmt.ts', source);
87+
git(['add', '--', 'ignored-by-fmt.ts']);
88+
89+
const result = runStaged();
90+
91+
expect(result.status).toBe(0);
92+
expect(readProjectFile('ignored-by-fmt.ts')).toBe(source);
93+
expect(git(['show', ':ignored-by-fmt.ts'])).toBe(source);
94+
expect(`${result.stdout}\n${result.stderr}`).not.toContain('No supported files matched');
95+
});
96+
97+
test('still rejects staged files unsupported by rs fmt', () => {
98+
writeProjectFile('notes.unknown', 'plain text');
99+
git(['add', '--', 'notes.unknown']);
100+
101+
const result = runStaged();
102+
103+
expect(result.status).toBe(1);
104+
expect(`${result.stdout}\n${result.stderr}`).toContain('No supported files matched');
105+
});
106+
84107
test('propagates rs fmt failures', () => {
85108
writeProjectFile('invalid.ts', 'const value = ;');
86109
git(['add', '--', 'invalid.ts']);

packages/rstack/tests/cli/staged/index.test.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import lintStaged from 'lint-staged';
2-
import { beforeEach, rs } from 'rstack/test';
2+
import { afterEach, beforeEach, rs } from 'rstack/test';
33
import { test } from '#test-helpers';
44
import { loadRstackConfig } from '../../../src/config.ts';
55
import { runStagedCLI, type StagedConfig } from '../../../src/staged.ts';
@@ -17,6 +17,7 @@ const stagedConfig: StagedConfig = {
1717
};
1818

1919
beforeEach(() => {
20+
delete process.env.RSTACK_STAGED;
2021
rs.resetAllMocks();
2122
mocks.lintStaged.mockResolvedValue(true);
2223
mocks.loadRstackConfig.mockResolvedValue({
@@ -26,6 +27,10 @@ beforeEach(() => {
2627
});
2728
});
2829

30+
afterEach(() => {
31+
delete process.env.RSTACK_STAGED;
32+
});
33+
2934
test('should display the staged help message', ({ execCli, expect }) => {
3035
const output = execCli('staged --help');
3136

@@ -62,6 +67,17 @@ test('should pass default options to lint-staged', async ({ expect }) => {
6267
});
6368
});
6469

70+
test('should set the staged environment', async ({ expect }) => {
71+
mocks.lintStaged.mockImplementation(async () => {
72+
expect(process.env.RSTACK_STAGED).toBe('1');
73+
return true;
74+
});
75+
76+
await runStagedCLI([]);
77+
78+
expect(process.env.RSTACK_STAGED).toBe('1');
79+
});
80+
6581
test('should pass long options to lint-staged', async ({ expect }) => {
6682
await runStagedCLI([
6783
'--allow-empty',

website/docs/en/guide/cli/fmt.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ rs fmt --no-error-on-unmatched-pattern 'src/**/*.ts'
107107

108108
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.
109109

110+
> [`rs staged`](./staged) enables this behavior automatically for its `rs fmt` tasks.
111+
110112
### `--parallel-workers <count>`
111113

112114
Set the maximum number of formatting workers to a positive integer:

website/docs/zh/guide/cli/fmt.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ rs fmt --no-error-on-unmatched-pattern 'src/**/*.ts'
107107

108108
例如,pre-commit 脚本可能会始终运行 `rs fmt`,即使暂存的改动中没有支持的文件。此选项可让命令在这种情况下成功退出,避免阻止提交。
109109

110+
> [`rs staged`](./staged) 会为其中的 `rs fmt` 任务自动启用此行为。
111+
110112
### `--parallel-workers <count>`
111113

112114
将格式化 worker 的最大数量设置为正整数:

0 commit comments

Comments
 (0)