typespec-typescript-emitter 1.2.0 → 2.0.1
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.
- package/.husky/pre-commit +2 -1
- package/.prettierignore +2 -1
- package/CHANGELOG.md +19 -24
- package/CODE_OF_CONDUCT.md +128 -0
- package/README.md +362 -219
- package/dist/src/emit_routedTypemap.d.ts +4 -0
- package/dist/src/emit_routedTypemap.js +83 -0
- package/dist/src/emit_routedTypemap.js.map +1 -0
- package/dist/src/emit_routes.d.ts +3 -2
- package/dist/src/emit_routes.js +73 -47
- package/dist/src/emit_routes.js.map +1 -1
- package/dist/src/emit_types.d.ts +3 -9
- package/dist/src/emit_types.js +109 -74
- package/dist/src/emit_types.js.map +1 -1
- package/dist/src/emitter.d.ts +2 -6
- package/dist/src/emitter.js +18 -76
- package/dist/src/emitter.js.map +1 -1
- package/dist/src/helpers/appendableString.d.ts +15 -0
- package/dist/src/helpers/appendableString.js +41 -0
- package/dist/src/helpers/appendableString.js.map +1 -0
- package/dist/src/helpers/arrays.d.ts +3 -0
- package/dist/src/helpers/arrays.js +23 -0
- package/dist/src/helpers/arrays.js.map +1 -0
- package/dist/src/{helper_autogenerateWarning.d.ts → helpers/autogenerateWarning.d.ts} +1 -1
- package/dist/src/{helper_autogenerateWarning.js → helpers/autogenerateWarning.js} +1 -2
- package/dist/src/helpers/autogenerateWarning.js.map +1 -0
- package/dist/src/helpers/buildTypeMap.d.ts +12 -0
- package/dist/src/helpers/buildTypeMap.js +44 -0
- package/dist/src/helpers/buildTypeMap.js.map +1 -0
- package/dist/src/helpers/diagnostics.d.ts +4 -0
- package/dist/src/helpers/diagnostics.js +15 -0
- package/dist/src/helpers/diagnostics.js.map +1 -0
- package/dist/src/helpers/getImports.d.ts +2 -0
- package/dist/src/helpers/getImports.js +3 -0
- package/dist/src/helpers/getImports.js.map +1 -0
- package/dist/src/helpers/namespaces.d.ts +4 -0
- package/dist/src/helpers/namespaces.js +14 -0
- package/dist/src/helpers/namespaces.js.map +1 -0
- package/dist/src/helpers/visibilityHelperFile.d.ts +4 -0
- package/dist/src/helpers/visibilityHelperFile.js +57 -0
- package/dist/src/helpers/visibilityHelperFile.js.map +1 -0
- package/dist/src/lib.d.ts +4 -1
- package/dist/src/lib.js +14 -3
- package/dist/src/lib.js.map +1 -1
- package/dist/src/parseOptions.d.ts +8 -0
- package/dist/src/parseOptions.js +26 -0
- package/dist/src/parseOptions.js.map +1 -0
- package/dist/src/resolve/Resolvable.d.ts +32 -0
- package/dist/src/resolve/Resolvable.js +180 -0
- package/dist/src/resolve/Resolvable.js.map +1 -0
- package/dist/src/resolve/Resolvable_helpers.d.ts +42 -0
- package/dist/src/resolve/Resolvable_helpers.js +19 -0
- package/dist/src/resolve/Resolvable_helpers.js.map +1 -0
- package/dist/src/resolve/operationTypemap.d.ts +21 -0
- package/dist/src/resolve/operationTypemap.js +138 -0
- package/dist/src/resolve/operationTypemap.js.map +1 -0
- package/dist/src/resolve/types/Enum.d.ts +21 -0
- package/dist/src/resolve/types/Enum.js +61 -0
- package/dist/src/resolve/types/Enum.js.map +1 -0
- package/dist/src/resolve/types/Model.Indexed.d.ts +12 -0
- package/dist/src/resolve/types/Model.Indexed.js +69 -0
- package/dist/src/resolve/types/Model.Indexed.js.map +1 -0
- package/dist/src/resolve/types/Model.Shaped.d.ts +17 -0
- package/dist/src/resolve/types/Model.Shaped.js +210 -0
- package/dist/src/resolve/types/Model.Shaped.js.map +1 -0
- package/dist/src/resolve/types/Scalar.d.ts +9 -0
- package/dist/src/resolve/types/Scalar.js +109 -0
- package/dist/src/resolve/types/Scalar.js.map +1 -0
- package/dist/src/resolve/types/Simple.d.ts +11 -0
- package/dist/src/resolve/types/Simple.js +49 -0
- package/dist/src/resolve/types/Simple.js.map +1 -0
- package/dist/src/resolve/types/Tuple.d.ts +9 -0
- package/dist/src/resolve/types/Tuple.js +38 -0
- package/dist/src/resolve/types/Tuple.js.map +1 -0
- package/dist/src/resolve/types/Union.d.ts +9 -0
- package/dist/src/resolve/types/Union.js +36 -0
- package/dist/src/resolve/types/Union.js.map +1 -0
- package/eslint.config.js +12 -4
- package/package.json +2 -2
- package/src/emit_routedTypemap.ts +128 -0
- package/src/emit_routes.ts +108 -61
- package/src/emit_types.ts +137 -92
- package/src/emitter.ts +19 -107
- package/src/helpers/appendableString.ts +52 -0
- package/src/helpers/arrays.ts +21 -0
- package/src/{helper_autogenerateWarning.ts → helpers/autogenerateWarning.ts} +0 -1
- package/src/helpers/buildTypeMap.ts +72 -0
- package/src/helpers/diagnostics.ts +19 -0
- package/src/helpers/getImports.ts +9 -0
- package/src/helpers/namespaces.ts +18 -0
- package/src/helpers/visibilityHelperFile.ts +63 -0
- package/src/lib.ts +25 -4
- package/src/parseOptions.ts +26 -0
- package/src/resolve/Resolvable.ts +267 -0
- package/src/resolve/Resolvable_helpers.ts +65 -0
- package/src/resolve/operationTypemap.ts +212 -0
- package/src/resolve/types/Enum.ts +92 -0
- package/src/resolve/types/Model.Indexed.ts +113 -0
- package/src/resolve/types/Model.Shaped.ts +291 -0
- package/src/resolve/types/Scalar.ts +140 -0
- package/src/resolve/types/Simple.ts +88 -0
- package/src/resolve/types/Tuple.ts +56 -0
- package/src/resolve/types/Union.ts +52 -0
- package/test/helpers/integrationTest-novis.tsp +51 -0
- package/test/helpers/integrationTest.tsp +53 -0
- package/test/helpers/largeModel.tsp +40 -0
- package/test/{runner.ts → helpers/runner.ts} +1 -1
- package/test/helpers/ts.ts +11 -0
- package/test/helpers/wrapper.ts +144 -0
- package/test/routes/routes.target.ts +35 -0
- package/test/routes/routes.test.ts +22 -0
- package/test/typeguards/combined.test.ts +78 -0
- package/test/typeguards/enum.test.ts +10 -0
- package/test/typeguards/model.indexed.test.ts +68 -0
- package/test/typeguards/model.shaped.test.ts +38 -0
- package/test/typeguards/scalar.test.ts +62 -0
- package/test/typeguards/simple.test.ts +35 -0
- package/test/typeguards/tuple.test.ts +34 -0
- package/test/typeguards/union.test.ts +29 -0
- package/test/typemap/typemap-novis.target.ts +38 -0
- package/test/typemap/typemap.target.ts +39 -0
- package/test/typemap/typemap.test.ts +48 -0
- package/test/types/combined.test.ts +71 -0
- package/test/types/enum.test.ts +57 -0
- package/test/types/model.indexed.test.ts +46 -0
- package/test/types/model.shaped.test.ts +23 -0
- package/test/types/scalar.test.ts +53 -0
- package/test/types/simple.test.ts +20 -0
- package/test/types/tuple.test.ts +29 -0
- package/test/types/union.test.ts +20 -0
- package/tsconfig.json +1 -0
- package/dist/src/emit_mapped_types.d.ts +0 -2
- package/dist/src/emit_mapped_types.js +0 -124
- package/dist/src/emit_mapped_types.js.map +0 -1
- package/dist/src/emit_types_resolve.d.ts +0 -22
- package/dist/src/emit_types_resolve.js +0 -217
- package/dist/src/emit_types_resolve.js.map +0 -1
- package/dist/src/emit_types_typeguards.d.ts +0 -17
- package/dist/src/emit_types_typeguards.js +0 -121
- package/dist/src/emit_types_typeguards.js.map +0 -1
- package/dist/src/helper_autogenerateWarning.js.map +0 -1
- package/src/emit_mapped_types.ts +0 -155
- package/src/emit_types_resolve.ts +0 -280
- package/src/emit_types_typeguards.ts +0 -178
- package/test/main.test.ts +0 -83
- package/test/out/.gitkeep +0 -0
- package/test/targets/enum.routed-types.ts +0 -59
- package/test/targets/enum.routes.ts +0 -29
- package/test/targets/enum.target.ts +0 -39
- package/test/targets/enum.tsp +0 -36
- package/test/targets/pr8.routed-types.ts +0 -131
- package/test/targets/pr8.routes.ts +0 -30
- package/test/targets/pr8.target.ts +0 -97
- package/test/targets/pr8.tsp +0 -62
- package/test/targets/simple-routes.routed-types.ts +0 -64
- package/test/targets/simple-routes.routes.ts +0 -29
- package/test/targets/simple-routes.target.ts +0 -21
- package/test/targets/simple-routes.tsp +0 -23
- package/test/targets/union.routed-types.ts +0 -59
- package/test/targets/union.routes.ts +0 -23
- package/test/targets/union.target.ts +0 -59
- package/test/targets/union.tsp +0 -38
- package/test/targets/visibility.routed-types.ts +0 -81
- package/test/targets/visibility.routes.ts +0 -38
- package/test/targets/visibility.target.ts +0 -36
- package/test/targets/visibility.tsp +0 -49
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import {
|
|
2
|
+
emitFile,
|
|
3
|
+
Namespace,
|
|
4
|
+
navigateProgram,
|
|
5
|
+
Operation,
|
|
6
|
+
Program,
|
|
7
|
+
resolvePath,
|
|
8
|
+
} from "@typespec/compiler";
|
|
9
|
+
import { getHttpOperation } from "@typespec/http";
|
|
10
|
+
import { unique2D } from "./helpers/arrays.js";
|
|
11
|
+
import autogenerateWarning from "./helpers/autogenerateWarning.js";
|
|
12
|
+
import { TTypeMap } from "./helpers/buildTypeMap.js";
|
|
13
|
+
import { getImports } from "./helpers/getImports.js";
|
|
14
|
+
import { visibilityHelperFileName } from "./helpers/visibilityHelperFile.js";
|
|
15
|
+
import { EmitterOptions } from "./lib.js";
|
|
16
|
+
import {
|
|
17
|
+
resolveOperationTypemap,
|
|
18
|
+
TOperationTypemap,
|
|
19
|
+
} from "./resolve/operationTypemap.js";
|
|
20
|
+
|
|
21
|
+
export const emitRoutedTypemap = async (
|
|
22
|
+
program: Program,
|
|
23
|
+
options: EmitterOptions,
|
|
24
|
+
typemap: TTypeMap,
|
|
25
|
+
): Promise<void> => {
|
|
26
|
+
// save original targeted namespaces array because it's mutated here
|
|
27
|
+
const targetedNamespaces = structuredClone(options["root-namespaces"]);
|
|
28
|
+
const namespaceImports: {
|
|
29
|
+
[namespace: string]: TTypeMap[number]["namespaces"][];
|
|
30
|
+
} = {};
|
|
31
|
+
const namespaceOps: {
|
|
32
|
+
[namespace: string]: {
|
|
33
|
+
ops: {
|
|
34
|
+
[path: string]: {
|
|
35
|
+
[verb: string]: TOperationTypemap;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
hasVisibility: boolean;
|
|
39
|
+
};
|
|
40
|
+
} = {};
|
|
41
|
+
|
|
42
|
+
// finding all operations
|
|
43
|
+
const foundOps: { [namespace: string]: Operation[] } = {};
|
|
44
|
+
const traverseNamespace = (n: Namespace, rootName: string) => {
|
|
45
|
+
if (!foundOps[rootName]) foundOps[rootName] = [];
|
|
46
|
+
foundOps[rootName].push(...Array.from(n.operations).map((op) => op[1]));
|
|
47
|
+
n.namespaces.forEach((ns) => traverseNamespace(ns, rootName));
|
|
48
|
+
};
|
|
49
|
+
navigateProgram(program, {
|
|
50
|
+
namespace(n) {
|
|
51
|
+
const nsIndex = options["root-namespaces"].findIndex(
|
|
52
|
+
(ns) => n.name === ns,
|
|
53
|
+
);
|
|
54
|
+
if (nsIndex === -1) return;
|
|
55
|
+
// for some reason, navigateProgram visits each namespace multiple times; this prevents that
|
|
56
|
+
delete options["root-namespaces"][nsIndex];
|
|
57
|
+
|
|
58
|
+
traverseNamespace(n, n.name);
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// resolving operations
|
|
63
|
+
for (const ns of Object.entries(foundOps)) {
|
|
64
|
+
for (const op of ns[1]) {
|
|
65
|
+
const resolved = await resolveOperationTypemap(
|
|
66
|
+
program,
|
|
67
|
+
options,
|
|
68
|
+
typemap,
|
|
69
|
+
op,
|
|
70
|
+
);
|
|
71
|
+
const httpOp = getHttpOperation(program, op)[0];
|
|
72
|
+
|
|
73
|
+
if (!namespaceImports[ns[0]]) namespaceImports[ns[0]] = [];
|
|
74
|
+
namespaceImports[ns[0]].push(...resolved.imports);
|
|
75
|
+
|
|
76
|
+
if (!namespaceOps[ns[0]])
|
|
77
|
+
namespaceOps[ns[0]] = { ops: {}, hasVisibility: false };
|
|
78
|
+
if (!namespaceOps[ns[0]].ops[httpOp.path])
|
|
79
|
+
namespaceOps[ns[0]].ops[httpOp.path] = {};
|
|
80
|
+
namespaceOps[ns[0]].ops[httpOp.path][httpOp.verb.toUpperCase()] =
|
|
81
|
+
resolved.types;
|
|
82
|
+
|
|
83
|
+
if (
|
|
84
|
+
resolved.types.request.hasVisibility ||
|
|
85
|
+
resolved.types.response.content.some((v) => v.hasVisibility)
|
|
86
|
+
)
|
|
87
|
+
namespaceOps[ns[0]].hasVisibility = true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// emitting
|
|
92
|
+
for (const ns of Object.entries(namespaceOps)) {
|
|
93
|
+
const importStrings = getImports(unique2D(namespaceImports[ns[0]]));
|
|
94
|
+
|
|
95
|
+
if (ns[1].hasVisibility) {
|
|
96
|
+
importStrings.push(
|
|
97
|
+
`import {Lifecycle, FilterLifecycle} from './${visibilityHelperFileName}';`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let out = `export type types_${ns[0]}${ns[1].hasVisibility ? "<V extends Lifecycle = Lifecycle.All>" : ""} = {\n`;
|
|
102
|
+
out += Object.entries(ns[1].ops)
|
|
103
|
+
.map((path) => {
|
|
104
|
+
let pathret = ` ['${path[0]}']: {\n`;
|
|
105
|
+
pathret += Object.entries(path[1])
|
|
106
|
+
.map((verb) => {
|
|
107
|
+
let verbret = ` ['${verb[0]}']: {\n`;
|
|
108
|
+
verbret += ` request: ${verb[1].request.content}\n`;
|
|
109
|
+
verbret += ` response: ${verb[1].response.content.map((res) => `{status: ${res.status}, body: ${res.body}}`).join(" | ")}\n`;
|
|
110
|
+
verbret += " }";
|
|
111
|
+
return verbret;
|
|
112
|
+
})
|
|
113
|
+
.join(",\n");
|
|
114
|
+
pathret += "\n }";
|
|
115
|
+
return pathret;
|
|
116
|
+
})
|
|
117
|
+
.join(",\n");
|
|
118
|
+
out += "\n};\n";
|
|
119
|
+
const content = `/* eslint-disable */\n\n${autogenerateWarning}\n${importStrings.join("\n")}\n\n${out}`;
|
|
120
|
+
await emitFile(program, {
|
|
121
|
+
path: resolvePath(options["out-dir"], `routedTypemap_${ns[0]}.ts`),
|
|
122
|
+
content: content,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// restore un-mutated version
|
|
127
|
+
options["root-namespaces"] = targetedNamespaces;
|
|
128
|
+
};
|
package/src/emit_routes.ts
CHANGED
|
@@ -1,63 +1,80 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
emitFile,
|
|
3
3
|
getDoc,
|
|
4
|
+
Interface,
|
|
4
5
|
isTemplateDeclaration,
|
|
5
6
|
Namespace,
|
|
6
|
-
|
|
7
|
+
navigateProgram,
|
|
8
|
+
Program,
|
|
9
|
+
resolvePath,
|
|
7
10
|
} from "@typespec/compiler";
|
|
8
11
|
import { getAuthentication, getHttpOperation } from "@typespec/http";
|
|
12
|
+
import { AppendableString } from "./helpers/appendableString.js";
|
|
13
|
+
import autogenerateWarning from "./helpers/autogenerateWarning.js";
|
|
14
|
+
import { EmitterOptions } from "./lib.js";
|
|
9
15
|
|
|
10
|
-
export const emitRoutes = (
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
):
|
|
14
|
-
|
|
16
|
+
export const emitRoutes = async (
|
|
17
|
+
program: Program,
|
|
18
|
+
options: EmitterOptions,
|
|
19
|
+
): Promise<void> => {
|
|
20
|
+
// save original targeted namespaces array because it's mutated here
|
|
21
|
+
const targetedNamespaces = structuredClone(options["root-namespaces"]);
|
|
15
22
|
|
|
16
|
-
const
|
|
17
|
-
// operations
|
|
18
|
-
const processOp = (op: Operation) => {
|
|
19
|
-
const httpOp = getHttpOperation(context.program, op);
|
|
23
|
+
const files: Record<string, AppendableString> = {};
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
out = out.addLine(
|
|
30
|
-
`method: '${httpOp[0].verb.toUpperCase()}',`,
|
|
31
|
-
nestLevel + 2,
|
|
25
|
+
const traverseNamespace = (
|
|
26
|
+
n: Namespace | Interface,
|
|
27
|
+
nestlevel: number,
|
|
28
|
+
rootName: string,
|
|
29
|
+
) => {
|
|
30
|
+
if (files[rootName] === undefined)
|
|
31
|
+
files[rootName] = new AppendableString(
|
|
32
|
+
`export const routes_${rootName} = {\n`,
|
|
32
33
|
);
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
const numCollections =
|
|
36
|
+
Array.from((n as any).interfaces ?? []).length +
|
|
37
|
+
Array.from(((n as any).namespaces ?? []) as any[]).length;
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
let i = 1;
|
|
40
|
+
for (const op of n.operations) {
|
|
41
|
+
const httpOp = getHttpOperation(program, op[1])[0];
|
|
42
|
+
|
|
43
|
+
const pathParams = httpOp.parameters.parameters.filter(
|
|
39
44
|
(p) => p.type === "path",
|
|
40
45
|
);
|
|
41
|
-
const
|
|
46
|
+
const urlParams = `params: {${pathParams
|
|
42
47
|
.map((p) => `${p.name}: string`)
|
|
43
|
-
.join(", ")
|
|
44
|
-
const
|
|
45
|
-
const pathParamsOutString = pathParams.reduce(
|
|
48
|
+
.join(", ")}}`;
|
|
49
|
+
const urlReturn = pathParams.reduce(
|
|
46
50
|
(sum, cur) =>
|
|
47
51
|
sum.replaceAll(`{${cur.name}}`, `${"$"}{params.${cur.name}}`),
|
|
48
|
-
`\`${httpOp
|
|
52
|
+
`\`${httpOp.path}\``,
|
|
49
53
|
);
|
|
50
54
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
const doc = getDoc(program, op[1]);
|
|
56
|
+
if (doc)
|
|
57
|
+
files[rootName].addLine(
|
|
58
|
+
`/** ${doc} */`
|
|
59
|
+
.split("\n")
|
|
60
|
+
.map((l) => `${" ".repeat(nestlevel)}${l}`)
|
|
61
|
+
.join("\n"),
|
|
62
|
+
0,
|
|
63
|
+
);
|
|
64
|
+
files[rootName]
|
|
65
|
+
.addLine(`${op[1].name}: {`, nestlevel)
|
|
66
|
+
.addLine(`verb: '${httpOp.verb.toUpperCase()}',`, nestlevel + 1)
|
|
67
|
+
.addLine(`path: '${httpOp.path}',`, nestlevel + 1)
|
|
68
|
+
.addLine(
|
|
69
|
+
`getUrl: (${pathParams.length > 0 ? urlParams : ""}): string => ${urlReturn},`,
|
|
70
|
+
nestlevel + 1,
|
|
71
|
+
);
|
|
55
72
|
|
|
56
73
|
// auth
|
|
57
74
|
let auth: Array<
|
|
58
75
|
null | string | { apiKeyLocation: string; apiKeyName: string }
|
|
59
76
|
> = [];
|
|
60
|
-
const opAuth = getAuthentication(
|
|
77
|
+
const opAuth = getAuthentication(program, op[1]);
|
|
61
78
|
if (opAuth) {
|
|
62
79
|
opAuth.options.forEach((authOption) =>
|
|
63
80
|
authOption.schemes.forEach((authScheme) => {
|
|
@@ -77,8 +94,7 @@ export const emitRoutes = (
|
|
|
77
94
|
}),
|
|
78
95
|
);
|
|
79
96
|
} else auth = [null];
|
|
80
|
-
|
|
81
|
-
out = out.addLine(
|
|
97
|
+
files[rootName].addLine(
|
|
82
98
|
`auth: [${auth
|
|
83
99
|
.map((authEntry) =>
|
|
84
100
|
!authEntry
|
|
@@ -90,34 +106,65 @@ export const emitRoutes = (
|
|
|
90
106
|
.join(", ")}}`,
|
|
91
107
|
)
|
|
92
108
|
.join(", ")}]`,
|
|
93
|
-
|
|
109
|
+
nestlevel + 1,
|
|
94
110
|
);
|
|
95
111
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (!isTemplateDeclaration(itf)) itf.operations.forEach(processOp);
|
|
103
|
-
});
|
|
112
|
+
files[rootName].addLine(
|
|
113
|
+
i < n.operations.size || numCollections > 0 ? "}," : "}",
|
|
114
|
+
nestlevel,
|
|
115
|
+
);
|
|
116
|
+
i++;
|
|
117
|
+
}
|
|
104
118
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
119
|
+
let collectionNum = 1;
|
|
120
|
+
const printCollection = (n: Namespace | Interface): void => {
|
|
121
|
+
const doc = getDoc(program, n);
|
|
122
|
+
if (doc)
|
|
123
|
+
files[rootName].addLine(
|
|
124
|
+
`/** ${doc} */`
|
|
125
|
+
.split("\n")
|
|
126
|
+
.map((l) => `${" ".repeat(nestlevel)}${l}`)
|
|
127
|
+
.join("\n"),
|
|
128
|
+
);
|
|
129
|
+
files[rootName].addLine(`${n.name}: {`, nestlevel);
|
|
130
|
+
traverseNamespace(n, nestlevel + 1, rootName);
|
|
131
|
+
files[rootName].addLine(
|
|
132
|
+
collectionNum < numCollections ? "}," : "}",
|
|
133
|
+
nestlevel,
|
|
116
134
|
);
|
|
135
|
+
collectionNum++;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
(((n as any).interfaces ?? []) as Interface[]).forEach((i) => {
|
|
139
|
+
if (!isTemplateDeclaration(i)) printCollection(i);
|
|
117
140
|
});
|
|
141
|
+
(((n as any).namespaces ?? []) as Namespace[]).forEach((ns) =>
|
|
142
|
+
printCollection(ns),
|
|
143
|
+
);
|
|
144
|
+
if (n.name === rootName && nestlevel === 1)
|
|
145
|
+
files[rootName].addLine("} as const;");
|
|
118
146
|
};
|
|
147
|
+
navigateProgram(program, {
|
|
148
|
+
namespace(n) {
|
|
149
|
+
const nsIndex = options["root-namespaces"].findIndex(
|
|
150
|
+
(ns) => n.name === ns,
|
|
151
|
+
);
|
|
152
|
+
if (nsIndex === -1) return;
|
|
153
|
+
// for some reason, navigateProgram visits each namespace multiple times; this prevents that
|
|
154
|
+
delete options["root-namespaces"][nsIndex];
|
|
155
|
+
|
|
156
|
+
traverseNamespace(n, 1, n.name);
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// emitting
|
|
161
|
+
for (const file of Object.entries(files)) {
|
|
162
|
+
await emitFile(program, {
|
|
163
|
+
path: resolvePath(options["out-dir"], `routes_${file[0]}.ts`),
|
|
164
|
+
content: `/* eslint-disable */\n\n${autogenerateWarning}\n${file[1].value}`,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
119
167
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return out;
|
|
168
|
+
// restore un-mutated version
|
|
169
|
+
options["root-namespaces"] = targetedNamespaces;
|
|
123
170
|
};
|
package/src/emit_types.ts
CHANGED
|
@@ -1,103 +1,148 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from "./
|
|
7
|
-
import {
|
|
1
|
+
import { emitFile, Program, resolvePath, Type } from "@typespec/compiler";
|
|
2
|
+
import { AppendableString } from "./helpers/appendableString.js";
|
|
3
|
+
import { unique2D } from "./helpers/arrays.js";
|
|
4
|
+
import autogenerateWarning from "./helpers/autogenerateWarning.js";
|
|
5
|
+
import { TTypeMap } from "./helpers/buildTypeMap.js";
|
|
6
|
+
import { getImports } from "./helpers/getImports.js";
|
|
7
|
+
import { filenameFromNamespaces } from "./helpers/namespaces.js";
|
|
8
|
+
import { visibilityHelperFileName } from "./helpers/visibilityHelperFile.js";
|
|
8
9
|
import { EmitterOptions } from "./lib.js";
|
|
10
|
+
import { Resolvable } from "./resolve/Resolvable.js";
|
|
11
|
+
import { Resolver } from "./resolve/Resolvable_helpers.js";
|
|
9
12
|
|
|
10
|
-
const emitTypes = (
|
|
11
|
-
|
|
12
|
-
namespace: Namespace,
|
|
13
|
+
export const emitTypes = async (
|
|
14
|
+
program: Program,
|
|
13
15
|
options: EmitterOptions,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
typemap: TTypeMap,
|
|
17
|
+
): Promise<void> => {
|
|
18
|
+
// maps file names to file contents
|
|
19
|
+
const files: Record<string, AppendableString> = {};
|
|
20
|
+
// maps file names to list of required imports
|
|
21
|
+
const imports: Record<
|
|
22
|
+
string,
|
|
23
|
+
{
|
|
24
|
+
namespaces: TTypeMap[number]["namespaces"][];
|
|
25
|
+
lifecycleTypes: string[];
|
|
26
|
+
}
|
|
27
|
+
> = {};
|
|
19
28
|
|
|
20
|
-
|
|
21
|
-
|
|
29
|
+
// generate files
|
|
30
|
+
typemap.forEach((t) => {
|
|
31
|
+
files[filenameFromNamespaces(t.namespaces)] = new AppendableString();
|
|
32
|
+
imports[filenameFromNamespaces(t.namespaces)] = {
|
|
33
|
+
namespaces: [],
|
|
34
|
+
lifecycleTypes: [],
|
|
35
|
+
};
|
|
36
|
+
});
|
|
22
37
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (options["enable-types"]) {
|
|
40
|
-
const resolved = resolveUnion(u, {
|
|
41
|
-
currentNamespace: n,
|
|
42
|
-
context,
|
|
43
|
-
nestlevel: 0,
|
|
44
|
-
isNamespaceRoot: true,
|
|
45
|
-
});
|
|
46
|
-
if (resolved) {
|
|
47
|
-
const doc = getDoc(context.program, u);
|
|
48
|
-
if (doc) file = file.addLine(`/** ${doc} */`);
|
|
49
|
-
file = file.addLine(`export type ${u.name} = ${resolved};\n`);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
38
|
+
const typeOrder: Type["kind"][] = ["Enum", "Scalar", "Model", "Union"];
|
|
39
|
+
typemap.sort(
|
|
40
|
+
(a, b) => typeOrder.indexOf(a.type.kind) - typeOrder.indexOf(b.type.kind),
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// resolve all types
|
|
44
|
+
for (let i = 0; i < typemap.length; i++) {
|
|
45
|
+
const t = typemap[i];
|
|
46
|
+
const filename = filenameFromNamespaces(t.namespaces);
|
|
47
|
+
const resolved = await Resolvable.resolve(Resolver.Type, t.type, {
|
|
48
|
+
program,
|
|
49
|
+
options,
|
|
50
|
+
emitDocs: true,
|
|
51
|
+
nestlevel: 0,
|
|
52
|
+
rootType: t,
|
|
53
|
+
typemap: typemap,
|
|
52
54
|
});
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
55
|
+
|
|
56
|
+
imports[filename].namespaces.push(...resolved.imports);
|
|
57
|
+
let declaration = "export ";
|
|
58
|
+
switch (t.type.kind) {
|
|
59
|
+
case "Enum":
|
|
60
|
+
declaration += `enum ${t.type.name}`;
|
|
61
|
+
break;
|
|
62
|
+
case "Scalar":
|
|
63
|
+
declaration += `type ${t.type.name} =`;
|
|
64
|
+
break;
|
|
65
|
+
case "Model":
|
|
66
|
+
declaration += `type ${t.type.name}${resolved.hasVisibility ? "<V extends Lifecycle = Lifecycle.All>" : ""} =`;
|
|
67
|
+
// Making ALL types generic (regardless of whether they need it) improves ease-of-use,
|
|
68
|
+
// both for the user as well for the dev when accessing known types.
|
|
69
|
+
// declaration += `type ${t.type.name}${resolved.hasVisibility ? "<V extends Lifecycle = Lifecycle.All>" : ""} =`;
|
|
70
|
+
break;
|
|
71
|
+
case "Union":
|
|
72
|
+
declaration += `type ${t.type.name}${resolved.hasVisibility ? "<V extends Lifecycle = Lifecycle.All>" : ""} =`;
|
|
73
|
+
// declaration += `type ${t.type.name}${resolved.hasVisibility ? "<V extends Lifecycle = Lifecycle.All>" : ""} =`;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
if (resolved.doc) files[filename].addLine(`/** ${resolved.doc} */`);
|
|
77
|
+
|
|
78
|
+
if (resolved.hasVisibility) {
|
|
79
|
+
imports[filename].lifecycleTypes.push("FilterLifecycle", "Lifecycle");
|
|
80
|
+
}
|
|
81
|
+
if (
|
|
82
|
+
(t.type as any).kind === "Model" &&
|
|
83
|
+
t.type.name !== "Array" &&
|
|
84
|
+
t.type.name !== "Record" &&
|
|
85
|
+
resolved.visibilityMap.replaceAll("{", "").replaceAll("}", "").trim()
|
|
86
|
+
) {
|
|
87
|
+
files[filename].addLine(`${declaration} ${resolved.resolved.value}`);
|
|
88
|
+
// files[filename].addLine(
|
|
89
|
+
// `${declaration} FilterLifecycle<${resolved.resolved.value}, typeof ${t.type.name}_VisMap, V>`,
|
|
90
|
+
// );
|
|
91
|
+
// files[filename].addLine(
|
|
92
|
+
// `export const ${t.type.name}_VisMap = ${resolved.visibilityMap} as const`,
|
|
93
|
+
// );
|
|
94
|
+
} else {
|
|
95
|
+
files[filename].addLine(`${declaration} ${resolved.resolved.value}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (options["enable-typeguards"]) {
|
|
99
|
+
const typeguard = await Resolvable.resolve(Resolver.Typeguard, t.type, {
|
|
100
|
+
program,
|
|
101
|
+
options,
|
|
102
|
+
nestlevel: 1,
|
|
103
|
+
rootType: t,
|
|
104
|
+
typemap: typemap,
|
|
105
|
+
accessor: "t",
|
|
106
|
+
});
|
|
107
|
+
if (typeguard.resolved.value) {
|
|
108
|
+
if (typeguard.hasVisibility) {
|
|
109
|
+
imports[filename].lifecycleTypes.push("Lifecycle");
|
|
110
|
+
files[filename].addLine(
|
|
111
|
+
`export function is${t.type.name}(t: any, vis: Lifecycle = Lifecycle.All): t is ${t.type.name}<typeof vis> {return (${typeguard.resolved})}`,
|
|
112
|
+
);
|
|
113
|
+
} else {
|
|
114
|
+
files[filename].addLine(
|
|
115
|
+
`export function is${t.type.name}(t: any): t is ${t.type.name} {return (${typeguard.resolved})}`,
|
|
116
|
+
);
|
|
65
117
|
}
|
|
118
|
+
imports[filename].namespaces.push(...typeguard.imports);
|
|
66
119
|
}
|
|
120
|
+
}
|
|
67
121
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
`export function is${m.name}(arg: any): arg is ${m.name} {`,
|
|
71
|
-
);
|
|
72
|
-
file = file.addLine("return (", 1);
|
|
73
|
-
getTypeguardModel(
|
|
74
|
-
m,
|
|
75
|
-
"arg",
|
|
76
|
-
undefined,
|
|
77
|
-
n,
|
|
78
|
-
!!options["serializable-date-types"],
|
|
79
|
-
)[0]
|
|
80
|
-
.split("\n")
|
|
81
|
-
.forEach((line) => {
|
|
82
|
-
file = file.addLine(line, 1);
|
|
83
|
-
});
|
|
84
|
-
file = file.addLine(");", 1);
|
|
85
|
-
file = file.addLine("};");
|
|
86
|
-
out.typeguardedNames.push({
|
|
87
|
-
filename: n.name.charAt(0).toUpperCase() + n.name.slice(1),
|
|
88
|
-
name: m.name,
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
file += "\n";
|
|
92
|
-
});
|
|
93
|
-
// set output for this namespace
|
|
94
|
-
out.files[n.name.charAt(0).toUpperCase() + n.name.slice(1)] = file;
|
|
95
|
-
// recursively iterate child namespaces
|
|
96
|
-
n.namespaces.forEach((ns) => traverseNamespace(ns));
|
|
97
|
-
};
|
|
98
|
-
traverseNamespace(namespace);
|
|
122
|
+
files[filename].append("\n");
|
|
123
|
+
}
|
|
99
124
|
|
|
100
|
-
|
|
101
|
-
|
|
125
|
+
const filesArr = Object.entries(files).filter((f) => !!f[1].value);
|
|
126
|
+
for (let i = 0; i < filesArr.length; i++) {
|
|
127
|
+
if (!filesArr[i][1].value) continue;
|
|
128
|
+
const filename = filesArr[i][0];
|
|
102
129
|
|
|
103
|
-
|
|
130
|
+
imports[filename].namespaces = unique2D(imports[filename].namespaces);
|
|
131
|
+
const importStrings = getImports(
|
|
132
|
+
imports[filename].namespaces.filter(
|
|
133
|
+
(i) => filenameFromNamespaces(i) !== filename,
|
|
134
|
+
),
|
|
135
|
+
);
|
|
136
|
+
if (imports[filename].lifecycleTypes.length > 0) {
|
|
137
|
+
importStrings.push(
|
|
138
|
+
`import {${[...new Set(imports[filename].lifecycleTypes)].join(", ")}} from './${visibilityHelperFileName}';`,
|
|
139
|
+
); // unique-ify
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const content = `/* eslint-disable */\n\n${autogenerateWarning}${importStrings.join("\n")}${importStrings.length > 0 ? "\n\n" : ""}${filesArr[i][1].value}`;
|
|
143
|
+
await emitFile(program, {
|
|
144
|
+
path: resolvePath(options["out-dir"], filename),
|
|
145
|
+
content: content,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
};
|