Skip to content

Commit f351f20

Browse files
authored
fix(fmt): handle unsupported files correctly (#181)
1 parent af69b59 commit f351f20

7 files changed

Lines changed: 101 additions & 23 deletions

File tree

packages/rstack/src/fmt/cli.ts

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,19 @@ const formatFileCount = (count: number, isError = false): string => {
147147
return `${isError ? color.red(formattedCount) : formattedCount} ${count === 1 ? 'file' : 'files'}`;
148148
};
149149

150+
const reportNoSupportedFiles = (patterns: string[]): void => {
151+
const targets = (patterns.length ? patterns : ['.'])
152+
.map((pattern) => color.cyan(JSON.stringify(pattern)))
153+
.join(', ');
154+
logger.error(`No supported files matched ${targets}, or all matching files were ignored.`);
155+
process.exitCode = 2;
156+
};
157+
150158
const logFmtResult = (
151159
result: FmtRunResult,
152160
mode: FmtMode,
153161
cwd: string,
154-
matchedFileCount: number,
162+
processedFileCount: number,
155163
durationSeconds: number,
156164
): void => {
157165
let writtenCount = 0;
@@ -177,12 +185,12 @@ const logFmtResult = (
177185
return;
178186
}
179187

180-
const matchedFiles = formatFileCount(matchedFileCount);
188+
const processedFiles = formatFileCount(processedFileCount);
181189
const time = prettyTime(durationSeconds);
182190
const message =
183191
writtenCount > 0
184-
? `Formatted ${formatCount(writtenCount)} of ${matchedFiles} in ${time}.`
185-
: `Checked ${matchedFiles} in ${time}. No changes needed.`;
192+
? `Formatted ${formatCount(writtenCount)} of ${processedFiles} in ${time}.`
193+
: `Checked ${processedFiles} in ${time}. No changes needed.`;
186194
logger[result.exitCode === 0 ? 'success' : 'info'](message);
187195
return;
188196
}
@@ -193,15 +201,15 @@ const logFmtResult = (
193201

194202
if (differentCount > 0) {
195203
const differentFiles = formatFileCount(differentCount, true);
196-
const matchedFiles = formatFileCount(matchedFileCount);
204+
const processedFiles = formatFileCount(processedFileCount);
197205
const checkOption = color.cyan('--check');
198206
logger.error(
199207
`Formatting issues found in ${differentFiles}. Run without ${checkOption} to fix.`,
200208
);
201-
logger.info(`Checked ${matchedFiles} in ${prettyTime(durationSeconds)}.`);
209+
logger.info(`Checked ${processedFiles} in ${prettyTime(durationSeconds)}.`);
202210
} else if (result.exitCode === 0) {
203211
logger.success(
204-
`Checked ${formatFileCount(matchedFileCount)} in ${prettyTime(durationSeconds)}. No issues found.`,
212+
`Checked ${formatFileCount(processedFileCount)} in ${prettyTime(durationSeconds)}. No issues found.`,
205213
);
206214
}
207215
};
@@ -263,11 +271,7 @@ const runFmtCLI = async (args: string[]): Promise<void> => {
263271
if (noErrorOnUnmatchedPattern) {
264272
return;
265273
}
266-
const targets = (patterns.length ? patterns : ['.'])
267-
.map((pattern) => color.cyan(JSON.stringify(pattern)))
268-
.join(', ');
269-
logger.error(`No supported files matched ${targets}, or all matching files were ignored.`);
270-
process.exitCode = 2;
274+
reportNoSupportedFiles(patterns);
271275
return;
272276
}
273277

@@ -281,8 +285,13 @@ const runFmtCLI = async (args: string[]): Promise<void> => {
281285
maxWorkers,
282286
});
283287

288+
if (result.processedFileCount === 0) {
289+
reportNoSupportedFiles(patterns);
290+
return;
291+
}
292+
284293
const durationSeconds = (performance.now() - startTime) / 1000;
285-
logFmtResult(result, mode, cwd, files.length, durationSeconds);
294+
logFmtResult(result, mode, cwd, result.processedFileCount, durationSeconds);
286295
process.exitCode = result.exitCode;
287296
} catch (error) {
288297
logger.error(error);

packages/rstack/src/fmt/runner.ts

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,23 @@ import type { FmtWorkerPool } from './workerPool.ts';
99

1010
/** Formats one file and reports whether its contents differ. */
1111
type FormatFile = FmtWorkerPool['formatFile'];
12+
type FmtFileOutcome = FmtFileResult | 'unchanged' | 'unsupported';
13+
14+
interface FmtWorkerPoolResult {
15+
files: FmtFileResult[];
16+
processedFileCount: number;
17+
}
1218

1319
/** Converts a formatter outcome into the shared per-file result. */
1420
const runFmtFile = async (
1521
file: FmtFileRequest,
1622
shouldWrite: boolean,
1723
formatFile: FormatFile,
18-
): Promise<FmtFileResult | undefined> => {
24+
): Promise<FmtFileOutcome> => {
1925
try {
2026
const result = await formatFile(file, shouldWrite);
21-
if (result !== 'changed') {
22-
return;
27+
if (result === 'unchanged' || result === 'unsupported') {
28+
return result;
2329
}
2430

2531
return {
@@ -40,15 +46,29 @@ const runFmtFilesInWorkerPool = async (
4046
files: FmtFileRequest[],
4147
shouldWrite: boolean,
4248
maxWorkers?: number,
43-
): Promise<FmtFileResult[]> => {
49+
): Promise<FmtWorkerPoolResult> => {
4450
const { createFmtWorkerPool } = await import('./workerPool.ts');
4551
const workerPool = await createFmtWorkerPool(files.length, maxWorkers);
4652

4753
try {
4854
const results = await Promise.all(
4955
files.map((file) => runFmtFile(file, shouldWrite, workerPool.formatFile)),
5056
);
51-
return results.filter((result): result is FmtFileResult => result !== undefined);
57+
const processedFiles: FmtFileResult[] = [];
58+
let processedFileCount = 0;
59+
60+
for (const result of results) {
61+
if (result === 'unsupported') {
62+
continue;
63+
}
64+
65+
processedFileCount++;
66+
if (result !== 'unchanged') {
67+
processedFiles.push(result);
68+
}
69+
}
70+
71+
return { files: processedFiles, processedFileCount };
5272
} finally {
5373
await workerPool.terminate();
5474
}
@@ -77,12 +97,15 @@ const runFmtFiles = async ({
7797
maxWorkers,
7898
}: RunFmtFilesOptions): Promise<FmtRunResult> => {
7999
const shouldWrite = mode === 'write';
80-
const results =
81-
files.length === 0 ? [] : await runFmtFilesInWorkerPool(files, shouldWrite, maxWorkers);
100+
const result =
101+
files.length === 0
102+
? { files: [], processedFileCount: 0 }
103+
: await runFmtFilesInWorkerPool(files, shouldWrite, maxWorkers);
82104

83105
return {
84-
files: results,
85-
exitCode: getFmtExitCode(results),
106+
...result,
107+
exitCode:
108+
files.length > 0 && result.processedFileCount === 0 ? 2 : getFmtExitCode(result.files),
86109
};
87110
};
88111

packages/rstack/src/fmt/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ type FmtFileResult = SuccessfulFmtFileResult | FailedFmtFileResult;
9696

9797
interface FmtRunResult {
9898
files: FmtFileResult[];
99+
/** Number of processed files, excluding files with no supported parser. */
100+
processedFileCount: number;
99101
/** Recommended CLI exit code. */
100102
exitCode: FmtExitCode;
101103
}

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,3 +610,41 @@ test.each(['--no-error-on-unmatched-pattern', '--noErrorOnUnmatchedPattern'])(
610610
}
611611
},
612612
);
613+
614+
test('counts only supported files', () => {
615+
writeProjectFile('index.ts', 'const value = 1;\n');
616+
writeProjectFile('notes.unknown', 'plain text');
617+
618+
const result = runFmt(['--check', 'index.ts', 'notes.unknown']);
619+
620+
expect(result.status).toBe(0);
621+
expect(normalizeDuration(result.stdout)).toBe(
622+
'start Checking formatting...\nsuccess Checked 1 file in <duration>. No issues found.\n',
623+
);
624+
expect(result.stderr).toBe('');
625+
});
626+
627+
test('returns exit code 2 when all matched files are unsupported', () => {
628+
writeProjectFile('notes.unknown', 'plain text');
629+
630+
for (const modeArgs of [[], ['--check'], ['--list-different']]) {
631+
const result = runFmt([...modeArgs, 'notes.unknown']);
632+
633+
expect(result.status).toBe(2);
634+
expect(result.stdout).not.toContain('success');
635+
expect(result.stderr).toContain(
636+
'No supported files matched "notes.unknown", or all matching files were ignored.',
637+
);
638+
expect(result.stderr).not.toContain('\n at ');
639+
}
640+
});
641+
642+
test('does not treat unsupported files as unmatched patterns', () => {
643+
writeProjectFile('notes.unknown', 'plain text');
644+
645+
const result = runFmt(['--no-error-on-unmatched-pattern', 'notes.unknown']);
646+
647+
expect(result.status).toBe(2);
648+
expect(result.stdout).toBe('');
649+
expect(result.stderr).toContain('No supported files matched "notes.unknown"');
650+
});

packages/rstack/tests/fmt/runner.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ test('does not rewrite unchanged files', async () => {
3131
expect(result).toMatchObject({
3232
exitCode: 0,
3333
files: [],
34+
processedFileCount: 1,
3435
});
3536
expect(statSync(filePath).mtimeMs).toBe(mtimeMs);
3637
});
@@ -46,6 +47,7 @@ test('writes changed files', async () => {
4647
expect(result).toMatchObject({
4748
exitCode: 0,
4849
files: [{ path: filePath, status: 'written' }],
50+
processedFileCount: 1,
4951
});
5052
expect(readFileSync(filePath, 'utf8')).toBe('const value = 1;\n');
5153
});
@@ -75,6 +77,7 @@ for (const mode of ['check', 'list-different'] as const) {
7577
expect(result).toMatchObject({
7678
exitCode: 1,
7779
files: [{ path: filePath, status: 'different' }],
80+
processedFileCount: 1,
7881
});
7982
expect(readFileSync(filePath, 'utf8')).toBe(source);
8083
});
@@ -96,6 +99,7 @@ test('continues after a file fails and gives errors exit-code precedence', async
9699
{ path: invalidPath, status: 'error' },
97100
{ path: validPath, status: 'different' },
98101
],
102+
processedFileCount: 2,
99103
});
100104
expect(readFileSync(validPath, 'utf8')).toBe('const value=1');
101105
});
@@ -113,7 +117,7 @@ test('omits unsupported files from the result', async () => {
113117
},
114118
]);
115119

116-
expect(result).toMatchObject({ exitCode: 0, files: [] });
120+
expect(result).toMatchObject({ exitCode: 2, files: [], processedFileCount: 0 });
117121
expect(readFileSync(filePath, 'utf8')).toBe('plain text');
118122
});
119123
});

packages/rstack/tests/fmt/runnerWorkerPreflight.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ test('does not start the worker pool when there are no files', async () => {
4747
await expect(runFmtFiles({ files: [], mode: 'write' })).resolves.toMatchObject({
4848
files: [],
4949
exitCode: 0,
50+
processedFileCount: 0,
5051
});
5152
expect(mocks.createFmtWorkerPoolCalls).toEqual([]);
5253
});

packages/rstack/tests/fmt/runnerWriteFailure.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ test('returns an error when a file write fails', async () => {
4040
error: { message: 'file write failed' },
4141
},
4242
],
43+
processedFileCount: 1,
4344
});
4445
expect(mocks.terminateCalls).toBe(1);
4546
});

0 commit comments

Comments
 (0)