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
package/src/emitter.ts CHANGED
@@ -1,114 +1,26 @@
1
- import {
2
- EmitContext,
3
- emitFile,
4
- navigateProgram,
5
- resolvePath,
6
- } from "@typespec/compiler";
7
- import { emitRoutedTypemap } from "./emit_mapped_types.js";
1
+ import { EmitContext } from "@typespec/compiler";
2
+ import { emitRoutedTypemap } from "./emit_routedTypemap.js";
8
3
  import { emitRoutes } from "./emit_routes.js";
9
- import emitTypes from "./emit_types.js";
10
- import autogenerateWarning from "./helper_autogenerateWarning.js";
4
+ import { emitTypes } from "./emit_types.js";
5
+ import { buildTypeMap } from "./helpers/buildTypeMap.js";
6
+ import { setContext } from "./helpers/diagnostics.js";
7
+ import { emitVisibilityHelperFile } from "./helpers/visibilityHelperFile.js";
11
8
  import { EmitterOptions } from "./lib.js";
9
+ import { parseOptions } from "./parseOptions.js";
12
10
 
13
- // helper to add lines to string with indentation
14
- declare global {
15
- interface String {
16
- addLine(str: string, tabs?: number, continued?: boolean): string;
17
- }
18
- }
19
- String.prototype.addLine = function (
20
- this: string,
21
- str: string,
22
- tabs?: number,
23
- continued?: boolean,
24
- ): string {
25
- return `${this}${" ".repeat(tabs ?? 0)}${str}${continued ? "" : "\n"}`;
26
- };
27
-
28
- export async function $onEmit(context: EmitContext) {
29
- if (!context.program.compilerOptions.noEmit) {
30
- const options: EmitterOptions = {
31
- "root-namespace": context.options["root-namespace"],
32
- "out-dir": context.options["out-dir"] ?? context.emitterOutputDir,
33
- "enable-types": context.options["enable-types"] ?? true,
34
- "enable-typeguards":
35
- (context.options["enable-types"] ?? true) &&
36
- (context.options["enable-typeguards"] ?? false),
37
- "enable-routes": context.options["enable-routes"] ?? false,
38
- "enable-routed-typemap":
39
- context.options["enable-routed-typemap"] ?? false,
40
- "string-nominal-enums":
41
- (context.options["enable-routed-typemap"] ?? false) &&
42
- (context.options["string-nominal-enums"] ?? false),
43
- "serializable-date-types":
44
- context.options["serializable-date-types"] ?? false,
45
- };
46
-
47
- console.log(`Writing routes to ${options["out-dir"]}`);
48
-
49
- let targetNamespaceFound = false;
50
- let routesObject = "";
51
- let routedTypemap = "";
52
- let typeFiles: ReturnType<typeof emitTypes> = {
53
- files: {},
54
- typeguardedNames: [],
55
- };
56
- navigateProgram(context.program, {
57
- namespace(n) {
58
- if (
59
- !targetNamespaceFound &&
60
- n.name === context.options["root-namespace"]
61
- ) {
62
- targetNamespaceFound = true;
63
- if (options["enable-types"] || options["enable-typeguards"])
64
- typeFiles = emitTypes(context, n, options);
65
- if (options["enable-routes"]) {
66
- routesObject = emitRoutes(context, n);
67
- }
68
- if (options["enable-routed-typemap"]) {
69
- routedTypemap = emitRoutedTypemap(context, n);
70
- }
71
- }
72
- },
73
- });
74
-
75
- if (!targetNamespaceFound)
76
- throw new Error("Targeted root namespace not found.");
11
+ export async function $onEmit(context: EmitContext<EmitterOptions>) {
12
+ if (context.program.compilerOptions.noEmit) return;
13
+ setContext(context);
14
+ parseOptions(context);
77
15
 
78
- // routes object
79
- if (options["enable-routes"]) {
80
- if (!routesObject) throw new Error("Routes object empty.");
81
- await emitFile(context.program, {
82
- path: resolvePath(
83
- options["out-dir"],
84
- `routes_${options["root-namespace"]}.ts`,
85
- ),
86
- content: `/* eslint-disable */\n\n${autogenerateWarning}${routesObject}`,
87
- });
88
- }
16
+ const typeMap = buildTypeMap(context.program, context.options);
89
17
 
90
- // routed typemap
91
- if (options["enable-routed-typemap"]) {
92
- if (!routedTypemap) throw new Error("Routed typemap empty.");
93
- await emitFile(context.program, {
94
- path: resolvePath(
95
- options["out-dir"],
96
- `routedTypemap_${options["root-namespace"]}.ts`,
97
- ),
98
- content: `/* eslint-disable */\n\n${autogenerateWarning}${routedTypemap}`,
99
- });
100
- }
18
+ if (context.options["enable-types"])
19
+ await emitTypes(context.program, context.options, typeMap);
20
+ if (context.options["enable-routed-typemap"])
21
+ await emitRoutedTypemap(context.program, context.options, typeMap);
22
+ if (context.options["enable-routes"])
23
+ await emitRoutes(context.program, context.options);
101
24
 
102
- // type files
103
- if (options["enable-types"] || options["enable-typeguards"]) {
104
- const typeFileArr = Object.entries(typeFiles.files);
105
- for (let i = 0; i < typeFileArr.length; i++) {
106
- if (typeFileArr[i][1])
107
- await emitFile(context.program, {
108
- path: resolvePath(options["out-dir"], `${typeFileArr[i][0]}.ts`),
109
- content: `/* eslint-disable */\n\n${autogenerateWarning}${typeFileArr[i][1]}`,
110
- });
111
- }
112
- }
113
- }
25
+ await emitVisibilityHelperFile(context.program, context.options);
114
26
  }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * This class provides a string type which can only be appended to but not overwritten.
3
+ */
4
+ export class AppendableString {
5
+ constructor(init?: string) {
6
+ this._content = init ?? "";
7
+ }
8
+
9
+ private _content: string;
10
+
11
+ get value(): string {
12
+ return this._content;
13
+ }
14
+ append(s?: string | AppendableString) {
15
+ this._content += s ?? "";
16
+ }
17
+
18
+ /** Changes in place, returning the new value */
19
+ addLine(
20
+ str: string | AppendableString,
21
+ tabs?: number,
22
+ continued: "continued" | "line-end" = "line-end",
23
+ ): this {
24
+ this._content += `${" ".repeat(tabs ?? 0)}${str}${continued === "continued" ? "" : "\n"}`;
25
+ return this;
26
+ }
27
+
28
+ [Symbol.toPrimitive](hint: string) {
29
+ switch (hint) {
30
+ case "default":
31
+ case "string":
32
+ return this.value;
33
+
34
+ case "boolean":
35
+ return !!this.value;
36
+
37
+ default:
38
+ throw new TypeError(`Appendable string cannot be coerced to ${hint}`);
39
+ }
40
+ }
41
+
42
+ /** Removes the last x characters */
43
+ dropLast(n: number = 1): this {
44
+ this._content = this._content.substring(0, this._content.length - n);
45
+ return this;
46
+ }
47
+
48
+ clear(): this {
49
+ this._content = "";
50
+ return this;
51
+ }
52
+ }
@@ -0,0 +1,21 @@
1
+ export const compareArrays = (a: any[], b: any[]): boolean => {
2
+ if (a.length !== b.length) return false;
3
+ for (let i = 0; i < a.length; i++) {
4
+ if (a[i] !== b[i]) return false;
5
+ }
6
+ return true;
7
+ };
8
+
9
+ /** Unique-ifies a 2D array of strings (imported namespaces list). */
10
+ export const unique2D = (rows: string[][]): string[][] => {
11
+ const seen = new Set<string>();
12
+ const out: string[][] = [];
13
+ for (const r of rows) {
14
+ const key = r.join("\u0000"); // safe separator
15
+ if (!seen.has(key)) {
16
+ seen.add(key);
17
+ out.push(r);
18
+ }
19
+ }
20
+ return out;
21
+ };
@@ -6,7 +6,6 @@ const autogenerateWarning = `
6
6
  * Instead, change the underlying spec.
7
7
  */
8
8
 
9
-
10
9
  `;
11
10
 
12
11
  export default autogenerateWarning;
@@ -0,0 +1,72 @@
1
+ import {
2
+ Enum,
3
+ Model,
4
+ Namespace,
5
+ navigateProgram,
6
+ navigateTypesInNamespace,
7
+ Program,
8
+ Scalar,
9
+ Union,
10
+ } from "@typespec/compiler";
11
+ import { EmitterOptions } from "../lib.js";
12
+
13
+ export type TTypeMap = {
14
+ type: Enum | Model | Scalar | Union;
15
+ namespaces: string[];
16
+ hasVisibility: boolean | undefined;
17
+ }[];
18
+
19
+ /**
20
+ * Traverses all namespaces in the program and produces
21
+ * a map of all types-to-be-emitted and their namespace hierarchy.
22
+ */
23
+ export const buildTypeMap = (
24
+ program: Program,
25
+ options: EmitterOptions,
26
+ ): TTypeMap => {
27
+ // save original targeted namespaces array because it's mutated here
28
+ const targetedNamespaces = structuredClone(options["root-namespaces"]);
29
+ const map: TTypeMap = [];
30
+
31
+ const pushType = (t: TTypeMap[number]["type"], hierarchy: string[]): void => {
32
+ if (!t.name) return;
33
+ map.push({
34
+ type: t,
35
+ namespaces: hierarchy,
36
+ hasVisibility: undefined,
37
+ });
38
+ };
39
+
40
+ const traverseNamespace = (n: Namespace, hierarchy: string[]) => {
41
+ navigateTypesInNamespace(
42
+ n,
43
+ {
44
+ enum: (t) => pushType(t, [...hierarchy, n.name]),
45
+ scalar: (t) => pushType(t, [...hierarchy, n.name]),
46
+ model: (t) => pushType(t, [...hierarchy, n.name]),
47
+ union: (t) => pushType(t, [...hierarchy, n.name]),
48
+ },
49
+ {
50
+ skipSubNamespaces: true,
51
+ },
52
+ );
53
+ n.namespaces.forEach((ns) => traverseNamespace(ns, [...hierarchy, n.name]));
54
+ };
55
+
56
+ navigateProgram(program, {
57
+ namespace(n) {
58
+ const nsIndex = options["root-namespaces"].findIndex(
59
+ (ns) => n.name === ns,
60
+ );
61
+ if (nsIndex === -1) return;
62
+ // for some reason, navigateProgram visits each namespace multiple times; this prevents that
63
+ delete options["root-namespaces"][nsIndex];
64
+
65
+ traverseNamespace(n, []);
66
+ },
67
+ });
68
+
69
+ // restore un-mutated version
70
+ options["root-namespaces"] = targetedNamespaces;
71
+ return map;
72
+ };
@@ -0,0 +1,19 @@
1
+ import { Diagnostic, EmitContext, NoTarget } from "@typespec/compiler";
2
+ import { EmitterOptions } from "../lib.js";
3
+
4
+ let context: EmitContext<EmitterOptions> | null = null;
5
+
6
+ export const setContext = (c: EmitContext<EmitterOptions>): void => {
7
+ context = c;
8
+ };
9
+
10
+ export const reportDiagnostic = (
11
+ diagnostic: Pick<Diagnostic, "code" | "message" | "severity">,
12
+ ): void => {
13
+ if (!context) throw new Error("Couldn't report diagnostic: context not set");
14
+ context.program.reportDiagnostic({
15
+ ...diagnostic,
16
+ code: `typespec-emitter-${diagnostic.code}`,
17
+ target: NoTarget,
18
+ });
19
+ };
@@ -0,0 +1,9 @@
1
+ import { TTypeMap } from "./buildTypeMap.js";
2
+ import { filenameFromNamespaces } from "./namespaces.js";
3
+
4
+ export const getImports = (
5
+ imports: TTypeMap[number]["namespaces"][],
6
+ ): string[] =>
7
+ imports.map(
8
+ (i) => `import * as ${i.join("_")} from './${filenameFromNamespaces(i)}';`,
9
+ );
@@ -0,0 +1,18 @@
1
+ import { Namespace } from "@typespec/compiler";
2
+
3
+ /** Traverses *up* a namespace, generating a hierarchy array from itself and its parents. */
4
+ export const namespaceListFromNamespace = (
5
+ n: Namespace | undefined,
6
+ ): string[] | null => {
7
+ if (!n) return null;
8
+ const ret: string[] = [];
9
+ let cur: Namespace | undefined = n;
10
+ while (cur && cur.name) {
11
+ ret.unshift(cur.name);
12
+ cur = cur.namespace;
13
+ }
14
+ return ret;
15
+ };
16
+
17
+ export const filenameFromNamespaces = (ns: string[]): string =>
18
+ `${ns.join(".")}.ts`;
@@ -0,0 +1,63 @@
1
+ import { emitFile, Program, resolvePath } from "@typespec/compiler";
2
+ import { EmitterOptions } from "../lib.js";
3
+
4
+ const visibilityHelperFile = `
5
+ /* eslint-disable */
6
+
7
+ /*
8
+ This file is auto-generated by typespec-typescript-emitter and
9
+ contains helpers for accessing lifecycle-visibility-modified
10
+ variants of the emitted types.
11
+ */
12
+
13
+ export enum Lifecycle {
14
+ Read, Create, Update, Delete, Query,
15
+ None, All
16
+ }
17
+
18
+ export type VisibilityMap<T> = {
19
+ [K in keyof T]?: {
20
+ vis?: readonly Lifecycle[]
21
+ nested?: VisibilityMap<T[K]>
22
+ }
23
+ }
24
+
25
+ export type FilterLifecycle<
26
+ T,
27
+ M extends VisibilityMap<T>,
28
+ Current extends Lifecycle
29
+ > = {
30
+ [K in keyof T as (
31
+ Current extends Lifecycle.All
32
+ ? K
33
+ : Current extends Lifecycle.None
34
+ ? never
35
+ : K extends keyof M
36
+ ? M[K] extends { vis?: readonly (infer L)[]; }
37
+ ? (undefined extends M[K]['vis']
38
+ ? K
39
+ : (Current extends L ? K : never)
40
+ )
41
+ : K
42
+ : K
43
+ )]: K extends keyof M
44
+ ? M[K] extends {nested?: infer N}
45
+ ? N extends VisibilityMap<T[K]>
46
+ ? FilterLifecycle<T[K], N, Current>
47
+ : T[K]
48
+ : T[K]
49
+ : T[K]
50
+ }
51
+ `;
52
+
53
+ export const visibilityHelperFileName = "lifecycleVisibility.ts";
54
+
55
+ export const emitVisibilityHelperFile = async (
56
+ program: Program,
57
+ options: EmitterOptions,
58
+ ): Promise<void> => {
59
+ await emitFile(program, {
60
+ path: resolvePath(options["out-dir"], visibilityHelperFileName),
61
+ content: visibilityHelperFile,
62
+ });
63
+ };
package/src/lib.ts CHANGED
@@ -1,7 +1,11 @@
1
- import { createTypeSpecLibrary, JSONSchemaType } from "@typespec/compiler";
1
+ import {
2
+ createTypeSpecLibrary,
3
+ EmitContext,
4
+ JSONSchemaType,
5
+ } from "@typespec/compiler";
2
6
 
3
7
  export interface EmitterOptions {
4
- "root-namespace": string;
8
+ "root-namespaces": string[];
5
9
  "out-dir": string;
6
10
  "enable-types": boolean;
7
11
  "enable-typeguards": boolean;
@@ -15,7 +19,7 @@ const EmitterOptionsSchema: JSONSchemaType<EmitterOptions> = {
15
19
  type: "object",
16
20
  additionalProperties: false,
17
21
  properties: {
18
- "root-namespace": { type: "string" },
22
+ "root-namespaces": { type: "array", items: { type: "string" } },
19
23
  "out-dir": { type: "string", format: "absolute-path" },
20
24
  "enable-types": { type: "boolean" },
21
25
  "enable-typeguards": { type: "boolean" },
@@ -24,9 +28,26 @@ const EmitterOptionsSchema: JSONSchemaType<EmitterOptions> = {
24
28
  "string-nominal-enums": { type: "boolean" },
25
29
  "serializable-date-types": { type: "boolean" },
26
30
  },
27
- required: ["root-namespace"],
31
+ required: ["root-namespaces"],
28
32
  };
29
33
 
34
+ /** Maps option to its default value and options that must be set to `true` for this one to work */
35
+ export const optionDependencies = (
36
+ context: EmitContext,
37
+ ): {
38
+ [K in keyof EmitterOptions]: [EmitterOptions[K], (keyof EmitterOptions)[]];
39
+ } => ({
40
+ ["root-namespaces"]: [[], []],
41
+ ["out-dir"]: [context.emitterOutputDir, []],
42
+ ["enable-types"]: [false, []],
43
+ ["enable-routes"]: [false, []],
44
+ ["string-nominal-enums"]: [false, []],
45
+ ["serializable-date-types"]: [false, []],
46
+
47
+ ["enable-typeguards"]: [false, ["enable-types"]],
48
+ ["enable-routed-typemap"]: [false, ["enable-types"]],
49
+ });
50
+
30
51
  export const $lib = createTypeSpecLibrary({
31
52
  name: "typespec-typescript-emitter",
32
53
  diagnostics: {},
@@ -0,0 +1,26 @@
1
+ import { EmitContext } from "@typespec/compiler";
2
+ import { reportDiagnostic } from "./helpers/diagnostics.js";
3
+ import { EmitterOptions, optionDependencies } from "./lib.js";
4
+
5
+ /**
6
+ * Parses config-file options including inter-option dependencies, setting defaults,
7
+ * emitting warnings/errors as required,
8
+ * replacing options in context with parsed versions.
9
+ */
10
+ export const parseOptions = (context: EmitContext<EmitterOptions>): void => {
11
+ Object.entries(optionDependencies).forEach(([k, val]) => {
12
+ const key: keyof EmitterOptions = k as any;
13
+ const [def, deps]: [unknown, (keyof EmitterOptions)[]] = val;
14
+ if (deps.length === 0) {
15
+ (context.options[key] as unknown) = context.options[key] ?? def;
16
+ } else {
17
+ if (context.options[key] && !deps.every((d) => context.options[d])) {
18
+ reportDiagnostic({
19
+ code: `opts-dependency-${key}`,
20
+ severity: "error",
21
+ message: `Option ${key} requires ${deps.join(", ")}`,
22
+ });
23
+ }
24
+ }
25
+ });
26
+ };