@fedify/vocab-tools 2.0.0-pr.458.1785

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,122 @@
1
+ import { deepStrictEqual } from "node:assert";
2
+ import { basename, dirname, join } from "node:path";
3
+ import { test } from "node:test";
4
+ import metadata from "../deno.json" with { type: "json" };
5
+ import { generateClasses, sortTopologically } from "./class.ts";
6
+ import { loadSchemaFiles } from "./schema.ts";
7
+
8
+ test("sortTopologically()", () => {
9
+ const sorted = sortTopologically({
10
+ "https://example.com/quux": {
11
+ uri: "https://example.com/quux",
12
+ name: "Foo",
13
+ extends: "https://example.com/qux",
14
+ entity: true,
15
+ description: "",
16
+ properties: [],
17
+ defaultContext: {},
18
+ },
19
+ "https://example.com/qux": {
20
+ uri: "https://example.com/qux",
21
+ name: "Foo",
22
+ extends: "https://example.com/bar",
23
+ entity: true,
24
+ description: "",
25
+ properties: [],
26
+ defaultContext: {},
27
+ },
28
+ "https://example.com/baz": {
29
+ uri: "https://example.com/baz",
30
+ name: "Foo",
31
+ extends: "https://example.com/foo",
32
+ entity: true,
33
+ description: "",
34
+ properties: [],
35
+ defaultContext: {},
36
+ },
37
+ "https://example.com/bar": {
38
+ uri: "https://example.com/bar",
39
+ name: "Foo",
40
+ extends: "https://example.com/foo",
41
+ entity: true,
42
+ description: "",
43
+ properties: [],
44
+ defaultContext: {},
45
+ },
46
+ "https://example.com/foo": {
47
+ uri: "https://example.com/foo",
48
+ name: "Foo",
49
+ entity: true,
50
+ description: "",
51
+ properties: [],
52
+ defaultContext: {},
53
+ },
54
+ });
55
+ deepStrictEqual(
56
+ sorted,
57
+ [
58
+ "https://example.com/foo",
59
+ "https://example.com/bar",
60
+ "https://example.com/qux",
61
+ "https://example.com/quux",
62
+ "https://example.com/baz",
63
+ ],
64
+ );
65
+ });
66
+
67
+ if ("Deno" in globalThis) {
68
+ const { assertSnapshot } = await import("@std/testing/snapshot");
69
+ Deno.test("generateClasses()", async (t) => {
70
+ const entireCode = await getEntireCode();
71
+ await assertSnapshot(t, entireCode, {
72
+ path: getDenoSnapshotPath(),
73
+ });
74
+ });
75
+ } else if ("Bun" in globalThis) {
76
+ const { test, expect } = await import("bun:test");
77
+ test("generateClasses()", async () => {
78
+ const entireCode = await getEntireCode();
79
+ expect(entireCode).toMatchSnapshot();
80
+ });
81
+ } else {
82
+ await changeNodeSnapshotPath();
83
+ test("generateClasses()", async (t) => {
84
+ const entireCode = await getEntireCode();
85
+ t.assert.snapshot(entireCode);
86
+ });
87
+ }
88
+
89
+ async function getEntireCode() {
90
+ const packagesDir = dirname(dirname(import.meta.dirname!));
91
+ const schemaDir = join(packagesDir, "fedify", "src", "vocab");
92
+ const types = await loadSchemaFiles(schemaDir);
93
+ const entireCode = (await Array.fromAsync(generateClasses(types)))
94
+ .join("")
95
+ .replaceAll(JSON.stringify(metadata.version), '"0.0.0"');
96
+ return entireCode;
97
+ }
98
+
99
+ async function changeNodeSnapshotPath() {
100
+ const { snapshot } = await import("node:test");
101
+ snapshot.setResolveSnapshotPath(
102
+ (path) => {
103
+ if (!path) {
104
+ throw new Error("path is undefined");
105
+ }
106
+ return join(
107
+ dirname(path),
108
+ "__snapshots__",
109
+ basename(path) + ".node.snap",
110
+ );
111
+ },
112
+ );
113
+ snapshot.setDefaultSnapshotSerializers([
114
+ (value) => JSON.stringify(value, null, 2),
115
+ (value) => value.replaceAll("\\n", "\n"),
116
+ ]);
117
+ }
118
+
119
+ function getDenoSnapshotPath() {
120
+ const pf = import.meta.filename!;
121
+ return join(dirname(pf), "__snapshots__", basename(pf) + ".deno.snap");
122
+ }
package/src/class.ts ADDED
@@ -0,0 +1,140 @@
1
+ import { generateDecoder, generateEncoder } from "./codec.ts";
2
+ import { generateCloner, generateConstructor } from "./constructor.ts";
3
+ import { generateFields } from "./field.ts";
4
+ import { generateInspector } from "./inspector.ts";
5
+ import { generateProperties } from "./property.ts";
6
+ import type { TypeSchema } from "./schema.ts";
7
+ import { emitOverride } from "./type.ts";
8
+
9
+ /**
10
+ * Sorts the given types topologically so that the base types come before the
11
+ * extended types.
12
+ * @param types The types to sort.
13
+ * @returns The sorted type URIs.
14
+ */
15
+ export function sortTopologically(types: Record<string, TypeSchema>): string[] {
16
+ const sorted: string[] = [];
17
+ const visited = new Set<string>();
18
+ const visiting = new Set<string>();
19
+ for (const node of Object.values(types)) {
20
+ visit(node);
21
+ }
22
+ return sorted;
23
+
24
+ function visit(node: TypeSchema) {
25
+ if (visited.has(node.uri)) return;
26
+ if (visiting.has(node.uri)) {
27
+ throw new Error(`Detected cyclic inheritance: ${node.uri}`);
28
+ }
29
+ visiting.add(node.uri);
30
+ if (node.extends) visit(types[node.extends]);
31
+ visiting.delete(node.uri);
32
+ visited.add(node.uri);
33
+ sorted.push(node.uri);
34
+ }
35
+ }
36
+
37
+ async function* generateClass(
38
+ typeUri: string,
39
+ types: Record<string, TypeSchema>,
40
+ ): AsyncIterable<string> {
41
+ const type = types[typeUri];
42
+ yield `/** ${type.description.replaceAll("\n", "\n * ")}\n */\n`;
43
+ if (type.extends) {
44
+ const baseType = types[type.extends];
45
+ yield `export class ${type.name} extends ${baseType.name} {\n`;
46
+ } else {
47
+ yield `export class ${type.name} {\n`;
48
+ }
49
+ if (type.extends == null) {
50
+ yield `
51
+ readonly #documentLoader?: DocumentLoader;
52
+ readonly #contextLoader?: DocumentLoader;
53
+ readonly #tracerProvider?: TracerProvider;
54
+ readonly #warning?: {
55
+ category: string[];
56
+ message: string;
57
+ values?: Record<string, unknown>;
58
+ };
59
+ #cachedJsonLd?: unknown;
60
+ readonly id: URL | null;
61
+
62
+ protected get _documentLoader(): DocumentLoader | undefined {
63
+ return this.#documentLoader;
64
+ }
65
+
66
+ protected get _contextLoader(): DocumentLoader | undefined {
67
+ return this.#contextLoader;
68
+ }
69
+
70
+ protected get _tracerProvider(): TracerProvider | undefined {
71
+ return this.#tracerProvider;
72
+ }
73
+
74
+ protected get _warning(): {
75
+ category: string[];
76
+ message: string;
77
+ values?: Record<string, unknown>;
78
+ } | undefined {
79
+ return this.#warning;
80
+ }
81
+
82
+ protected get _cachedJsonLd(): unknown | undefined {
83
+ return this.#cachedJsonLd;
84
+ }
85
+
86
+ protected set _cachedJsonLd(value: unknown | undefined) {
87
+ this.#cachedJsonLd = value;
88
+ }
89
+ `;
90
+ }
91
+ yield `
92
+ /**
93
+ * The type URI of {@link ${type.name}}: \`${typeUri}\`.
94
+ */
95
+ static ${emitOverride(typeUri, types)} get typeId(): URL {
96
+ return new URL(${JSON.stringify(typeUri)});
97
+ }
98
+ `;
99
+ for await (const code of generateFields(typeUri, types)) yield code;
100
+ for await (const code of generateConstructor(typeUri, types)) yield code;
101
+ for await (const code of generateCloner(typeUri, types)) yield code;
102
+ for await (const code of generateProperties(typeUri, types)) yield code;
103
+ for await (const code of generateEncoder(typeUri, types)) yield code;
104
+ for await (const code of generateDecoder(typeUri, types)) yield code;
105
+ for await (const code of generateInspector(typeUri, types)) yield code;
106
+ yield "}\n\n";
107
+ }
108
+
109
+ /**
110
+ * Generates the TypeScript classes from the given types.
111
+ * @param types The types to generate classes from.
112
+ * @returns The source code of the generated classes.
113
+ */
114
+ export async function* generateClasses(
115
+ types: Record<string, TypeSchema>,
116
+ ): AsyncIterable<string> {
117
+ yield "// deno-lint-ignore-file ban-unused-ignore prefer-const\n";
118
+ yield "// @ts-ignore TS7016\n";
119
+ yield 'import jsonld from "jsonld";\n';
120
+ yield 'import { getLogger } from "@logtape/logtape";\n';
121
+ yield `import { type Span, SpanStatusCode, type TracerProvider, trace }
122
+ from "@opentelemetry/api";\n`;
123
+ yield `import {
124
+ decodeMultibase,
125
+ type DocumentLoader,
126
+ encodeMultibase,
127
+ exportMultibaseKey,
128
+ exportSpki,
129
+ getDocumentLoader,
130
+ importMultibaseKey,
131
+ importPem,
132
+ LanguageString,
133
+ type RemoteDocument,
134
+ } from "@fedify/vocab-runtime";\n`;
135
+ yield "\n\n";
136
+ const sorted = sortTopologically(types);
137
+ for (const typeUri of sorted) {
138
+ for await (const code of generateClass(typeUri, types)) yield code;
139
+ }
140
+ }