Skip to content

Commit e9fd92e

Browse files
authored
Merge pull request #112 from jsr-core/gen-mod
docs: add documentations of `is` and `as`
2 parents 248bdc4 + b673fbd commit e9fd92e

File tree

5 files changed

+1153
-7
lines changed

5 files changed

+1153
-7
lines changed

.github/workflows/test.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ jobs:
2424
run: deno lint
2525
- name: Type check
2626
run: deno task check
27+
- name: Gen check
28+
run: |
29+
deno task gen
30+
git diff --exit-code
2731
2832
test:
2933
runs-on: ubuntu-latest

.scripts/gen-mod.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { fromFileUrl, globToRegExp, join, relative } from "@std/path";
2+
import { map } from "@core/iterutil/map";
3+
import { flatMap } from "@core/iterutil/async/flat-map";
4+
5+
const decoder = new TextDecoder();
6+
7+
const excludes = [
8+
"mod.ts",
9+
"*_test.ts",
10+
"*_bench.ts",
11+
"_*.ts",
12+
];
13+
14+
type DenoDocEntry = {
15+
name: string;
16+
location: {
17+
filename: string;
18+
};
19+
declarationKind: string;
20+
jsDoc: {
21+
doc: string;
22+
};
23+
kind: string;
24+
};
25+
26+
function isDenoDocEntry(x: unknown): x is DenoDocEntry {
27+
if (x == null || typeof x !== "object") return false;
28+
if (typeof (x as DenoDocEntry).name !== "string") return false;
29+
if (typeof (x as DenoDocEntry).location !== "object") return false;
30+
if (typeof (x as DenoDocEntry).location.filename !== "string") return false;
31+
if (typeof (x as DenoDocEntry).declarationKind !== "string") return false;
32+
if (typeof (x as DenoDocEntry).jsDoc !== "object") return false;
33+
if (typeof (x as DenoDocEntry).jsDoc.doc !== "string") return false;
34+
if (typeof (x as DenoDocEntry).kind !== "string") return false;
35+
return true;
36+
}
37+
38+
async function listDenoDocEntries(path: string): Promise<DenoDocEntry[]> {
39+
const cmd = new Deno.Command(Deno.execPath(), {
40+
args: ["doc", "--json", path],
41+
stdout: "piped",
42+
stderr: "piped",
43+
});
44+
const { success, stdout, stderr } = await cmd.output();
45+
if (!success) {
46+
throw new Error(decoder.decode(stderr));
47+
}
48+
const json = JSON.parse(decoder.decode(stdout));
49+
if (!Array.isArray(json)) {
50+
throw new Error(`Expected array but got ${JSON.stringify(json)}`);
51+
}
52+
return json.filter(isDenoDocEntry);
53+
}
54+
55+
async function* iterModules(path: string): AsyncIterable<string> {
56+
const patterns = excludes.map((p) => globToRegExp(p));
57+
for await (const entry of Deno.readDir(path)) {
58+
if (!entry.isFile || !entry.name.endsWith(".ts")) continue;
59+
if (patterns.some((p) => p.test(entry.name))) continue;
60+
yield join(path, entry.name);
61+
}
62+
}
63+
64+
async function generateModTs(
65+
namespace: string,
66+
): Promise<void> {
67+
const path = fromFileUrl(import.meta.resolve(`../${namespace}/`));
68+
const exports = (await Array.fromAsync(
69+
flatMap(iterModules(path), (x) => listDenoDocEntries(x)),
70+
))
71+
.filter((x) => x.kind === "function")
72+
.filter((x) => x.declarationKind === "export")
73+
.filter((x) => x.name.startsWith(namespace))
74+
.map((x) => ({
75+
path: relative(path, fromFileUrl(x.location.filename)),
76+
name: x.name,
77+
doc: x.jsDoc.doc,
78+
}))
79+
.toSorted((a, b) => a.name.localeCompare(b.name));
80+
const lines = [
81+
"// NOTE: This file is generated by gen-mod.ts",
82+
...exports.map((x) => {
83+
return `import { ${x.name} } from "./${x.path}";`;
84+
}),
85+
"",
86+
...map((new Set(exports.map((x) => x.path))).values(), (x) => {
87+
return `export * from "./${x}";`;
88+
}),
89+
"",
90+
"/**",
91+
` * An object containing all the functions in ${namespace} module.`,
92+
" */",
93+
`export const ${namespace}: {`,
94+
...exports.flatMap((x) => {
95+
return [
96+
" /**",
97+
...x.doc.split("\n").map((line) => ` * ${line}`.trimEnd()),
98+
" */",
99+
` ${x.name.replace(namespace, "")}: typeof ${x.name};`.trimEnd(),
100+
];
101+
}),
102+
"} = {",
103+
...exports.flatMap((x) => {
104+
return [
105+
` ${x.name.replace(namespace, "")}: ${x.name},`.trimEnd(),
106+
];
107+
}),
108+
"};",
109+
];
110+
await Deno.writeTextFile(join(path, "mod.ts"), lines.join("\n") + "\n");
111+
}
112+
113+
async function main(): Promise<void> {
114+
await generateModTs("is");
115+
await generateModTs("as");
116+
}
117+
118+
if (import.meta.main) {
119+
main().catch((err) => {
120+
console.error(err);
121+
Deno.exit(1);
122+
});
123+
}

as/mod.ts

Lines changed: 103 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,109 @@
1-
import { asOptional, asUnoptional } from "./optional.ts";
2-
import { asReadonly, asUnreadonly } from "./readonly.ts";
1+
// NOTE: This file is generated by gen-mod.ts
2+
import { asOptional } from "./optional.ts";
3+
import { asReadonly } from "./readonly.ts";
4+
import { asUnoptional } from "./optional.ts";
5+
import { asUnreadonly } from "./readonly.ts";
6+
7+
export * from "./optional.ts";
8+
export * from "./readonly.ts";
39

410
/**
5-
* Annotation collection for object predicate properties.
11+
* An object containing all the functions in as module.
612
*/
7-
export const as = {
13+
export const as: {
14+
/**
15+
* Annotate the given predicate function as optional.
16+
*
17+
* Use this function to annotate a predicate function of `predObj` in {@linkcode isObjectOf}.
18+
*
19+
* Note that the annotated predicate function will return `true` if the type of `x` is `T` or `undefined`, indicating that
20+
* this function is not just for annotation but it also changes the behavior of the predicate function.
21+
*
22+
* Use {@linkcode asUnoptional} to remove the annotation.
23+
* Use {@linkcode hasOptional} to check if a predicate function has annotated with this function.
24+
*
25+
* To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost.
26+
*
27+
* ```ts
28+
* import { as, is } from "@core/unknownutil";
29+
*
30+
* const isMyType = is.ObjectOf({
31+
* foo: as.Optional(is.String),
32+
* });
33+
* const a: unknown = {};
34+
* if (isMyType(a)) {
35+
* const _: {foo?: string} = a;
36+
* }
37+
* ```
38+
*/
39+
Optional: typeof asOptional;
40+
/**
41+
* Annotate the given predicate function as readonly.
42+
*
43+
* Use this function to annotate a predicate function of `predObj` in {@linkcode isObjectOf}.
44+
*
45+
* Use {@linkcode asUnreadonly} to remove the annotation.
46+
* Use {@linkcode hasReadonly} to check if a predicate function has annotated with this function.
47+
*
48+
* To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost.
49+
*
50+
* ```ts
51+
* import { as, is } from "@core/unknownutil";
52+
*
53+
* const isMyType = is.ObjectOf({
54+
* foo: as.Readonly(is.String),
55+
* });
56+
* const a: unknown = {};
57+
* if (isMyType(a)) {
58+
* const _: {readonly foo: string} = a;
59+
* }
60+
* ```
61+
*/
62+
Readonly: typeof asReadonly;
63+
/**
64+
* Unannotate the annotated predicate function with {@linkcode asOptional}.
65+
*
66+
* Use this function to unannotate a predicate function of `predObj` in {@linkcode isObjectOf}.
67+
*
68+
* Note that the annotated predicate function will return `true` if the type of `x` is `T`, indicating that
69+
* this function is not just for annotation but it also changes the behavior of the predicate function.
70+
*
71+
* To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost.
72+
*
73+
* ```ts
74+
* import { as, is } from "@core/unknownutil";
75+
*
76+
* const isMyType = is.ObjectOf({
77+
* foo: as.Unoptional(as.Optional(is.String)),
78+
* });
79+
* const a: unknown = {foo: "a"};
80+
* if (isMyType(a)) {
81+
* const _: {foo: string} = a;
82+
* }
83+
* ```
84+
*/
85+
Unoptional: typeof asUnoptional;
86+
/**
87+
* Unannotate the annotated predicate function with {@linkcode asReadonly}.
88+
*
89+
* Use this function to unannotate a predicate function of `predObj` in {@linkcode isObjectOf}.
90+
*
91+
* To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost.
92+
*
93+
* ```ts
94+
* import { as, is } from "@core/unknownutil";
95+
*
96+
* const isMyType = is.ObjectOf({
97+
* foo: as.Unreadonly(as.Readonly(is.String)),
98+
* });
99+
* const a: unknown = {foo: "a"};
100+
* if (isMyType(a)) {
101+
* const _: {foo: string} = a;
102+
* }
103+
* ```
104+
*/
105+
Unreadonly: typeof asUnreadonly;
106+
} = {
8107
Optional: asOptional,
9108
Readonly: asReadonly,
10109
Unoptional: asUnoptional,

deno.jsonc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
]
6767
},
6868
"imports": {
69+
"@core/iterutil": "jsr:@core/iterutil@^0.3.0",
6970
"@core/unknownutil": "./mod.ts",
7071
"@deno/dnt": "jsr:@deno/dnt@^0.41.1",
7172
"@std/assert": "jsr:@std/assert@^0.221.0",
@@ -78,6 +79,7 @@
7879
"test": "deno test -A --doc --parallel --shuffle",
7980
"test:coverage": "deno task test --coverage=.coverage",
8081
"coverage": "deno coverage .coverage",
82+
"gen": "deno run --allow-run=deno --allow-read --allow-write=. .scripts/gen-mod.ts",
8183
"update": "deno run --allow-env --allow-read --allow-write=. --allow-run=git,deno --allow-net=jsr.io,registry.npmjs.org jsr:@molt/cli ./*.ts",
8284
"update:commit": "deno task -q update --commit --prefix deps: --pre-commit=fmt,lint"
8385
}

0 commit comments

Comments
 (0)