Skip to content

Commit f17f00d

Browse files
committed
refactor dynamic.ts, remove ScanAction casts
1 parent 0aaa983 commit f17f00d

File tree

5 files changed

+106
-57
lines changed

5 files changed

+106
-57
lines changed

src/command/check/check.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ import { notebookContext } from "../../render/notebook/notebook-context.ts";
4747
import { typstBinaryPath } from "../../core/typst.ts";
4848
import { quartoCacheDir } from "../../core/appdirs.ts";
4949
import { isWindows } from "../../deno_ral/platform.ts";
50-
import { checkStringEnum } from "../../typing/dynamic.ts";
50+
import { makeStringEnumTypeEnforcer } from "../../typing/dynamic.ts";
5151

5252
export const kTargets = [
5353
"install",
@@ -58,7 +58,7 @@ export const kTargets = [
5858
"all",
5959
] as const;
6060
export type Target = typeof kTargets[number];
61-
export const checkTargetType = checkStringEnum(...kTargets);
61+
export const enforceTargetType = makeStringEnumTypeEnforcer(...kTargets);
6262

6363
const kIndent = " ";
6464

src/command/check/cmd.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66

77
import { Command } from "cliffy/command/mod.ts";
8-
import { check, checkTargetType } from "./check.ts";
8+
import { check, enforceTargetType } from "./check.ts";
99

1010
export const checkCommand = new Command()
1111
.name("check")
@@ -20,5 +20,5 @@ export const checkCommand = new Command()
2020
.example("Check installation and all engines", "quarto check all")
2121
.action(async (_options: unknown, targetStr?: string) => {
2222
targetStr = targetStr || "all";
23-
await check(checkTargetType(targetStr));
23+
await check(enforceTargetType(targetStr));
2424
});

src/command/create/editor.ts

Lines changed: 45 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ import {
1616
import { basename, dirname, join } from "../../deno_ral/path.ts";
1717
import { existsSync } from "../../deno_ral/fs.ts";
1818
import { isMac, isWindows } from "../../deno_ral/platform.ts";
19+
import {
20+
enforcer,
21+
makeStringEnumTypeFunctions,
22+
objectPredicate,
23+
stringTypePredicate,
24+
} from "../../typing/dynamic.ts";
1925

2026
export interface Editor {
2127
// A short, command line friendly id
@@ -77,11 +83,14 @@ interface EditorInfo {
7783
open: (path: string, createResult: CreateResult) => () => Promise<void>;
7884
}
7985

80-
interface ScanAction {
81-
action: "path" | "which" | "env";
86+
const scanActionActions = ["path", "which", "env"] as const;
87+
type ScanActionAction = typeof scanActionActions[number];
88+
89+
type ScanAction = {
90+
action: ScanActionAction;
8291
arg: string;
8392
filter?: (path: string) => string;
84-
}
93+
};
8594

8695
function vscodeEditorInfo(): EditorInfo {
8796
const editorInfo: EditorInfo = {
@@ -110,29 +119,26 @@ function vscodeEditorInfo(): EditorInfo {
110119
action: "which",
111120
arg: "code.exe",
112121
});
113-
const pathActions = windowsAppPaths("Microsoft VS Code", "code.exe").map(
114-
(path) => {
115-
return {
116-
action: "path",
117-
arg: path,
118-
} as ScanAction;
119-
},
120-
);
122+
const pathActions: ScanAction[] = windowsAppPaths(
123+
"Microsoft VS Code",
124+
"code.exe",
125+
).map((path) => ({
126+
action: "path",
127+
arg: path,
128+
}));
121129
editorInfo.actions.push(...pathActions);
122130
} else if (isMac) {
123131
editorInfo.actions.push({
124132
action: "which",
125133
arg: "code",
126134
});
127135

128-
const pathActions = macosAppPaths(
136+
const pathActions: ScanAction[] = macosAppPaths(
129137
"Visual Studio Code.app/Contents/Resources/app/bin/code",
130-
).map((path) => {
131-
return {
132-
action: "path",
133-
arg: path,
134-
} as ScanAction;
135-
});
138+
).map((path) => ({
139+
action: "path",
140+
arg: path,
141+
}));
136142
editorInfo.actions.push(...pathActions);
137143
} else {
138144
editorInfo.actions.push({
@@ -174,13 +180,14 @@ function positronEditorInfo(): EditorInfo {
174180
action: "which",
175181
arg: "Positron.exe",
176182
});
177-
const pathActions = windowsAppPaths("Positron", "Positron.exe").map(
178-
(path) => {
179-
return {
180-
action: "path",
181-
arg: path,
182-
} as ScanAction;
183-
},
183+
const pathActions: ScanAction[] = windowsAppPaths(
184+
"Positron",
185+
"Positron.exe",
186+
).map(
187+
(path) => ({
188+
action: "path",
189+
arg: path,
190+
}),
184191
);
185192
editorInfo.actions.push(...pathActions);
186193
} else if (isMac) {
@@ -189,13 +196,13 @@ function positronEditorInfo(): EditorInfo {
189196
arg: "positron",
190197
});
191198

192-
const pathActions = macosAppPaths(
199+
const pathActions: ScanAction[] = macosAppPaths(
193200
"Positron.app/Contents/Resources/app/bin/code",
194201
).map((path) => {
195202
return {
196203
action: "path",
197204
arg: path,
198-
} as ScanAction;
205+
};
199206
});
200207
editorInfo.actions.push(...pathActions);
201208
} else {
@@ -249,22 +256,19 @@ function rstudioEditorInfo(): EditorInfo {
249256
},
250257
});
251258

252-
const paths = windowsAppPaths(join("RStudio", "bin"), rstudioExe).map(
253-
(path) => {
254-
return {
255-
action: "path",
256-
arg: path,
257-
} as ScanAction;
258-
},
259-
);
259+
const paths: ScanAction[] = windowsAppPaths(
260+
join("RStudio", "bin"),
261+
rstudioExe,
262+
).map((path) => ({
263+
action: "path",
264+
arg: path,
265+
}));
260266
editorInfo.actions.push(...paths);
261267
} else if (isMac) {
262-
const paths = macosAppPaths("RStudio.app").map((path) => {
263-
return {
264-
action: "path",
265-
arg: path,
266-
} as ScanAction;
267-
});
268+
const paths: ScanAction[] = macosAppPaths("RStudio.app").map((path) => ({
269+
action: "path",
270+
arg: path,
271+
}));
268272
editorInfo.actions.push(...paths);
269273
} else {
270274
editorInfo.actions.push({

src/typing/dynamic.ts

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,62 @@
1111

1212
import { DynamicTypeCheckError } from "../core/lib/error.ts";
1313

14-
export const checkStringEnum = <T extends string>(
14+
export const makeStringEnumTypeFunctions = <T extends string>(
1515
...values: T[]
16-
): (value: string) => T => {
16+
): {
17+
predicate: (value: unknown) => value is T;
18+
enforce: (value: unknown) => T;
19+
} => {
1720
const valueSet: Set<string> = new Set(values);
18-
return (value: string): T => {
19-
if (!valueSet.has(value)) {
20-
throw new DynamicTypeCheckError(
21-
"Invalid value '" + value + "' (valid values are " +
22-
values.join(", ") + ").",
23-
);
21+
const predicate = (value: unknown): value is T => {
22+
return typeof value === "string" && valueSet.has(value);
23+
};
24+
const enforce = (value: unknown): T => {
25+
if (predicate(value)) {
26+
return value;
27+
}
28+
throw new DynamicTypeCheckError(
29+
"Invalid value '" + value + "' (valid values are " +
30+
values.join(", ") + ").",
31+
);
32+
};
33+
return { predicate, enforce };
34+
};
35+
36+
export const makeStringEnumTypeEnforcer = <T extends string>(
37+
...values: T[]
38+
): (value: unknown) => T => {
39+
return makeStringEnumTypeFunctions(...values).enforce;
40+
};
41+
42+
export const enforcer = <T>(
43+
predicate: (value: unknown) => value is T,
44+
msg?: (value: unknown) => string,
45+
) => {
46+
if (!msg) {
47+
msg = (_value: unknown) => "Invalid value.";
48+
}
49+
return (value: unknown): T => {
50+
if (predicate(value)) {
51+
return value;
2452
}
25-
return value as unknown as T;
53+
throw new DynamicTypeCheckError(msg(value));
2654
};
2755
};
56+
57+
export const enforceStringType = (value: unknown): string => {
58+
if (stringTypePredicate(value)) {
59+
return value;
60+
}
61+
throw new DynamicTypeCheckError("Expected a string.");
62+
};
63+
64+
export const stringTypePredicate = (value: unknown): value is string => {
65+
return typeof value === "string";
66+
};
67+
68+
export const objectPredicate = (
69+
value: unknown,
70+
): value is Record<string, unknown> => {
71+
return typeof value === "object" && value !== null;
72+
};

tests/unit/typing/dynamic.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
*
66
*/
77

8-
import { checkStringEnum } from "../../../src/typing/dynamic.ts";
8+
import { makeStringEnumTypeEnforcer } from "../../../src/typing/dynamic.ts";
99
import { unitTest } from "../../test.ts";
1010
import { assert, assertThrows } from "testing/asserts";
1111

1212
// deno-lint-ignore require-await
13-
unitTest("checkStringEnum", async () => {
14-
const check = checkStringEnum("a", "b", "c");
13+
unitTest("makeStringEnumTypeEnforcer", async () => {
14+
const check = makeStringEnumTypeEnforcer("a", "b", "c");
1515
assert(check("a") === "a");
1616
assert(check("b") === "b");
1717
assert(check("c") === "c");

0 commit comments

Comments
 (0)