@azure-tools/typespec-ts 0.29.0-alpha.20240626.1 → 0.30.0

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 (73) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/src/contextManager.d.ts +70 -0
  3. package/dist/src/contextManager.d.ts.map +1 -0
  4. package/dist/src/contextManager.js +59 -0
  5. package/dist/src/contextManager.js.map +1 -0
  6. package/dist/src/index.d.ts.map +1 -1
  7. package/dist/src/index.js +13 -2
  8. package/dist/src/index.js.map +1 -1
  9. package/dist/src/metaTree.d.ts +12 -0
  10. package/dist/src/metaTree.d.ts.map +1 -0
  11. package/dist/src/metaTree.js +2 -0
  12. package/dist/src/metaTree.js.map +1 -0
  13. package/dist/src/modular/buildClassicalClient.js +3 -1
  14. package/dist/src/modular/buildClassicalClient.js.map +1 -1
  15. package/dist/src/modular/buildCodeModel.d.ts.map +1 -1
  16. package/dist/src/modular/buildCodeModel.js +17 -7
  17. package/dist/src/modular/buildCodeModel.js.map +1 -1
  18. package/dist/src/modular/buildHelperSerializers.d.ts +3 -0
  19. package/dist/src/modular/buildHelperSerializers.d.ts.map +1 -0
  20. package/dist/src/modular/buildHelperSerializers.js +60 -0
  21. package/dist/src/modular/buildHelperSerializers.js.map +1 -0
  22. package/dist/src/modular/buildOperations.d.ts.map +1 -1
  23. package/dist/src/modular/buildOperations.js +5 -0
  24. package/dist/src/modular/buildOperations.js.map +1 -1
  25. package/dist/src/modular/buildSerializeUtils.d.ts.map +1 -1
  26. package/dist/src/modular/buildSerializeUtils.js +11 -4
  27. package/dist/src/modular/buildSerializeUtils.js.map +1 -1
  28. package/dist/src/modular/buildSubpathIndex.d.ts.map +1 -1
  29. package/dist/src/modular/buildSubpathIndex.js +6 -0
  30. package/dist/src/modular/buildSubpathIndex.js.map +1 -1
  31. package/dist/src/modular/emitModels.d.ts +5 -5
  32. package/dist/src/modular/emitModels.d.ts.map +1 -1
  33. package/dist/src/modular/emitModels.js +32 -4
  34. package/dist/src/modular/emitModels.js.map +1 -1
  35. package/dist/src/modular/helpers/operationHelpers.d.ts +10 -2
  36. package/dist/src/modular/helpers/operationHelpers.d.ts.map +1 -1
  37. package/dist/src/modular/helpers/operationHelpers.js +135 -56
  38. package/dist/src/modular/helpers/operationHelpers.js.map +1 -1
  39. package/dist/src/modular/helpers/typeHelpers.d.ts +1 -0
  40. package/dist/src/modular/helpers/typeHelpers.d.ts.map +1 -1
  41. package/dist/src/modular/helpers/typeHelpers.js +24 -19
  42. package/dist/src/modular/helpers/typeHelpers.js.map +1 -1
  43. package/dist/src/modular/modularCodeModel.d.ts +2 -2
  44. package/dist/src/modular/modularCodeModel.d.ts.map +1 -1
  45. package/dist/src/modular/serialization/buildSerializerFunction.d.ts +4 -0
  46. package/dist/src/modular/serialization/buildSerializerFunction.d.ts.map +1 -0
  47. package/dist/src/modular/serialization/buildSerializerFunction.js +160 -0
  48. package/dist/src/modular/serialization/buildSerializerFunction.js.map +1 -0
  49. package/dist/src/transform/transformSchemas.d.ts.map +1 -1
  50. package/dist/src/transform/transformSchemas.js +3 -0
  51. package/dist/src/transform/transformSchemas.js.map +1 -1
  52. package/dist/src/utils/importHelper.d.ts +10 -0
  53. package/dist/src/utils/importHelper.d.ts.map +1 -0
  54. package/dist/src/utils/importHelper.js +54 -0
  55. package/dist/src/utils/importHelper.js.map +1 -0
  56. package/dist/tsconfig.tsbuildinfo +1 -1
  57. package/package.json +4 -4
  58. package/src/contextManager.ts +99 -0
  59. package/src/index.ts +14 -2
  60. package/src/metaTree.ts +14 -0
  61. package/src/modular/buildClassicalClient.ts +5 -1
  62. package/src/modular/buildCodeModel.ts +22 -6
  63. package/src/modular/buildHelperSerializers.ts +71 -0
  64. package/src/modular/buildOperations.ts +7 -1
  65. package/src/modular/buildSerializeUtils.ts +19 -11
  66. package/src/modular/buildSubpathIndex.ts +10 -0
  67. package/src/modular/emitModels.ts +66 -16
  68. package/src/modular/helpers/operationHelpers.ts +198 -84
  69. package/src/modular/helpers/typeHelpers.ts +24 -17
  70. package/src/modular/modularCodeModel.ts +2 -2
  71. package/src/modular/serialization/buildSerializerFunction.ts +224 -0
  72. package/src/transform/transformSchemas.ts +3 -0
  73. package/src/utils/importHelper.ts +64 -0
@@ -0,0 +1,224 @@
1
+ import { toCamelCase, toPascalCase } from "../../utils/casingUtils.js";
2
+ import { Type as ModularType } from "../modularCodeModel.js";
3
+ import { getRequestModelMapping } from "../helpers/operationHelpers.js";
4
+ import { useContext } from "../../contextManager.js";
5
+
6
+ import {
7
+ Imports as RuntimeImports,
8
+ SchemaContext,
9
+ addImportToSpecifier
10
+ } from "@azure-tools/rlc-common";
11
+ import { UsageFlags } from "@typespec/compiler";
12
+ import {
13
+ SdkModelType,
14
+ SdkType
15
+ } from "@azure-tools/typespec-client-generator-core";
16
+
17
+ function getTcgcType(type: ModularType): SdkType {
18
+ if (type.tcgcType?.kind === "nullable") {
19
+ return type.tcgcType.type!;
20
+ }
21
+ return type.tcgcType!;
22
+ }
23
+ export function buildModelSerializer(
24
+ type: ModularType,
25
+ runtimeImports: RuntimeImports
26
+ ): string | undefined {
27
+ const rlcMetadata = useContext("rlcMetaTree");
28
+ const modelTcgcType = getTcgcType(type) as SdkModelType;
29
+ if (
30
+ modelTcgcType.usage !== undefined &&
31
+ (modelTcgcType.usage & UsageFlags.Input) !== UsageFlags.Input
32
+ ) {
33
+ return undefined;
34
+ }
35
+
36
+ if (!type.name) {
37
+ throw new Error(`NYI Serialization of anonymous types`);
38
+ }
39
+
40
+ let serializerName = `${toCamelCase(type.name)}Serializer`;
41
+
42
+ const restModel = rlcMetadata.get(type.__raw!);
43
+ const restModelName = restModel?.rlcType.name;
44
+
45
+ const output: string[] = [];
46
+
47
+ if (
48
+ !isDiscriminatedUnion(type) &&
49
+ type.type === "combined" &&
50
+ type.discriminator
51
+ ) {
52
+ return buildPolymorphicSerializer(type);
53
+ }
54
+
55
+ if (isDiscriminatedUnion(type)) {
56
+ const discriminatedTgcType = getTcgcType(type) as SdkModelType;
57
+ const cases: string[] = [];
58
+ const baseSerializerName = `${toCamelCase(
59
+ discriminatedTgcType.name
60
+ )}Serializer`;
61
+ for (const key in discriminatedTgcType.discriminatedSubtypes) {
62
+ const subType = discriminatedTgcType.discriminatedSubtypes[key]!;
63
+ const discriminatedValue = subType.discriminatorValue!;
64
+ const union = subType.discriminatedSubtypes ? "Union" : "";
65
+ const subTypeName = `${toPascalCase(subType.name)}${union}`;
66
+ const subtypeSerializerName = toCamelCase(`${subTypeName}Serializer`);
67
+
68
+ cases.push(`
69
+ case "${discriminatedValue}":
70
+ return ${subtypeSerializerName}(item as ${subTypeName});
71
+ `);
72
+ }
73
+ output.push(`
74
+ export function ${toCamelCase(type.name)}Serializer(item: ${toPascalCase(
75
+ type.name
76
+ )}) {
77
+ switch (item.${type.discriminator}) {
78
+ ${cases.join("\n")}
79
+ default:
80
+ return ${baseSerializerName}(item);
81
+ }
82
+ }
83
+ `);
84
+
85
+ serializerName = baseSerializerName;
86
+ }
87
+
88
+ let serializerReturnType = "";
89
+
90
+ // There are some situations where the type is not present in the REST API,
91
+ // this can happen when client.tsp overrides visibility to public on an orphan model
92
+ // In this case we just let TypeScript infer the return type.
93
+ let restModelNameAlias = "";
94
+ if (restModelName && restModel.rlcType.usage?.includes(SchemaContext.Input)) {
95
+ restModelNameAlias = `${restModelName}Rest`;
96
+ serializerReturnType = `: ${restModelNameAlias}`;
97
+ }
98
+
99
+ if (type.type === "model" || type.type === "dict") {
100
+ const nullabilityPrefix = "";
101
+ // getPropertySerializationPrefix({
102
+ // clientName: "item",
103
+ // type
104
+ // });
105
+
106
+ // This is only handling the compatibility mode, will need to update when we handle additionalProperties property.
107
+ const additionalPropertiesSpread = hasAdditionalProperties(type.tcgcType)
108
+ ? "...item,"
109
+ : "";
110
+
111
+ const { propertiesStr, directAssignment } = getRequestModelMapping(
112
+ type,
113
+ "item",
114
+ runtimeImports
115
+ );
116
+ const propertiesSerialization = propertiesStr.filter((p) => p.trim());
117
+
118
+ // don't emit a serializer if there is nothing to serialize
119
+ if (propertiesSerialization.length || additionalPropertiesSpread) {
120
+ const spreadSerialized = directAssignment ? "..." : "";
121
+ const fnBody = `{
122
+ ${additionalPropertiesSpread}
123
+ ${nullabilityPrefix} ${spreadSerialized} ${propertiesSerialization.join(
124
+ ",\n"
125
+ )}
126
+ }`;
127
+ output.push(`
128
+ export function ${serializerName}(item: ${toPascalCase(
129
+ type.name
130
+ )})${serializerReturnType} {
131
+ return ${fnBody}
132
+ }
133
+ `);
134
+
135
+ if (serializerReturnType) {
136
+ addImportToSpecifier(
137
+ "rlcIndex",
138
+ runtimeImports,
139
+ `${restModelName} as ${restModelNameAlias}`
140
+ );
141
+ }
142
+ } else {
143
+ output.push(`
144
+ export function ${serializerName}(item: ${toPascalCase(type.name)}) {
145
+ return item as any;
146
+ }
147
+ `);
148
+ }
149
+ } else if (type.type === "enum") {
150
+ output.push(`
151
+ export function ${serializerName}(item: ${toPascalCase(
152
+ type.name
153
+ )})${serializerReturnType} {
154
+ return item;
155
+ }
156
+ `);
157
+ }
158
+
159
+ return output.join("\n");
160
+ }
161
+
162
+ function isDiscriminatedUnion(
163
+ type: ModularType
164
+ ): type is ModularType & { tcgcType: SdkModelType } {
165
+ const { tcgcType } = type;
166
+
167
+ return Boolean(
168
+ tcgcType?.kind === "model" &&
169
+ tcgcType.discriminatorProperty &&
170
+ tcgcType.discriminatedSubtypes
171
+ );
172
+ }
173
+
174
+ function hasAdditionalProperties(type: SdkType | undefined) {
175
+ if (!type || !("additionalProperties" in type)) {
176
+ return false;
177
+ }
178
+
179
+ if (type.additionalProperties) {
180
+ return true;
181
+ }
182
+
183
+ if (type.baseModel) {
184
+ return hasAdditionalProperties(type.baseModel);
185
+ }
186
+
187
+ return false;
188
+ }
189
+
190
+ function buildPolymorphicSerializer(type: ModularType) {
191
+ if (!type.discriminator) {
192
+ return;
193
+ }
194
+ const output: string[] = [];
195
+
196
+ const subTypes = type.types ?? [];
197
+
198
+ const cases: string[] = [];
199
+ for (const subType of subTypes) {
200
+ const discriminatedValue = subType.discriminatorValue!;
201
+ const union = subType.types ? "Union" : "";
202
+ const subTypeName = `${toPascalCase(subType.name!)}${union}`;
203
+ const subtypeSerializerName = toCamelCase(`${subTypeName}Serializer`);
204
+
205
+ cases.push(`
206
+ case "${discriminatedValue}":
207
+ return ${subtypeSerializerName}(item as ${subTypeName});
208
+ `);
209
+ }
210
+
211
+ output.push(`
212
+ export function ${toCamelCase(type.name!)}Serializer(item: ${toPascalCase(
213
+ type.name!
214
+ )}) {
215
+ switch (item.${type.discriminator}) {
216
+ ${cases.join("\n")}
217
+ default:
218
+ return item;
219
+ }
220
+ }
221
+ `);
222
+
223
+ return output.join("\n");
224
+ }
@@ -19,6 +19,7 @@ import {
19
19
  isAzureCoreErrorType,
20
20
  trimUsage
21
21
  } from "../utils/modelUtils.js";
22
+ import { useContext } from "../contextManager.js";
22
23
 
23
24
  export function transformSchemas(client: SdkClient, dpgContext: SdkContext) {
24
25
  const program = dpgContext.program;
@@ -118,6 +119,7 @@ export function transformSchemas(client: SdkClient, dpgContext: SdkContext) {
118
119
  }
119
120
  transformHostParameters();
120
121
  usageMap.forEach((context, tspModel) => {
122
+ const metatree = useContext("rlcMetaTree");
121
123
  const model = getSchemaForType(dpgContext, tspModel, {
122
124
  usage: context,
123
125
  isRequestBody: requestBodySet.has(tspModel),
@@ -127,6 +129,7 @@ export function transformSchemas(client: SdkClient, dpgContext: SdkContext) {
127
129
  if (model) {
128
130
  model.usage = context;
129
131
  }
132
+ metatree.set(tspModel, { rlcType: model });
130
133
  if (model.name === "") {
131
134
  return;
132
135
  }
@@ -0,0 +1,64 @@
1
+ import { dirname, relative, resolve } from "path";
2
+ import { useContext } from "../contextManager.js";
3
+ import { SourceFile } from "ts-morph";
4
+
5
+ /**
6
+ * Adds a named import to a TypeScript source file if it does not already exist.
7
+ * Utilizes a global symbol map context to resolve module paths and ensure the import is correctly referenced.
8
+ *
9
+ * @param {string} symbol The name of the export to import.
10
+ * @param {SourceFile} currentFile The ts-morph SourceFile object where the import will be added.
11
+ */
12
+ export function addImportBySymbol(symbol: string, currentFile: SourceFile) {
13
+ // Retrieve the global map of symbols to their source files.
14
+ const symbolMap = useContext("symbolMap");
15
+
16
+ // Attempt to resolve the file path for the given symbol.
17
+ const modulePath = symbolMap.get(symbol)?.getFilePath();
18
+ if (!modulePath) {
19
+ return;
20
+ }
21
+
22
+ // Resolve absolute path and compute a relative path from the current file to the module.
23
+ const moduleAbsolutePath = resolve(modulePath);
24
+ const relativeImportPath = getRelativeImportPath(
25
+ currentFile.getFilePath(),
26
+ moduleAbsolutePath
27
+ )
28
+ .replace(/\\/g, "/")
29
+ .replace(/\.ts$/, ".js");
30
+
31
+ // Check if the import declaration already exists and if it includes the symbol.
32
+ const existing = currentFile.getImportDeclaration(
33
+ (i) => i.getModuleSpecifierValue() === relativeImportPath
34
+ );
35
+
36
+ if (existing) {
37
+ const sameImport = existing
38
+ .getNamedImports()
39
+ .some((i) => i.getName() === symbol);
40
+
41
+ if (!sameImport) {
42
+ existing.addNamedImport(symbol);
43
+ }
44
+ } else {
45
+ currentFile.addImportDeclaration({
46
+ moduleSpecifier: relativeImportPath,
47
+ namedImports: [symbol]
48
+ });
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Computes a relative import path suitable for TypeScript imports given two file paths.
54
+ * Ensures the returned path starts with './' if necessary to be recognized as a relative module.
55
+ *
56
+ * @param {string} from The file path to import from.
57
+ * @param {string} to The file path to import to.
58
+ * @returns {string} The relative path adjusted for import syntax.
59
+ */
60
+ function getRelativeImportPath(from: string, to: string) {
61
+ const relativePath = relative(dirname(from), to);
62
+ // Adjust the path format to TypeScript module syntax
63
+ return relativePath.startsWith(".") ? relativePath : "./" + relativePath;
64
+ }