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,267 @@
1
+ import { Model, Type } from "@typespec/compiler";
2
+ import { AppendableString } from "../helpers/appendableString.js";
3
+ import { compareArrays } from "../helpers/arrays.js";
4
+ import { TTypeMap } from "../helpers/buildTypeMap.js";
5
+ import { reportDiagnostic } from "../helpers/diagnostics.js";
6
+ import { namespaceListFromNamespace } from "../helpers/namespaces.js";
7
+ import {
8
+ Resolver,
9
+ ResolverOptions,
10
+ ResolverResult,
11
+ } from "./Resolvable_helpers.js";
12
+
13
+ export abstract class Resolvable<T extends Type> {
14
+ /** Recursively resolves a type. */
15
+ static async resolve<R extends Resolver>(
16
+ r: R,
17
+ t: Type,
18
+ opts: ResolverOptions<R>,
19
+ ): Promise<ResolverResult<R>> {
20
+ const ret: ResolverResult<R> = {
21
+ resolved: new AppendableString(),
22
+ visibilityMap: "",
23
+ hasVisibility: false,
24
+ imports: [],
25
+ };
26
+ await (await Resolvable.for(r, t)).resolve(opts, ret);
27
+ return ret;
28
+ }
29
+
30
+ /**
31
+ * To be called from within the resolution logic of a type:
32
+ * Resolves another type for the current type, handling required changes to the resolution options.
33
+ * The passed `out` object is not signifiantly changed, only the `hasVisibility` flag is managed.
34
+ */
35
+ protected async resolveNested<R extends typeof this._r>(
36
+ t: Type,
37
+ opts: ResolverOptions<R>,
38
+ out: ResolverResult<R>,
39
+ nest: boolean = true,
40
+ ): Promise<ResolverResult<R>> {
41
+ // prepare nested resolution
42
+ if (nest) opts.nestlevel++;
43
+ if (!opts.parents) opts.parents = [];
44
+ opts.parents.push(this._t);
45
+
46
+ const ret = await Resolvable.resolve(this._r, t, opts);
47
+
48
+ // revert to current level
49
+ if (nest) opts.nestlevel--;
50
+ opts.parents.pop();
51
+
52
+ return ret;
53
+ }
54
+
55
+ /** Creates the appropriate Resolvable instance for a given type. */
56
+ static async for<T extends Type>(r: Resolver, t: T): Promise<Resolvable<T>> {
57
+ // use lazy imports to break circular dependencies
58
+ const { ResolvableSimple } = await import("./types/Simple.js");
59
+ if (ResolvableSimple.AllowedTypeKinds.includes(t.kind as any)) {
60
+ return new ResolvableSimple(t, r) as any;
61
+ }
62
+ switch (t.kind) {
63
+ case "Enum": {
64
+ const { ResolvableEnum } = await import("./types/Enum.js");
65
+ return new ResolvableEnum(t, r) as any;
66
+ }
67
+ case "EnumMember": {
68
+ const { ResolvableEnumMember } = await import("./types/Enum.js");
69
+ return new ResolvableEnumMember(t, r) as any;
70
+ }
71
+ case "Model": {
72
+ const { IndexedModel } = await import("./types/Model.Indexed.js");
73
+ const { ShapedModel } = await import("./types/Model.Shaped.js");
74
+ switch ((t as Model).name) {
75
+ case "Array":
76
+ case "Record":
77
+ return new IndexedModel(t, r) as any;
78
+
79
+ default:
80
+ return new ShapedModel(t, r) as any;
81
+ }
82
+ }
83
+ case "Scalar": {
84
+ const { ResolvableScalar } = await import("./types/Scalar.js");
85
+ return new ResolvableScalar(t, r) as any;
86
+ }
87
+ case "Tuple": {
88
+ const { ResolvableTuple } = await import("./types/Tuple.js");
89
+ return new ResolvableTuple(t, r) as any;
90
+ }
91
+ case "Union": {
92
+ const { ResolvableUnion } = await import("./types/Union.js");
93
+ return new ResolvableUnion(t, r) as any;
94
+ }
95
+
96
+ default:
97
+ reportDiagnostic({
98
+ code: "resolve-unresolved",
99
+ message: `Could not resolve type ${t.kind}`,
100
+ severity: "error",
101
+ });
102
+ throw new TypeError(`Could not resolve type ${t.kind}`);
103
+ }
104
+ }
105
+
106
+ constructor(t: T, r: Resolver) {
107
+ this._t = t;
108
+ this._r = r;
109
+ }
110
+
111
+ protected abstract expectedTypeKind: string;
112
+
113
+ protected _t: T;
114
+ protected _r: Resolver;
115
+
116
+ /** Modifies passed output object in-place. */
117
+ protected abstract type(
118
+ opts: ResolverOptions<Resolver.Type>,
119
+ out: ResolverResult<Resolver.Type>,
120
+ ): Promise<void>;
121
+ /** Modifies passed output object in-place. */
122
+ protected abstract typeguard(
123
+ opts: ResolverOptions<Resolver.Typeguard>,
124
+ out: ResolverResult<Resolver.Typeguard>,
125
+ ): Promise<void>;
126
+
127
+ /** Resolves the type, modifying an output object in place. */
128
+ public async resolve(
129
+ opts: ResolverOptions<Resolver>,
130
+ out: ResolverResult<Resolver>,
131
+ ): Promise<void> {
132
+ this.validate();
133
+
134
+ const ownVisibility = await this.hasVisibility(opts, out);
135
+ if (ownVisibility) out.hasVisibility = true;
136
+
137
+ // check for known resolved type
138
+ if (opts.rootTypeReady && (this._t as any).name) {
139
+ // type without name can't be resolved
140
+ const currentNamespaces = namespaceListFromNamespace(
141
+ (this._t as any).namespace,
142
+ );
143
+ // find known resolved type
144
+ const foundKnownResolved = ((): TTypeMap[number] | null => {
145
+ const currentNamespaces = namespaceListFromNamespace(
146
+ (this._t as any).namespace,
147
+ );
148
+ if (currentNamespaces && currentNamespaces.length > 0) {
149
+ const found = opts.typemap.find(
150
+ (mt) =>
151
+ mt.type.kind === this._t.kind &&
152
+ mt.type.name === (this._t as any).name &&
153
+ compareArrays(mt.namespaces, currentNamespaces),
154
+ );
155
+ return found ?? null;
156
+ }
157
+ return null;
158
+ })();
159
+
160
+ // if found, return name instead of full resolution
161
+ if (foundKnownResolved) {
162
+ if (
163
+ opts.rootType &&
164
+ compareArrays(currentNamespaces!, opts.rootType.namespaces)
165
+ ) {
166
+ // if namespace of found is same as current (= same file), just return name
167
+ out.resolved.append(
168
+ this._r === Resolver.Type
169
+ ? foundKnownResolved.type.name
170
+ : this._t.kind === "Enum"
171
+ ? "true" // enums don't have typeguards
172
+ : `is${foundKnownResolved.type.name}(${(opts as ResolverOptions<Resolver.Typeguard>).accessor}${ownVisibility ? ", vis" : ""})`,
173
+ );
174
+ } else {
175
+ // found is in different file than current, prepare importing
176
+ out.resolved.append(
177
+ `${foundKnownResolved.namespaces.join("_")}.${
178
+ this._r === Resolver.Type
179
+ ? foundKnownResolved.type.name
180
+ : this._t.kind === "Enum"
181
+ ? "true" // enums don't have typeguards
182
+ : `is${foundKnownResolved.type.name}(${(opts as ResolverOptions<Resolver.Typeguard>).accessor}${ownVisibility ? ", vis" : ""})`
183
+ }`,
184
+ );
185
+ out.imports.push(foundKnownResolved.namespaces);
186
+ }
187
+
188
+ // optionally allow found type to modify output
189
+ if (this.transformKnownType !== undefined)
190
+ this.transformKnownType(opts, out);
191
+ return;
192
+ }
193
+ }
194
+
195
+ // this prevents the root type from triggering known resolution
196
+ opts.rootTypeReady = true;
197
+
198
+ switch (this._r) {
199
+ case Resolver.Type:
200
+ return await this.type(
201
+ opts as ResolverOptions<Resolver.Type>,
202
+ out as ResolverResult<Resolver.Type>,
203
+ );
204
+ case Resolver.Typeguard:
205
+ return await this.typeguard(
206
+ opts as ResolverOptions<Resolver.Typeguard>,
207
+ out as ResolverResult<Resolver.Typeguard>,
208
+ );
209
+ }
210
+ }
211
+
212
+ protected transformKnownType(
213
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
214
+ opts: ResolverOptions<Resolver>,
215
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
216
+ out: ResolverResult<Resolver>,
217
+ ): void {}
218
+
219
+ /** Returns true if this type or anything it contains or references has visibility modifiers. */
220
+ public async hasVisibility(
221
+ opts: ResolverOptions<Resolver>,
222
+ out: ResolverResult<Resolver>,
223
+ ): Promise<boolean> {
224
+ return out.hasVisibility;
225
+ }
226
+ /** Returns true if this type or anything it contains or references has visibility modifiers. */
227
+ protected static async hasVisibility(
228
+ t: Type,
229
+ opts: ResolverOptions<Resolver>,
230
+ out: ResolverResult<Resolver>,
231
+ current: Type,
232
+ ): Promise<boolean> {
233
+ if (!opts.parents) opts.parents = [];
234
+ opts.parents.push(current);
235
+ const ret = await (
236
+ await Resolvable.for(Resolver.Type /** doesn't matter */, t)
237
+ ).hasVisibility(opts, out);
238
+ opts.parents.pop();
239
+ return ret;
240
+ }
241
+
242
+ /** Checks its stored type to ensure the instance was created on a suitable type. */
243
+ protected validate(): void {
244
+ if (this._t.kind !== this.expectedTypeKind) {
245
+ this.diagnostic(
246
+ "typeclass-mismatch",
247
+ `Tried creating resolver for ${this.expectedTypeKind} on type ${this._t.kind}`,
248
+ "error",
249
+ );
250
+ throw new TypeError(
251
+ `Type-kind/class mismatch: instanced ${this.expectedTypeKind} on type ${this._t.kind}`,
252
+ );
253
+ }
254
+ }
255
+
256
+ protected diagnostic(
257
+ code: string,
258
+ msg: string,
259
+ level: "error" | "warning",
260
+ ): void {
261
+ reportDiagnostic({
262
+ code: `resolve-${this._r}-${code}`,
263
+ message: msg,
264
+ severity: level,
265
+ });
266
+ }
267
+ }
@@ -0,0 +1,65 @@
1
+ import { Program, Type } from "@typespec/compiler";
2
+ import { AppendableString } from "../helpers/appendableString.js";
3
+ import { compareArrays } from "../helpers/arrays.js";
4
+ import { TTypeMap } from "../helpers/buildTypeMap.js";
5
+ import { namespaceListFromNamespace } from "../helpers/namespaces.js";
6
+ import { EmitterOptions } from "../lib.js";
7
+
8
+ export enum Resolver {
9
+ Type,
10
+ Typeguard,
11
+ }
12
+
13
+ export type ResolverOptions<R extends Resolver> = {
14
+ program: Program;
15
+ options: EmitterOptions;
16
+ typemap: TTypeMap;
17
+ nestlevel: number;
18
+ /** The type the entire resolution chain started with. */
19
+ rootType: TTypeMap[number] | null;
20
+ /**
21
+ * This tracks whether or not resolving *to* the type currently being investigated
22
+ * is valid. This allows for recursive types.
23
+ */
24
+ rootTypeReady?: boolean;
25
+ /** The types "up the chain" - "parent" types on nested resolution. */
26
+ parents?: Type[];
27
+ } & (R extends Resolver.Type
28
+ ? {
29
+ emitDocs: boolean;
30
+ }
31
+ : {
32
+ /**
33
+ * This string describes how a function (part) of a typeguard can
34
+ * access the value currently being tested by it.
35
+ * Example: typeguard(t: any) on `n` for {n: string} -> this = "t"
36
+ */
37
+ accessor: string;
38
+ });
39
+
40
+ /** Tries to find type in known types map */
41
+ export const getKnownResolvedType = (
42
+ typemap: TTypeMap,
43
+ t: any,
44
+ ): TTypeMap[number] | null => {
45
+ const currentNamespaces = namespaceListFromNamespace((t as any).namespace);
46
+ if (currentNamespaces && currentNamespaces.length > 0) {
47
+ const found = typemap.find(
48
+ (mt) =>
49
+ mt.type.kind === t.kind &&
50
+ mt.type.name === (t as any).name &&
51
+ compareArrays(mt.namespaces, currentNamespaces),
52
+ );
53
+ return found ?? null;
54
+ }
55
+ return null;
56
+ };
57
+
58
+ export type ResolverResult<R extends Resolver> = {
59
+ readonly resolved: AppendableString;
60
+ visibilityMap: string;
61
+ imports: TTypeMap[number]["namespaces"][];
62
+ /** Whether any resolved part (nested types and base types included) have @visibility modifiers */
63
+ hasVisibility: boolean;
64
+ doc?: R extends Resolver.Type ? string : never;
65
+ };
@@ -0,0 +1,212 @@
1
+ import { Operation, Program, Type } from "@typespec/compiler";
2
+ import { getHttpOperation } from "@typespec/http";
3
+ import { TTypeMap } from "../helpers/buildTypeMap.js";
4
+ import { EmitterOptions } from "../lib.js";
5
+ import { Resolvable } from "./Resolvable.js";
6
+ import { Resolver } from "./Resolvable_helpers.js";
7
+
8
+ /** Maps a route path to its typemap definition and required imports */
9
+ export type TOperationTypemap = {
10
+ // "string" in these does not refer to the type "string"; it's the typescript code *as* string.
11
+ request: { content: string; hasVisibility: boolean };
12
+ response: {
13
+ content: Array<{
14
+ status: number | "unknown";
15
+ body: string;
16
+ hasVisibility: boolean;
17
+ }>;
18
+ };
19
+ };
20
+
21
+ export const resolveOperationTypemap = async (
22
+ program: Program,
23
+ options: EmitterOptions,
24
+ typemap: TTypeMap,
25
+ op: Operation,
26
+ ): Promise<{
27
+ types: TOperationTypemap;
28
+ imports: TTypeMap[number]["namespaces"][];
29
+ }> => {
30
+ const httpOp = getHttpOperation(program, op)[0];
31
+ const ret: Awaited<ReturnType<typeof resolveOperationTypemap>> = {
32
+ types: {
33
+ request: { content: "null", hasVisibility: false },
34
+ response: { content: [] },
35
+ },
36
+ imports: [],
37
+ };
38
+
39
+ // request
40
+ if (httpOp.parameters.body) {
41
+ const resolved = await Resolvable.resolve(
42
+ Resolver.Type,
43
+ httpOp.parameters.body.type,
44
+ {
45
+ program,
46
+ options,
47
+ emitDocs: false,
48
+ nestlevel: 3,
49
+ rootType: null,
50
+ typemap,
51
+ rootTypeReady: true,
52
+ },
53
+ );
54
+ ret.imports.push(...resolved.imports);
55
+ ret.types.request.content = replaceLifecycle(
56
+ resolved.resolved.value,
57
+ httpOp.verb.toUpperCase(),
58
+ resolved.hasVisibility,
59
+ );
60
+ if (resolved.hasVisibility) ret.types.request.hasVisibility = true;
61
+ }
62
+
63
+ // response
64
+ if (op.returnType) {
65
+ const getReturnType = async (
66
+ t: Type,
67
+ ): Promise<{
68
+ content: {
69
+ status: number | "unknown";
70
+ body: string;
71
+ hasVisibility: boolean;
72
+ }[];
73
+ }> => {
74
+ const responseRet: TOperationTypemap["response"] = { content: [] };
75
+
76
+ switch (t.kind) {
77
+ case "Model": {
78
+ // If the return type is a model, it may either be a "blank" body or a fully
79
+ // qualified response with status and body.
80
+ const modelret: TOperationTypemap["response"]["content"][number] = {
81
+ status: 200,
82
+ body: "{}",
83
+ hasVisibility: false,
84
+ };
85
+
86
+ // check for fully qualified response or plain body
87
+ let wasFullyQualified = false;
88
+ for (const prop of t.properties) {
89
+ for (const decorator of prop[1].decorators) {
90
+ // find status code
91
+ if (
92
+ decorator.definition?.name === "@statusCode" &&
93
+ prop[1].type.kind === "Number"
94
+ )
95
+ modelret.status = prop[1].type.value;
96
+ // find body definiton
97
+ if (decorator.definition?.name === "@body") {
98
+ const resolved = await Resolvable.resolve(
99
+ Resolver.Type,
100
+ prop[1].type,
101
+ {
102
+ program,
103
+ options,
104
+ emitDocs: false,
105
+ nestlevel: 3,
106
+ rootType: null,
107
+ typemap,
108
+ rootTypeReady: true,
109
+ },
110
+ );
111
+ if (resolved.hasVisibility) modelret.hasVisibility = true;
112
+ ret.imports.push(...resolved.imports);
113
+ modelret.body = replaceLifecycle(
114
+ resolved.resolved.value,
115
+ "RETURN",
116
+ resolved.hasVisibility,
117
+ );
118
+ wasFullyQualified = true;
119
+ }
120
+ }
121
+ }
122
+
123
+ if (!wasFullyQualified) {
124
+ const resolved = await Resolvable.resolve(Resolver.Type, t, {
125
+ program,
126
+ options,
127
+ emitDocs: false,
128
+ nestlevel: 3,
129
+ rootType: null,
130
+ typemap,
131
+ rootTypeReady: true,
132
+ });
133
+ if (resolved.hasVisibility) modelret.hasVisibility = true;
134
+ ret.imports.push(...resolved.imports);
135
+ modelret.body = replaceLifecycle(
136
+ resolved.resolved.value,
137
+ "RETURN",
138
+ resolved.hasVisibility,
139
+ );
140
+ }
141
+
142
+ responseRet.content.push(modelret);
143
+
144
+ break;
145
+ }
146
+
147
+ case "Union": {
148
+ // if return type is a union, each variant may be fully qualified or body-only
149
+ for (const variant of t.variants) {
150
+ const resolved = await getReturnType(variant[1].type);
151
+ responseRet.content.push(...resolved.content);
152
+ }
153
+ break;
154
+ }
155
+
156
+ default: {
157
+ // return type does not have a qualified body; making one up and resolving that one
158
+ const resolved = await Resolvable.resolve(Resolver.Type, t, {
159
+ program,
160
+ options,
161
+ emitDocs: false,
162
+ nestlevel: 3,
163
+ rootType: null,
164
+ typemap,
165
+ rootTypeReady: true,
166
+ });
167
+ ret.imports.push(...resolved.imports);
168
+ responseRet.content.push({
169
+ status: 200,
170
+ body: replaceLifecycle(
171
+ resolved.resolved.value,
172
+ httpOp.verb.toUpperCase(),
173
+ resolved.hasVisibility,
174
+ ),
175
+ hasVisibility: resolved.hasVisibility,
176
+ });
177
+ break;
178
+ }
179
+ }
180
+
181
+ return responseRet;
182
+ };
183
+ ret.types.response = await getReturnType(op.returnType);
184
+ }
185
+
186
+ return ret;
187
+ };
188
+
189
+ /** Maps HTTP verbs to lifecycle states. See @typespec/compiler/.../visibility.tsp */
190
+ const VerbToLifecycle = {
191
+ RETURN: ["Read"], // for all return types
192
+ POST: ["Create"],
193
+ PUT: ["Create", "Update"],
194
+ PATCH: ["Update"],
195
+ DELETE: ["Delete"],
196
+ GET: ["Query"], // *parameters* of request, return type is still RETURN
197
+ Head: ["Query"],
198
+ };
199
+
200
+ // lifecycle assignment helper
201
+ const replaceLifecycle = (
202
+ resolved: string,
203
+ verb: string,
204
+ hasVisibility: boolean,
205
+ ): string => {
206
+ const opLifecycle = VerbToLifecycle[verb as keyof typeof VerbToLifecycle];
207
+ if (!hasVisibility) return resolved;
208
+ return resolved.replaceAll(
209
+ "V>",
210
+ `V extends Lifecycle.All ? (${opLifecycle.map((l) => `Lifecycle.${l}`).join(" | ")}) : V>`,
211
+ );
212
+ };
@@ -0,0 +1,92 @@
1
+ import { Enum, EnumMember, getDoc } from "@typespec/compiler";
2
+ import { compareArrays } from "../../helpers/arrays.js";
3
+ import { namespaceListFromNamespace } from "../../helpers/namespaces.js";
4
+ import { Resolvable } from "../Resolvable.js";
5
+ import {
6
+ Resolver,
7
+ ResolverOptions,
8
+ ResolverResult,
9
+ } from "../Resolvable_helpers.js";
10
+
11
+ /**
12
+ * This resolves Enums completely, meaning it doesn't try to resolve
13
+ * members as EnumMember, because EnumMember resolution references the base Enum.
14
+ */
15
+ export class ResolvableEnum extends Resolvable<Enum> {
16
+ protected expectedTypeKind = "Enum";
17
+
18
+ protected async type(
19
+ opts: ResolverOptions<Resolver.Type>,
20
+ out: ResolverResult<Resolver.Type>,
21
+ ): Promise<void> {
22
+ if (opts.emitDocs) {
23
+ out.doc = getDoc(opts.program, this._t);
24
+ }
25
+ out.resolved.append("{\n");
26
+ let i = 1;
27
+ this._t.members.forEach((member) => {
28
+ const val = resolveEnumMemberValue(member, opts);
29
+ out.resolved.addLine(
30
+ `${member.name.includes("-") ? `'${member.name}'` : member.name}${val}${i < this._t.members.size ? "," : ""}`,
31
+ opts.nestlevel + 1,
32
+ );
33
+ i++;
34
+ });
35
+ out.resolved.addLine("}", opts.nestlevel, "continued");
36
+ }
37
+
38
+ protected async typeguard(): Promise<void> {
39
+ // enums cannot have typeguards
40
+ return;
41
+ }
42
+ }
43
+
44
+ /**
45
+ * This cannot resolve members during Enum resolution, because this
46
+ * will try to reference the base Enum by name.
47
+ */
48
+ export class ResolvableEnumMember extends Resolvable<EnumMember> {
49
+ protected expectedTypeKind = "EnumMember";
50
+
51
+ protected async type(
52
+ opts: ResolverOptions<Resolver.Type>,
53
+ out: ResolverResult<Resolver.Type>,
54
+ ): Promise<void> {
55
+ const foundParent = opts.typemap.find(
56
+ (mt) =>
57
+ mt.type.kind === "Enum" &&
58
+ mt.type.name === this._t.enum.name &&
59
+ compareArrays(
60
+ mt.namespaces,
61
+ namespaceListFromNamespace(this._t.enum.namespace) ?? [],
62
+ ),
63
+ );
64
+ if (foundParent) {
65
+ out.resolved.append(`${foundParent.type.name}.${this._t.name}`);
66
+ out.imports.push(foundParent.namespaces);
67
+ } else {
68
+ out.resolved.append(resolveEnumMemberValue(this._t, opts));
69
+ }
70
+ }
71
+
72
+ protected async typeguard(
73
+ opts: ResolverOptions<Resolver.Typeguard>,
74
+ out: ResolverResult<Resolver.Typeguard>,
75
+ ): Promise<void> {
76
+ out.resolved.append("true");
77
+ }
78
+ }
79
+
80
+ const resolveEnumMemberValue = (
81
+ member: EnumMember,
82
+ opts: ResolverOptions<any>,
83
+ ): string => {
84
+ return member.value === undefined
85
+ ? opts.options["string-nominal-enums"]
86
+ ? ` = '${member.name}'`
87
+ : ""
88
+ : " = " +
89
+ (typeof member.value === "string"
90
+ ? `'${member.value}'`
91
+ : member.value.toString());
92
+ };