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.
Files changed (166) hide show
  1. package/.husky/pre-commit +2 -1
  2. package/.prettierignore +2 -1
  3. package/CHANGELOG.md +19 -24
  4. package/CODE_OF_CONDUCT.md +128 -0
  5. package/README.md +362 -219
  6. package/dist/src/emit_routedTypemap.d.ts +4 -0
  7. package/dist/src/emit_routedTypemap.js +83 -0
  8. package/dist/src/emit_routedTypemap.js.map +1 -0
  9. package/dist/src/emit_routes.d.ts +3 -2
  10. package/dist/src/emit_routes.js +73 -47
  11. package/dist/src/emit_routes.js.map +1 -1
  12. package/dist/src/emit_types.d.ts +3 -9
  13. package/dist/src/emit_types.js +109 -74
  14. package/dist/src/emit_types.js.map +1 -1
  15. package/dist/src/emitter.d.ts +2 -6
  16. package/dist/src/emitter.js +18 -76
  17. package/dist/src/emitter.js.map +1 -1
  18. package/dist/src/helpers/appendableString.d.ts +15 -0
  19. package/dist/src/helpers/appendableString.js +41 -0
  20. package/dist/src/helpers/appendableString.js.map +1 -0
  21. package/dist/src/helpers/arrays.d.ts +3 -0
  22. package/dist/src/helpers/arrays.js +23 -0
  23. package/dist/src/helpers/arrays.js.map +1 -0
  24. package/dist/src/{helper_autogenerateWarning.d.ts → helpers/autogenerateWarning.d.ts} +1 -1
  25. package/dist/src/{helper_autogenerateWarning.js → helpers/autogenerateWarning.js} +1 -2
  26. package/dist/src/helpers/autogenerateWarning.js.map +1 -0
  27. package/dist/src/helpers/buildTypeMap.d.ts +12 -0
  28. package/dist/src/helpers/buildTypeMap.js +44 -0
  29. package/dist/src/helpers/buildTypeMap.js.map +1 -0
  30. package/dist/src/helpers/diagnostics.d.ts +4 -0
  31. package/dist/src/helpers/diagnostics.js +15 -0
  32. package/dist/src/helpers/diagnostics.js.map +1 -0
  33. package/dist/src/helpers/getImports.d.ts +2 -0
  34. package/dist/src/helpers/getImports.js +3 -0
  35. package/dist/src/helpers/getImports.js.map +1 -0
  36. package/dist/src/helpers/namespaces.d.ts +4 -0
  37. package/dist/src/helpers/namespaces.js +14 -0
  38. package/dist/src/helpers/namespaces.js.map +1 -0
  39. package/dist/src/helpers/visibilityHelperFile.d.ts +4 -0
  40. package/dist/src/helpers/visibilityHelperFile.js +57 -0
  41. package/dist/src/helpers/visibilityHelperFile.js.map +1 -0
  42. package/dist/src/lib.d.ts +4 -1
  43. package/dist/src/lib.js +14 -3
  44. package/dist/src/lib.js.map +1 -1
  45. package/dist/src/parseOptions.d.ts +8 -0
  46. package/dist/src/parseOptions.js +26 -0
  47. package/dist/src/parseOptions.js.map +1 -0
  48. package/dist/src/resolve/Resolvable.d.ts +32 -0
  49. package/dist/src/resolve/Resolvable.js +180 -0
  50. package/dist/src/resolve/Resolvable.js.map +1 -0
  51. package/dist/src/resolve/Resolvable_helpers.d.ts +42 -0
  52. package/dist/src/resolve/Resolvable_helpers.js +19 -0
  53. package/dist/src/resolve/Resolvable_helpers.js.map +1 -0
  54. package/dist/src/resolve/operationTypemap.d.ts +21 -0
  55. package/dist/src/resolve/operationTypemap.js +138 -0
  56. package/dist/src/resolve/operationTypemap.js.map +1 -0
  57. package/dist/src/resolve/types/Enum.d.ts +21 -0
  58. package/dist/src/resolve/types/Enum.js +61 -0
  59. package/dist/src/resolve/types/Enum.js.map +1 -0
  60. package/dist/src/resolve/types/Model.Indexed.d.ts +12 -0
  61. package/dist/src/resolve/types/Model.Indexed.js +69 -0
  62. package/dist/src/resolve/types/Model.Indexed.js.map +1 -0
  63. package/dist/src/resolve/types/Model.Shaped.d.ts +17 -0
  64. package/dist/src/resolve/types/Model.Shaped.js +210 -0
  65. package/dist/src/resolve/types/Model.Shaped.js.map +1 -0
  66. package/dist/src/resolve/types/Scalar.d.ts +9 -0
  67. package/dist/src/resolve/types/Scalar.js +109 -0
  68. package/dist/src/resolve/types/Scalar.js.map +1 -0
  69. package/dist/src/resolve/types/Simple.d.ts +11 -0
  70. package/dist/src/resolve/types/Simple.js +49 -0
  71. package/dist/src/resolve/types/Simple.js.map +1 -0
  72. package/dist/src/resolve/types/Tuple.d.ts +9 -0
  73. package/dist/src/resolve/types/Tuple.js +38 -0
  74. package/dist/src/resolve/types/Tuple.js.map +1 -0
  75. package/dist/src/resolve/types/Union.d.ts +9 -0
  76. package/dist/src/resolve/types/Union.js +36 -0
  77. package/dist/src/resolve/types/Union.js.map +1 -0
  78. package/eslint.config.js +12 -4
  79. package/package.json +2 -2
  80. package/src/emit_routedTypemap.ts +128 -0
  81. package/src/emit_routes.ts +108 -61
  82. package/src/emit_types.ts +137 -92
  83. package/src/emitter.ts +19 -107
  84. package/src/helpers/appendableString.ts +52 -0
  85. package/src/helpers/arrays.ts +21 -0
  86. package/src/{helper_autogenerateWarning.ts → helpers/autogenerateWarning.ts} +0 -1
  87. package/src/helpers/buildTypeMap.ts +72 -0
  88. package/src/helpers/diagnostics.ts +19 -0
  89. package/src/helpers/getImports.ts +9 -0
  90. package/src/helpers/namespaces.ts +18 -0
  91. package/src/helpers/visibilityHelperFile.ts +63 -0
  92. package/src/lib.ts +25 -4
  93. package/src/parseOptions.ts +26 -0
  94. package/src/resolve/Resolvable.ts +267 -0
  95. package/src/resolve/Resolvable_helpers.ts +65 -0
  96. package/src/resolve/operationTypemap.ts +212 -0
  97. package/src/resolve/types/Enum.ts +92 -0
  98. package/src/resolve/types/Model.Indexed.ts +113 -0
  99. package/src/resolve/types/Model.Shaped.ts +291 -0
  100. package/src/resolve/types/Scalar.ts +140 -0
  101. package/src/resolve/types/Simple.ts +88 -0
  102. package/src/resolve/types/Tuple.ts +56 -0
  103. package/src/resolve/types/Union.ts +52 -0
  104. package/test/helpers/integrationTest-novis.tsp +51 -0
  105. package/test/helpers/integrationTest.tsp +53 -0
  106. package/test/helpers/largeModel.tsp +40 -0
  107. package/test/{runner.ts → helpers/runner.ts} +1 -1
  108. package/test/helpers/ts.ts +11 -0
  109. package/test/helpers/wrapper.ts +144 -0
  110. package/test/routes/routes.target.ts +35 -0
  111. package/test/routes/routes.test.ts +22 -0
  112. package/test/typeguards/combined.test.ts +78 -0
  113. package/test/typeguards/enum.test.ts +10 -0
  114. package/test/typeguards/model.indexed.test.ts +68 -0
  115. package/test/typeguards/model.shaped.test.ts +38 -0
  116. package/test/typeguards/scalar.test.ts +62 -0
  117. package/test/typeguards/simple.test.ts +35 -0
  118. package/test/typeguards/tuple.test.ts +34 -0
  119. package/test/typeguards/union.test.ts +29 -0
  120. package/test/typemap/typemap-novis.target.ts +38 -0
  121. package/test/typemap/typemap.target.ts +39 -0
  122. package/test/typemap/typemap.test.ts +48 -0
  123. package/test/types/combined.test.ts +71 -0
  124. package/test/types/enum.test.ts +57 -0
  125. package/test/types/model.indexed.test.ts +46 -0
  126. package/test/types/model.shaped.test.ts +23 -0
  127. package/test/types/scalar.test.ts +53 -0
  128. package/test/types/simple.test.ts +20 -0
  129. package/test/types/tuple.test.ts +29 -0
  130. package/test/types/union.test.ts +20 -0
  131. package/tsconfig.json +1 -0
  132. package/dist/src/emit_mapped_types.d.ts +0 -2
  133. package/dist/src/emit_mapped_types.js +0 -124
  134. package/dist/src/emit_mapped_types.js.map +0 -1
  135. package/dist/src/emit_types_resolve.d.ts +0 -22
  136. package/dist/src/emit_types_resolve.js +0 -217
  137. package/dist/src/emit_types_resolve.js.map +0 -1
  138. package/dist/src/emit_types_typeguards.d.ts +0 -17
  139. package/dist/src/emit_types_typeguards.js +0 -121
  140. package/dist/src/emit_types_typeguards.js.map +0 -1
  141. package/dist/src/helper_autogenerateWarning.js.map +0 -1
  142. package/src/emit_mapped_types.ts +0 -155
  143. package/src/emit_types_resolve.ts +0 -280
  144. package/src/emit_types_typeguards.ts +0 -178
  145. package/test/main.test.ts +0 -83
  146. package/test/out/.gitkeep +0 -0
  147. package/test/targets/enum.routed-types.ts +0 -59
  148. package/test/targets/enum.routes.ts +0 -29
  149. package/test/targets/enum.target.ts +0 -39
  150. package/test/targets/enum.tsp +0 -36
  151. package/test/targets/pr8.routed-types.ts +0 -131
  152. package/test/targets/pr8.routes.ts +0 -30
  153. package/test/targets/pr8.target.ts +0 -97
  154. package/test/targets/pr8.tsp +0 -62
  155. package/test/targets/simple-routes.routed-types.ts +0 -64
  156. package/test/targets/simple-routes.routes.ts +0 -29
  157. package/test/targets/simple-routes.target.ts +0 -21
  158. package/test/targets/simple-routes.tsp +0 -23
  159. package/test/targets/union.routed-types.ts +0 -59
  160. package/test/targets/union.routes.ts +0 -23
  161. package/test/targets/union.target.ts +0 -59
  162. package/test/targets/union.tsp +0 -38
  163. package/test/targets/visibility.routed-types.ts +0 -81
  164. package/test/targets/visibility.routes.ts +0 -38
  165. package/test/targets/visibility.target.ts +0 -36
  166. 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
+ };
@@ -1,63 +1,80 @@
1
1
  import {
2
- EmitContext,
2
+ emitFile,
3
3
  getDoc,
4
+ Interface,
4
5
  isTemplateDeclaration,
5
6
  Namespace,
6
- Operation,
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
- context: EmitContext,
12
- namespace: Namespace,
13
- ): string => {
14
- let out = `export const routes_${context.options["root-namespace"]} = {\n`;
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 traverseNamespace = (n: Namespace, nestLevel: number): void => {
17
- // operations
18
- const processOp = (op: Operation) => {
19
- const httpOp = getHttpOperation(context.program, op);
23
+ const files: Record<string, AppendableString> = {};
20
24
 
21
- // jsdoc comment
22
- const doc = getDoc(context.program, op);
23
- if (doc) out = out.addLine(`/** ${doc} */`, nestLevel + 1);
24
-
25
- // start op body
26
- out = out.addLine(`${op.name}: {`, nestLevel + 1);
27
-
28
- // HTTP method
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
- // path
35
- out = out.addLine(`path: '${httpOp[0].path}',`, nestLevel + 2);
35
+ const numCollections =
36
+ Array.from((n as any).interfaces ?? []).length +
37
+ Array.from(((n as any).namespaces ?? []) as any[]).length;
36
38
 
37
- // getUrl
38
- const pathParams = httpOp[0].parameters.parameters.filter(
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 pathParamsArgsString = pathParams
46
+ const urlParams = `params: {${pathParams
42
47
  .map((p) => `${p.name}: string`)
43
- .join(", ");
44
- const pathParamsArg = `params: {${pathParamsArgsString}}`;
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[0].path}\``,
52
+ `\`${httpOp.path}\``,
49
53
  );
50
54
 
51
- out = out.addLine(
52
- `getUrl: (${pathParams.length > 0 ? pathParamsArg : ""}): string => ${pathParamsOutString},`,
53
- nestLevel + 2,
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(context.program, op);
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
- nestLevel + 2,
109
+ nestlevel + 1,
94
110
  );
95
111
 
96
- // finalize route entry
97
- out = out.addLine("},", nestLevel + 1);
98
- }; // end operations
99
-
100
- n.operations.forEach(processOp);
101
- n.interfaces.forEach((itf) => {
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
- // get and traverse all namespaces
106
- let nsnum = 0;
107
- n.namespaces.forEach((ns) => {
108
- nsnum++;
109
- const doc = getDoc(context.program, ns);
110
- if (doc) out = out.addLine(`/** ${doc} */`, nestLevel + 1);
111
- out = out.addLine(`${ns.name}: {`, nestLevel + 1);
112
- traverseNamespace(ns, nestLevel + 1);
113
- out = out.addLine(
114
- `}${nsnum < n.namespaces.size ? "," : ""}`,
115
- nestLevel + 1,
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
- traverseNamespace(namespace, 0);
121
- out += "} as const;\n";
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 { EmitContext, getDoc, Namespace } from "@typespec/compiler";
2
- import {
3
- resolveEnum,
4
- resolveModel,
5
- resolveUnion,
6
- } from "./emit_types_resolve.js";
7
- import { getTypeguardModel } from "./emit_types_typeguards.js";
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
- context: EmitContext,
12
- namespace: Namespace,
13
+ export const emitTypes = async (
14
+ program: Program,
13
15
  options: EmitterOptions,
14
- ): {
15
- files: Record<string, string>;
16
- typeguardedNames: Array<{ filename: string; name: string }>;
17
- } => {
18
- const out: ReturnType<typeof emitTypes> = { files: {}, typeguardedNames: [] };
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
- const traverseNamespace = (n: Namespace): void => {
21
- let file = "";
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
- n.enums.forEach((e) => {
24
- if (options["enable-types"]) {
25
- const resolved = resolveEnum(e, {
26
- context: context,
27
- currentNamespace: n,
28
- nestlevel: 0,
29
- isNamespaceRoot: true,
30
- });
31
- if (resolved) {
32
- const doc = getDoc(context.program, e);
33
- if (doc) file = file.addLine(`/** ${doc} */`);
34
- file = file.addLine(`export enum ${e.name} ${resolved};\n`);
35
- }
36
- }
37
- });
38
- n.unions.forEach((u) => {
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
- n.models.forEach((m) => {
54
- if (options["enable-types"]) {
55
- const resolved = resolveModel(m, {
56
- nestlevel: 0,
57
- currentNamespace: n,
58
- context,
59
- isNamespaceRoot: true,
60
- });
61
- if (resolved) {
62
- const doc = getDoc(context.program, m);
63
- if (doc) file = file.addLine(`/** ${doc} */`);
64
- file = file.addLine(`export interface ${m.name} ${resolved};`);
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
- if (options["enable-typeguards"]) {
69
- file = file.addLine(
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
- return out;
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
- export default emitTypes;
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
+ };