Skip to content

Commit e98b6f3

Browse files
Per binary supported arguments
1 parent 0f1961c commit e98b6f3

10 files changed

Lines changed: 190 additions & 45 deletions

src/cli/cli.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export async function main(cli: CLI): Promise<void> {
2121
}
2222

2323
async function mainUnsafe(cli: CLI): Promise<number> {
24-
const args = parseArgs(process.argv.slice(2));
24+
const args = parseArgs(cli, process.argv.slice(2));
2525

2626
if (args.help) {
2727
printHelp(cli);

src/cli/snippetFormatter.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { main } from "./cli.js";
66
void main({
77
binName: "snippet-fmt",
88
fileEndings: ["snippet"],
9+
supportedFlagArgs: [],
10+
supportedValueArgs: [],
911

1012
format: async (text) => {
1113
const updated = snippetFormatter(text);

src/cli/talonFormatter.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { main } from "./cli.js";
88
void main({
99
binName: "talon-fmt",
1010
fileEndings: ["talon", "talon-list"],
11+
supportedFlagArgs: ["--indent-tabs"],
12+
supportedValueArgs: ["--indent-width", "--column-width"],
1113

1214
format: async (text, options, fileName) => {
1315
if (isListFile(text, fileName)) {

src/cli/treeSitterFormatter.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { main } from "./cli.js";
77
void main({
88
binName: "tree-sitter-fmt",
99
fileEndings: ["scm"],
10+
supportedFlagArgs: ["--indent-tabs"],
11+
supportedValueArgs: ["--indent-width"],
1012

1113
format: async (text, options) => {
1214
const node = await parseText(text, "tree-sitter-query");

src/test/cli.test.ts

Lines changed: 123 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
getDefaultOptions,
1212
} from "../util/getDefaultArguments.js";
1313
import { parseArgs } from "../util/parseArgs.js";
14+
import { printHelp } from "../util/printHelp.js";
1415

1516
suite("CLI", () => {
1617
test("formats a file in place", async () => {
@@ -180,6 +181,8 @@ suite("CLI", () => {
180181
const cli: CLI = {
181182
binName: "talon-fmt",
182183
fileEndings: ["txt"],
184+
supportedFlagArgs: ["--indent-tabs"],
185+
supportedValueArgs: ["--indent-width", "--column-width"],
183186
format: (text, receivedOptions, receivedFileName) => {
184187
actualText = text;
185188
actualOptions = receivedOptions;
@@ -211,6 +214,8 @@ suite("CLI", () => {
211214
const cli: CLI = {
212215
binName: "talon-fmt",
213216
fileEndings: ["txt"],
217+
supportedFlagArgs: ["--indent-tabs"],
218+
supportedValueArgs: ["--indent-width", "--column-width"],
214219
format: (text, receivedOptions, receivedFileName) => {
215220
actualText = text;
216221
actualOptions = receivedOptions;
@@ -232,7 +237,10 @@ suite("CLI", () => {
232237
filePatterns: ["a.txt", "b.txt"],
233238
check: true,
234239
});
235-
const actual = parseArgs(["--check", "a.txt", "b.txt"]);
240+
const actual = parseArgs(
241+
createCLI(() => ""),
242+
["--check", "a.txt", "b.txt"],
243+
);
236244

237245
assert.deepEqual(actual, expected);
238246
});
@@ -242,7 +250,10 @@ suite("CLI", () => {
242250
filePatterns: ["--check"],
243251
check: true,
244252
});
245-
const actual = parseArgs(["--check", "--", "--check"]);
253+
const actual = parseArgs(
254+
createCLI(() => ""),
255+
["--check", "--", "--check"],
256+
);
246257

247258
assert.deepEqual(actual, expected);
248259
});
@@ -258,38 +269,132 @@ suite("CLI", () => {
258269
lineWidth: 80,
259270
columnWidth: 24,
260271
});
261-
const actual = parseArgs([
262-
"--indent-tabs",
263-
"--indent-width",
264-
"2",
265-
"--line-width",
266-
"80",
267-
"--column-width",
268-
"24",
269-
"a.txt",
270-
]);
272+
const actual = parseArgs(
273+
createCLI(() => ""),
274+
[
275+
"--indent-tabs",
276+
"--indent-width",
277+
"2",
278+
"--column-width",
279+
"24",
280+
"a.txt",
281+
],
282+
);
283+
284+
assert.deepEqual(actual, expected);
285+
});
286+
287+
test("rejects unsupported formatter arguments", () => {
288+
const snippetCli: CLI = {
289+
...createCLI(() => ""),
290+
binName: "snippet-fmt",
291+
supportedFlagArgs: [],
292+
supportedValueArgs: [],
293+
};
294+
295+
assert.throws(
296+
() => parseArgs(snippetCli, ["--indent-width", "2"]),
297+
/Unknown argument: --indent-width/,
298+
);
299+
});
300+
301+
test("rejects unsupported formatter flags", () => {
302+
const snippetCli: CLI = {
303+
...createCLI(() => ""),
304+
binName: "snippet-fmt",
305+
supportedFlagArgs: [],
306+
};
307+
308+
assert.throws(
309+
() => parseArgs(snippetCli, ["--indent-tabs"]),
310+
/Unknown argument: --indent-tabs/,
311+
);
312+
});
313+
314+
test("parses only supported arguments for current cli", () => {
315+
const expected = getArguments({
316+
filePatterns: ["a.txt"],
317+
indentTabs: true,
318+
indentWidth: 2,
319+
columnWidth: 24,
320+
});
321+
const actual = parseArgs(
322+
createCLI(() => ""),
323+
[
324+
"--indent-tabs",
325+
"--indent-width",
326+
"2",
327+
"--column-width",
328+
"24",
329+
"a.txt",
330+
],
331+
);
271332

272333
assert.deepEqual(actual, expected);
273334
});
274335

336+
test("prints help only for supported arguments", async () => {
337+
const cli: CLI = {
338+
binName: "tree-sitter-fmt",
339+
fileEndings: ["scm"],
340+
supportedFlagArgs: ["--indent-tabs"],
341+
supportedValueArgs: ["--indent-width"],
342+
format: (text) => Promise.resolve(text),
343+
};
344+
345+
const output = await captureStreamWrite(process.stdout, () => {
346+
printHelp(cli);
347+
return Promise.resolve();
348+
});
349+
350+
assert.equal(
351+
output.text,
352+
[
353+
"Usage: tree-sitter-fmt [options] [file/dir/glob ...]",
354+
"",
355+
"Flags:",
356+
" --help",
357+
" --version",
358+
" --check",
359+
" --indent-tabs",
360+
"",
361+
"Options:",
362+
" --indent-width <n>",
363+
"",
364+
].join("\n"),
365+
);
366+
});
367+
275368
test("rejects unknown arguments", () => {
276369
assert.throws(
277-
() => parseArgs(["--check", "--write"]),
370+
() =>
371+
parseArgs(
372+
createCLI(() => ""),
373+
["--check", "--write"],
374+
),
278375
/Unknown argument: --write/,
279376
);
280377
});
281378

282379
test("rejects missing width values", () => {
283380
assert.throws(
284-
() => parseArgs(["--indent-width"]),
381+
() =>
382+
parseArgs(
383+
createCLI(() => ""),
384+
["--indent-width"],
385+
),
285386
/Missing value for argument: --indent-width/,
286387
);
287388
});
288389

289390
test("rejects invalid width values", () => {
290391
assert.throws(
291-
() => parseArgs(["--line-width", "0"]),
292-
/Invalid value for --line-width: 0/,
392+
() =>
393+
parseArgs(
394+
createCLI(() => ""),
395+
["--indent-width", "0"],
396+
),
397+
/Invalid value for --indent-width: 0/,
293398
);
294399
});
295400
});
@@ -313,6 +418,8 @@ function createCLI(format: (text: string) => string | Promise<string>): CLI {
313418
return {
314419
binName: "talon-fmt" as const,
315420
fileEndings: ["txt"],
421+
supportedFlagArgs: ["--indent-tabs"],
422+
supportedValueArgs: ["--indent-width", "--column-width"],
316423
format: (text: string) => Promise.resolve(format(text)),
317424
};
318425
}

src/test/parseFilePatterns.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ function createCLI(): CLI {
170170
return {
171171
binName: "talon-fmt",
172172
fileEndings: ["txt"],
173+
supportedFlagArgs: [],
174+
supportedValueArgs: [],
173175
format: (text: string) => Promise.resolve(text),
174176
};
175177
}

src/types.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,26 @@
1+
export const GLOBAL_FLAG_ARGUMENTS = [
2+
"--help",
3+
"--version",
4+
"--check",
5+
] as const;
6+
7+
export const KNOWN_FLAG_ARGUMENTS = ["--indent-tabs"] as const;
8+
9+
export const KNOWN_VALUE_ARGUMENTS = [
10+
"--indent-width",
11+
"--line-width",
12+
"--column-width",
13+
] as const;
14+
15+
export type GlobalFlagArg = (typeof GLOBAL_FLAG_ARGUMENTS)[number];
16+
export type FlagArg = (typeof KNOWN_FLAG_ARGUMENTS)[number];
17+
export type ValueArg = (typeof KNOWN_VALUE_ARGUMENTS)[number];
18+
119
export interface CLI {
220
binName: "snippet-fmt" | "talon-fmt" | "tree-sitter-fmt";
3-
fileEndings: string[];
21+
fileEndings: readonly string[];
22+
supportedFlagArgs: readonly FlagArg[];
23+
supportedValueArgs: readonly ValueArg[];
424

525
format(text: string, options: Options, fileName: string): Promise<string>;
626
}

src/util/parseArgs.ts

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,14 @@
11
/* eslint-disable @typescript-eslint/naming-convention */
22

3-
import type { ParsedArgs } from "../types.js";
3+
import type {
4+
CLI,
5+
FlagArg,
6+
GlobalFlagArg,
7+
ParsedArgs,
8+
ValueArg,
9+
} from "../types.js";
410
import { getDefaultArguments } from "./getDefaultArguments.js";
511

6-
export const KNOWN_FLAG_ARGUMENTS = [
7-
"--help",
8-
"--version",
9-
"--check",
10-
"--indent-tabs",
11-
] as const;
12-
13-
export const KNOWN_VALUE_ARGUMENTS = [
14-
"--indent-width",
15-
"--line-width",
16-
"--column-width",
17-
] as const;
18-
19-
type FlagArg = (typeof KNOWN_FLAG_ARGUMENTS)[number];
20-
type ValueArg = (typeof KNOWN_VALUE_ARGUMENTS)[number];
2112
type KnownArg = FlagArg | ValueArg;
2213

2314
type FlagHandler = (parsedArgs: ParsedArgs) => void;
@@ -28,7 +19,7 @@ type ValueHandler = (
2819
value: string,
2920
) => void;
3021

31-
const FLAG_ARG_HANDLERS: Record<FlagArg, FlagHandler> = {
22+
const GLOBAL_FLAG_ARG_HANDLERS: Record<GlobalFlagArg, FlagHandler> = {
3223
"--help": (parsedArgs) => {
3324
parsedArgs.help = true;
3425
},
@@ -38,6 +29,9 @@ const FLAG_ARG_HANDLERS: Record<FlagArg, FlagHandler> = {
3829
"--check": (parsedArgs) => {
3930
parsedArgs.check = true;
4031
},
32+
};
33+
34+
const FLAG_ARG_HANDLERS: Record<FlagArg, FlagHandler> = {
4135
"--indent-tabs": (parsedArgs) => {
4236
parsedArgs.indentTabs = true;
4337
},
@@ -55,8 +49,10 @@ const VALUE_ARG_HANDLERS: Record<ValueArg, ValueHandler> = {
5549
},
5650
};
5751

58-
export function parseArgs(argv: string[]): ParsedArgs {
52+
export function parseArgs(cli: CLI, argv: string[]): ParsedArgs {
5953
const result = getDefaultArguments();
54+
const supportedFlagArgs = new Set(cli.supportedFlagArgs);
55+
const supportedValueArgs = new Set(cli.supportedValueArgs);
6056

6157
for (let i = 0; i < argv.length; i++) {
6258
const arg = argv[i];
@@ -67,16 +63,24 @@ export function parseArgs(argv: string[]): ParsedArgs {
6763
break;
6864
}
6965

66+
const globalFlagHandler =
67+
GLOBAL_FLAG_ARG_HANDLERS[arg as GlobalFlagArg];
68+
69+
if (globalFlagHandler != null) {
70+
globalFlagHandler(result);
71+
continue;
72+
}
73+
7074
const flagHandler = FLAG_ARG_HANDLERS[arg as FlagArg];
7175

72-
if (flagHandler != null) {
76+
if (flagHandler != null && supportedFlagArgs.has(arg as FlagArg)) {
7377
flagHandler(result);
7478
continue;
7579
}
7680

7781
const valueHandler = VALUE_ARG_HANDLERS[arg as ValueArg];
7882

79-
if (valueHandler != null) {
83+
if (valueHandler != null && supportedValueArgs.has(arg as ValueArg)) {
8084
const value = argv[i + 1];
8185
if (value == null) {
8286
throw new Error(`Missing value for argument: ${arg}`);

src/util/parseFilePatterns.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export async function parseFilePatterns(
5555
return Array.from(seen).sort((a, b) => a.localeCompare(b));
5656
}
5757

58-
function getGlobFileEndingsPattern(fileEndings: string[]): string {
58+
function getGlobFileEndingsPattern(fileEndings: readonly string[]): string {
5959
return fileEndings.length === 1
6060
? fileEndings[0]
6161
: `{${fileEndings.join(",")}}`;

0 commit comments

Comments
 (0)