Skip to content

feat: export all exports in module and fix collection doc #112

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ jobs:
run: deno lint
- name: Type check
run: deno task check
- name: Gen check
run: |
deno task gen
git diff --exit-code

test:
runs-on: ubuntu-latest
Expand Down
123 changes: 123 additions & 0 deletions .scripts/gen-mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { fromFileUrl, globToRegExp, join, relative } from "@std/path";
import { map } from "@core/iterutil/map";
import { flatMap } from "@core/iterutil/async/flat-map";

const decoder = new TextDecoder();

const excludes = [
"mod.ts",
"*_test.ts",
"*_bench.ts",
"_*.ts",
];

type DenoDocEntry = {
name: string;
location: {
filename: string;
};
declarationKind: string;
jsDoc: {
doc: string;
};
kind: string;
};

function isDenoDocEntry(x: unknown): x is DenoDocEntry {
if (x == null || typeof x !== "object") return false;
if (typeof (x as DenoDocEntry).name !== "string") return false;
if (typeof (x as DenoDocEntry).location !== "object") return false;
if (typeof (x as DenoDocEntry).location.filename !== "string") return false;
if (typeof (x as DenoDocEntry).declarationKind !== "string") return false;
if (typeof (x as DenoDocEntry).jsDoc !== "object") return false;
if (typeof (x as DenoDocEntry).jsDoc.doc !== "string") return false;
if (typeof (x as DenoDocEntry).kind !== "string") return false;
return true;
}

async function listDenoDocEntries(path: string): Promise<DenoDocEntry[]> {
const cmd = new Deno.Command(Deno.execPath(), {
args: ["doc", "--json", path],
stdout: "piped",
stderr: "piped",
});
const { success, stdout, stderr } = await cmd.output();
if (!success) {
throw new Error(decoder.decode(stderr));
}
const json = JSON.parse(decoder.decode(stdout));
if (!Array.isArray(json)) {
throw new Error(`Expected array but got ${JSON.stringify(json)}`);
}
return json.filter(isDenoDocEntry);
}

async function* iterModules(path: string): AsyncIterable<string> {
const patterns = excludes.map((p) => globToRegExp(p));
for await (const entry of Deno.readDir(path)) {
if (!entry.isFile || !entry.name.endsWith(".ts")) continue;
if (patterns.some((p) => p.test(entry.name))) continue;
yield join(path, entry.name);
}
}

async function generateModTs(
namespace: string,
): Promise<void> {
const path = fromFileUrl(import.meta.resolve(`../${namespace}/`));
const exports = (await Array.fromAsync(
flatMap(iterModules(path), (x) => listDenoDocEntries(x)),
))
.filter((x) => x.kind === "function")
.filter((x) => x.declarationKind === "export")
.filter((x) => x.name.startsWith(namespace))
.map((x) => ({
path: relative(path, fromFileUrl(x.location.filename)),
name: x.name,
doc: x.jsDoc.doc,
}))
.toSorted((a, b) => a.name.localeCompare(b.name));
const lines = [
"// NOTE: This file is generated by gen-mod.ts",
...exports.map((x) => {
return `import { ${x.name} } from "./${x.path}";`;
}),
"",
...map((new Set(exports.map((x) => x.path))).values(), (x) => {
return `export * from "./${x}";`;
}),
"",
"/**",
` * An object containing all the functions in ${namespace} module.`,
" */",
`export const ${namespace}: {`,
...exports.flatMap((x) => {
return [
" /**",
...x.doc.split("\n").map((line) => ` * ${line}`.trimEnd()),
" */",
` ${x.name.replace(namespace, "")}: typeof ${x.name};`.trimEnd(),
];
}),
"} = {",
...exports.flatMap((x) => {
return [
` ${x.name.replace(namespace, "")}: ${x.name},`.trimEnd(),
];
}),
"};",
];
await Deno.writeTextFile(join(path, "mod.ts"), lines.join("\n") + "\n");
}

async function main(): Promise<void> {
await generateModTs("is");
await generateModTs("as");
}

if (import.meta.main) {
main().catch((err) => {
console.error(err);
Deno.exit(1);
});
}
107 changes: 103 additions & 4 deletions as/mod.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,109 @@
import { asOptional, asUnoptional } from "./optional.ts";
import { asReadonly, asUnreadonly } from "./readonly.ts";
// NOTE: This file is generated by gen-mod.ts
import { asOptional } from "./optional.ts";
import { asReadonly } from "./readonly.ts";
import { asUnoptional } from "./optional.ts";
import { asUnreadonly } from "./readonly.ts";

export * from "./optional.ts";
export * from "./readonly.ts";

/**
* Annotation collection for object predicate properties.
* An object containing all the functions in as module.
*/
export const as = {
export const as: {
/**
* Annotate the given predicate function as optional.
*
* Use this function to annotate a predicate function of `predObj` in {@linkcode isObjectOf}.
*
* Note that the annotated predicate function will return `true` if the type of `x` is `T` or `undefined`, indicating that
* this function is not just for annotation but it also changes the behavior of the predicate function.
*
* Use {@linkcode asUnoptional} to remove the annotation.
* Use {@linkcode hasOptional} to check if a predicate function has annotated with this function.
*
* To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost.
*
* ```ts
* import { as, is } from "@core/unknownutil";
*
* const isMyType = is.ObjectOf({
* foo: as.Optional(is.String),
* });
* const a: unknown = {};
* if (isMyType(a)) {
* const _: {foo?: string} = a;
* }
* ```
*/
Optional: typeof asOptional;
/**
* Annotate the given predicate function as readonly.
*
* Use this function to annotate a predicate function of `predObj` in {@linkcode isObjectOf}.
*
* Use {@linkcode asUnreadonly} to remove the annotation.
* Use {@linkcode hasReadonly} to check if a predicate function has annotated with this function.
*
* To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost.
*
* ```ts
* import { as, is } from "@core/unknownutil";
*
* const isMyType = is.ObjectOf({
* foo: as.Readonly(is.String),
* });
* const a: unknown = {};
* if (isMyType(a)) {
* const _: {readonly foo: string} = a;
* }
* ```
*/
Readonly: typeof asReadonly;
/**
* Unannotate the annotated predicate function with {@linkcode asOptional}.
*
* Use this function to unannotate a predicate function of `predObj` in {@linkcode isObjectOf}.
*
* Note that the annotated predicate function will return `true` if the type of `x` is `T`, indicating that
* this function is not just for annotation but it also changes the behavior of the predicate function.
*
* To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost.
*
* ```ts
* import { as, is } from "@core/unknownutil";
*
* const isMyType = is.ObjectOf({
* foo: as.Unoptional(as.Optional(is.String)),
* });
* const a: unknown = {foo: "a"};
* if (isMyType(a)) {
* const _: {foo: string} = a;
* }
* ```
*/
Unoptional: typeof asUnoptional;
/**
* Unannotate the annotated predicate function with {@linkcode asReadonly}.
*
* Use this function to unannotate a predicate function of `predObj` in {@linkcode isObjectOf}.
*
* To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost.
*
* ```ts
* import { as, is } from "@core/unknownutil";
*
* const isMyType = is.ObjectOf({
* foo: as.Unreadonly(as.Readonly(is.String)),
* });
* const a: unknown = {foo: "a"};
* if (isMyType(a)) {
* const _: {foo: string} = a;
* }
* ```
*/
Unreadonly: typeof asUnreadonly;
} = {
Optional: asOptional,
Readonly: asReadonly,
Unoptional: asUnoptional,
Expand Down
2 changes: 2 additions & 0 deletions deno.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
]
},
"imports": {
"@core/iterutil": "jsr:@core/iterutil@^0.3.0",
"@core/unknownutil": "./mod.ts",
"@deno/dnt": "jsr:@deno/dnt@^0.41.1",
"@std/assert": "jsr:@std/assert@^0.221.0",
Expand All @@ -78,6 +79,7 @@
"test": "deno test -A --doc --parallel --shuffle",
"test:coverage": "deno task test --coverage=.coverage",
"coverage": "deno coverage .coverage",
"gen": "deno run --allow-run=deno --allow-read --allow-write=. .scripts/gen-mod.ts",
"update": "deno run --allow-env --allow-read --allow-write=. --allow-run=git,deno --allow-net=jsr.io,registry.npmjs.org jsr:@molt/cli ./*.ts",
"update:commit": "deno task -q update --commit --prefix deps: --pre-commit=fmt,lint"
}
Expand Down
Loading