@typespec/http-server-csharp 0.58.0-alpha.9 → 0.58.0-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -6
- package/dist/src/cli/cli.js +92 -43
- package/dist/src/cli/cli.js.map +1 -1
- package/dist/src/lib/attributes.d.ts.map +1 -1
- package/dist/src/lib/attributes.js +65 -31
- package/dist/src/lib/attributes.js.map +1 -1
- package/dist/src/lib/boilerplate.d.ts +1 -1
- package/dist/src/lib/boilerplate.d.ts.map +1 -1
- package/dist/src/lib/boilerplate.js +153 -31
- package/dist/src/lib/boilerplate.js.map +1 -1
- package/dist/src/lib/doc.d.ts +5 -0
- package/dist/src/lib/doc.d.ts.map +1 -0
- package/dist/src/lib/doc.js +237 -0
- package/dist/src/lib/doc.js.map +1 -0
- package/dist/src/lib/interfaces.d.ts +48 -4
- package/dist/src/lib/interfaces.d.ts.map +1 -1
- package/dist/src/lib/interfaces.js +89 -28
- package/dist/src/lib/interfaces.js.map +1 -1
- package/dist/src/lib/lib.d.ts +32 -3
- package/dist/src/lib/lib.d.ts.map +1 -1
- package/dist/src/lib/lib.js +57 -2
- package/dist/src/lib/lib.js.map +1 -1
- package/dist/src/lib/project.d.ts +5 -0
- package/dist/src/lib/project.d.ts.map +1 -0
- package/dist/src/lib/project.js +101 -0
- package/dist/src/lib/project.js.map +1 -0
- package/dist/src/lib/scaffolding.d.ts +7 -5
- package/dist/src/lib/scaffolding.d.ts.map +1 -1
- package/dist/src/lib/scaffolding.js +113 -40
- package/dist/src/lib/scaffolding.js.map +1 -1
- package/dist/src/lib/service.d.ts.map +1 -1
- package/dist/src/lib/service.js +496 -587
- package/dist/src/lib/service.js.map +1 -1
- package/dist/src/lib/type-helpers.d.ts +5 -1
- package/dist/src/lib/type-helpers.d.ts.map +1 -1
- package/dist/src/lib/type-helpers.js +32 -3
- package/dist/src/lib/type-helpers.js.map +1 -1
- package/dist/src/lib/utils.d.ts +79 -7
- package/dist/src/lib/utils.d.ts.map +1 -1
- package/dist/src/lib/utils.js +971 -36
- package/dist/src/lib/utils.js.map +1 -1
- package/package.json +39 -26
- package/dist/src/lib/testing/index.d.ts +0 -3
- package/dist/src/lib/testing/index.d.ts.map +0 -1
- package/dist/src/lib/testing/index.js +0 -7
- package/dist/src/lib/testing/index.js.map +0 -1
package/dist/src/lib/service.js
CHANGED
|
@@ -1,52 +1,138 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { CodeTypeEmitter, StringBuilder, code, createAssetEmitter, } from "@typespec/asset-emitter";
|
|
2
|
+
import { getDoc, getNamespaceFullName, getService, isErrorModel, isNeverType, isNullType, isNumericType, isTemplateDeclaration, isVoidType, resolvePath, serializeValueAsJson, } from "@typespec/compiler";
|
|
3
3
|
import { createRekeyableMap } from "@typespec/compiler/utils";
|
|
4
|
-
import {
|
|
4
|
+
import { getHeaderFieldName, getHttpOperation, getHttpPart, isHeader, isStatusCode, } from "@typespec/http";
|
|
5
|
+
import { getUniqueItems } from "@typespec/json-schema";
|
|
5
6
|
import { getResourceOperation } from "@typespec/rest";
|
|
6
7
|
import { execFile } from "child_process";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import { getEncodedNameAttribute } from "./attributes.js";
|
|
7
10
|
import { GeneratedFileHeader, GeneratedFileHeaderWithNullable, getSerializationSourceFiles, } from "./boilerplate.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
11
|
+
import { getProjectDocs } from "./doc.js";
|
|
12
|
+
import { CSharpSourceType, CSharpType, CollectionType, NameCasingType, checkOrAddNamespaceToScope, } from "./interfaces.js";
|
|
13
|
+
import { CSharpServiceOptions, reportDiagnostic } from "./lib.js";
|
|
14
|
+
import { getProjectHelpers } from "./project.js";
|
|
10
15
|
import { getBusinessLogicImplementations, getScaffoldingHelpers, } from "./scaffolding.js";
|
|
11
|
-
import { getRecordType, isKnownReferenceType } from "./type-helpers.js";
|
|
12
|
-
import { HttpMetadata, UnknownType, coalesceTypes, ensureCSharpIdentifier, ensureCleanDirectory, formatComment, getCSharpIdentifier, getCSharpStatusCode, getCSharpType, getCSharpTypeForIntrinsic, getCSharpTypeForScalar, getModelAttributes, getModelInstantiationName, getOperationVerbDecorator, isValueType, } from "./utils.js";
|
|
16
|
+
import { getEnumType, getRecordType, isKnownReferenceType } from "./type-helpers.js";
|
|
17
|
+
import { CSharpOperationHelpers, HttpMetadata, ModelInfo, UnknownType, coalesceTypes, coalesceUnionTypes, ensureCSharpIdentifier, ensureCleanDirectory, formatComment, getBusinessLogicCallParameters, getBusinessLogicDeclParameters, getCSharpIdentifier, getCSharpStatusCode, getCSharpType, getCSharpTypeForIntrinsic, getCSharpTypeForScalar, getControllerReturnStatement, getFreePort, getHttpDeclParameters, getImports, getModelAttributes, getModelDeclarationName, getModelInstantiationName, getOpenApiConfig, getOperationVerbDecorator, getStatusCode, isEmptyResponseModel, isRecord, isStringEnumType, isValueType, resolveReferenceFromScopes, } from "./utils.js";
|
|
13
18
|
export async function $onEmit(context) {
|
|
14
19
|
let _unionCounter = 0;
|
|
15
20
|
const controllers = new Map();
|
|
16
21
|
const NoResourceContext = "RPCOperations";
|
|
17
|
-
const doNotEmit = context.program.compilerOptions.
|
|
22
|
+
const doNotEmit = context.program.compilerOptions.dryRun || false;
|
|
23
|
+
CSharpServiceOptions.getInstance().initialize(context.options);
|
|
24
|
+
function getFileWriter(program) {
|
|
25
|
+
return async (path) => !!(await program.host.stat(resolvePath(path)).catch((_) => false));
|
|
26
|
+
}
|
|
18
27
|
class CSharpCodeEmitter extends CodeTypeEmitter {
|
|
19
28
|
#metadateMap = new Map();
|
|
20
29
|
#generatedFileHeaderWithNullable = GeneratedFileHeaderWithNullable;
|
|
21
30
|
#generatedFileHeader = GeneratedFileHeader;
|
|
22
31
|
#sourceTypeKey = "sourceType";
|
|
23
|
-
#
|
|
24
|
-
#
|
|
32
|
+
#nsKey = "ResolvedNamespace";
|
|
33
|
+
#namespaces = new Map();
|
|
25
34
|
#emitterOutputType = context.options["output-type"];
|
|
26
35
|
#emitMocks = context.options["emit-mocks"];
|
|
27
36
|
#useSwagger = context.options["use-swaggerui"] || false;
|
|
28
37
|
#openapiPath = context.options["openapi-path"] || "openapi/openapi.yaml";
|
|
29
38
|
#mockRegistrations = new Map();
|
|
30
|
-
#
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
#opHelpers = new CSharpOperationHelpers(this.emitter);
|
|
40
|
+
#fileExists = getFileWriter(this.emitter.getProgram());
|
|
41
|
+
#getOrAddNamespaceForType(type) {
|
|
42
|
+
const program = this.emitter.getProgram();
|
|
43
|
+
switch (type.kind) {
|
|
44
|
+
case "Boolean":
|
|
45
|
+
case "String":
|
|
46
|
+
case "StringTemplate":
|
|
47
|
+
case "Number":
|
|
48
|
+
case "Scalar":
|
|
49
|
+
case "Intrinsic":
|
|
50
|
+
case "FunctionParameter":
|
|
51
|
+
case "ScalarConstructor":
|
|
52
|
+
case "StringTemplateSpan":
|
|
53
|
+
case "TemplateParameter":
|
|
54
|
+
case "Tuple":
|
|
55
|
+
case "FunctionType":
|
|
56
|
+
return undefined;
|
|
57
|
+
case "EnumMember":
|
|
58
|
+
return this.#getOrAddNamespaceForType(type.enum);
|
|
59
|
+
case "ModelProperty":
|
|
60
|
+
case "UnionVariant":
|
|
61
|
+
return this.#getOrAddNamespaceForType(type.type);
|
|
62
|
+
case "Model":
|
|
63
|
+
if (isRecord(type) && type.indexer)
|
|
64
|
+
return this.#getOrAddNamespaceForType(type.indexer.value);
|
|
65
|
+
if (type.indexer !== undefined && isNumericType(program, type.indexer?.key))
|
|
66
|
+
return this.#getOrAddNamespaceForType(type.indexer.value);
|
|
67
|
+
return this.#getOrAddNamespace(type.namespace);
|
|
68
|
+
case "Union":
|
|
69
|
+
if (isStringEnumType(program, type))
|
|
70
|
+
return this.#getOrAddNamespace(type.namespace);
|
|
71
|
+
const unionType = this.#coalesceTsUnion(type);
|
|
72
|
+
if (unionType === undefined)
|
|
73
|
+
return undefined;
|
|
74
|
+
return this.#getOrAddNamespaceForType(unionType);
|
|
75
|
+
default:
|
|
76
|
+
return this.#getOrAddNamespace(type.namespace);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
#coalesceTsUnion(union) {
|
|
80
|
+
let result = undefined;
|
|
81
|
+
for (const [_, variant] of union.variants) {
|
|
82
|
+
if (!isNullType(variant.type)) {
|
|
83
|
+
if (result !== undefined && result !== variant.type)
|
|
84
|
+
return undefined;
|
|
85
|
+
result = variant.type;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
#getOrAddNamespace(typespecNamespace) {
|
|
91
|
+
if (!typespecNamespace?.name)
|
|
92
|
+
return this.#getDefaultNamespace();
|
|
93
|
+
let resolvedNamespace = this.#namespaces.get(typespecNamespace);
|
|
94
|
+
if (resolvedNamespace !== undefined)
|
|
95
|
+
return resolvedNamespace;
|
|
96
|
+
const nsName = getNamespaceFullName(typespecNamespace);
|
|
97
|
+
resolvedNamespace = ensureCSharpIdentifier(this.emitter.getProgram(), typespecNamespace, nsName, NameCasingType.Namespace);
|
|
98
|
+
this.#namespaces.set(typespecNamespace, resolvedNamespace);
|
|
99
|
+
return resolvedNamespace;
|
|
100
|
+
}
|
|
101
|
+
#getDefaultNamespace() {
|
|
102
|
+
return "TypeSpec.Service";
|
|
103
|
+
}
|
|
38
104
|
arrayDeclaration(array, name, elementType) {
|
|
39
|
-
return this.
|
|
105
|
+
return this.collectionDeclaration(elementType, array);
|
|
40
106
|
}
|
|
41
107
|
arrayLiteral(array, elementType) {
|
|
42
|
-
|
|
108
|
+
this.emitter.emitType(elementType, this.emitter.getContext());
|
|
109
|
+
const csType = getCSharpType(this.emitter.getProgram(), array, this.#getOrAddNamespaceForType(elementType));
|
|
110
|
+
if (csType?.type) {
|
|
111
|
+
return code `${csType.type.getTypeReference(this.emitter.getContext()?.scope)}`;
|
|
112
|
+
}
|
|
113
|
+
return this.collectionDeclaration(elementType, array);
|
|
114
|
+
}
|
|
115
|
+
collectionDeclaration(elementType, array) {
|
|
116
|
+
const isUniqueItems = getUniqueItems(this.emitter.getProgram(), array);
|
|
117
|
+
const collectionType = isUniqueItems
|
|
118
|
+
? CollectionType.ISet
|
|
119
|
+
: CSharpServiceOptions.getInstance().collectionType;
|
|
120
|
+
switch (collectionType) {
|
|
121
|
+
case CollectionType.ISet:
|
|
122
|
+
return code `ISet<${this.emitter.emitTypeReference(elementType, this.emitter.getContext())}>`;
|
|
123
|
+
case CollectionType.IEnumerable:
|
|
124
|
+
return code `IEnumerable<${this.emitter.emitTypeReference(elementType, this.emitter.getContext())}>`;
|
|
125
|
+
case CollectionType.Array:
|
|
126
|
+
default:
|
|
127
|
+
return code `${this.emitter.emitTypeReference(elementType, this.emitter.getContext())}[]`;
|
|
128
|
+
}
|
|
43
129
|
}
|
|
44
130
|
booleanLiteral(boolean) {
|
|
45
|
-
return
|
|
131
|
+
return code `${boolean.value === true ? "true" : "false"}`;
|
|
46
132
|
}
|
|
47
133
|
unionLiteral(union) {
|
|
48
|
-
const csType =
|
|
49
|
-
return this.emitter.result.rawCode(csType
|
|
134
|
+
const csType = coalesceUnionTypes(this.emitter.getProgram(), union);
|
|
135
|
+
return this.emitter.result.rawCode(csType ? csType.getTypeReference(this.emitter.getContext()?.scope) : "object");
|
|
50
136
|
}
|
|
51
137
|
declarationName(declarationType) {
|
|
52
138
|
switch (declarationType.kind) {
|
|
@@ -60,7 +146,7 @@ export async function $onEmit(context) {
|
|
|
60
146
|
return getCSharpIdentifier(declarationType.name, NameCasingType.Class);
|
|
61
147
|
case "Model":
|
|
62
148
|
if (!declarationType.name)
|
|
63
|
-
return
|
|
149
|
+
return getModelDeclarationName(this.emitter.getProgram(), declarationType, `${_unionCounter++}`);
|
|
64
150
|
return getCSharpIdentifier(declarationType.name, NameCasingType.Class);
|
|
65
151
|
case "Operation":
|
|
66
152
|
return getCSharpIdentifier(declarationType.name, NameCasingType.Class);
|
|
@@ -74,16 +160,15 @@ export async function $onEmit(context) {
|
|
|
74
160
|
}
|
|
75
161
|
}
|
|
76
162
|
enumDeclaration(en, name) {
|
|
163
|
+
if (getEnumType(en) === "double")
|
|
164
|
+
return "";
|
|
77
165
|
const program = this.emitter.getProgram();
|
|
78
166
|
const enumName = ensureCSharpIdentifier(program, en, name);
|
|
79
167
|
const namespace = this.emitter.getContext().namespace;
|
|
80
168
|
const doc = getDoc(this.emitter.getProgram(), en);
|
|
81
169
|
const attributes = getModelAttributes(program, en, enumName);
|
|
82
170
|
this.#metadateMap.set(en, new CSharpType({ name: enumName, namespace: namespace }));
|
|
83
|
-
return this.emitter.result.declaration(enumName, code
|
|
84
|
-
|
|
85
|
-
${this.#emitUsings()}
|
|
86
|
-
|
|
171
|
+
return this.emitter.result.declaration(enumName, code `
|
|
87
172
|
namespace ${namespace}
|
|
88
173
|
{
|
|
89
174
|
|
|
@@ -91,40 +176,48 @@ export async function $onEmit(context) {
|
|
|
91
176
|
${attributes.map((attribute) => attribute.getApplicationString(this.emitter.getContext().scope)).join("\n")}
|
|
92
177
|
public enum ${enumName}
|
|
93
178
|
{
|
|
94
|
-
${this.emitter.emitEnumMembers(en)}
|
|
179
|
+
${this.emitter.emitEnumMembers(en)}
|
|
95
180
|
}
|
|
96
181
|
} `);
|
|
97
182
|
}
|
|
98
183
|
enumDeclarationContext(en) {
|
|
184
|
+
if (getEnumType(en) === "double")
|
|
185
|
+
return this.emitter.getContext();
|
|
99
186
|
const enumName = ensureCSharpIdentifier(this.emitter.getProgram(), en, en.name);
|
|
100
|
-
const enumFile = this.emitter.createSourceFile(`models/${enumName}.cs`);
|
|
187
|
+
const enumFile = this.emitter.createSourceFile(`generated/models/${enumName}.cs`);
|
|
101
188
|
enumFile.meta[this.#sourceTypeKey] = CSharpSourceType.Model;
|
|
102
|
-
const enumNamespace =
|
|
103
|
-
return
|
|
104
|
-
namespace: enumNamespace,
|
|
105
|
-
file: enumFile,
|
|
106
|
-
scope: enumFile.globalScope,
|
|
107
|
-
};
|
|
189
|
+
const enumNamespace = this.#getOrAddNamespace(en.namespace);
|
|
190
|
+
return this.#createEnumContext(enumNamespace, enumFile, enumName);
|
|
108
191
|
}
|
|
109
192
|
enumMembers(en) {
|
|
193
|
+
const enumType = getEnumType(en);
|
|
110
194
|
const result = new StringBuilder();
|
|
111
195
|
let i = 0;
|
|
112
196
|
for (const [name, member] of en.members) {
|
|
113
197
|
i++;
|
|
114
198
|
const memberName = ensureCSharpIdentifier(this.emitter.getProgram(), member, name);
|
|
115
199
|
this.#metadateMap.set(member, { name: memberName });
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
200
|
+
if (enumType === "string") {
|
|
201
|
+
result.push(code `
|
|
202
|
+
[JsonStringEnumMemberName("${member.value ? member.value : name}")]
|
|
203
|
+
${ensureCSharpIdentifier(this.emitter.getProgram(), member, name)}`);
|
|
204
|
+
if (i < en.members.size)
|
|
205
|
+
result.pushLiteralSegment(",\n");
|
|
206
|
+
}
|
|
207
|
+
else if (member.value !== undefined) {
|
|
208
|
+
result.push(code `
|
|
209
|
+
${ensureCSharpIdentifier(this.emitter.getProgram(), member, name)} = ${member.value.toString()}`);
|
|
210
|
+
if (i < en.members.size)
|
|
211
|
+
result.pushLiteralSegment(",\n");
|
|
212
|
+
}
|
|
119
213
|
}
|
|
120
214
|
return this.emitter.result.rawCode(result.reduce());
|
|
121
215
|
}
|
|
122
216
|
intrinsic(intrinsic, name) {
|
|
123
217
|
switch (intrinsic.name) {
|
|
124
218
|
case "unknown":
|
|
125
|
-
return this.emitter.result.rawCode(code `System.Text.Json.Nodes.JsonNode`);
|
|
126
219
|
case "null":
|
|
127
|
-
return this.emitter.result.rawCode(code
|
|
220
|
+
return this.emitter.result.rawCode(code `${UnknownType.getTypeReference(this.emitter.getContext().scope)}`);
|
|
128
221
|
case "ErrorType":
|
|
129
222
|
case "never":
|
|
130
223
|
reportDiagnostic(this.emitter.getProgram(), {
|
|
@@ -135,7 +228,7 @@ export async function $onEmit(context) {
|
|
|
135
228
|
return "";
|
|
136
229
|
case "void":
|
|
137
230
|
const type = getCSharpTypeForIntrinsic(this.emitter.getProgram(), intrinsic);
|
|
138
|
-
return this.emitter.result.rawCode(`${type?.type.getTypeReference()}`);
|
|
231
|
+
return this.emitter.result.rawCode(`${type?.type.getTypeReference(this.emitter.getContext().scope)}`);
|
|
139
232
|
}
|
|
140
233
|
}
|
|
141
234
|
#emitUsings(file) {
|
|
@@ -153,39 +246,143 @@ export async function $onEmit(context) {
|
|
|
153
246
|
parts.forEach((p) => this.emitter.emitType(p));
|
|
154
247
|
return "";
|
|
155
248
|
}
|
|
156
|
-
const
|
|
249
|
+
const isErrorType = isErrorModel(this.emitter.getProgram(), model);
|
|
250
|
+
if (model.baseModel && model.baseModel.namespace !== model.namespace) {
|
|
251
|
+
const resolvedNs = this.#getOrAddNamespaceForType(model.baseModel);
|
|
252
|
+
if (resolvedNs)
|
|
253
|
+
checkOrAddNamespaceToScope(resolvedNs, this.emitter.getContext().scope);
|
|
254
|
+
}
|
|
255
|
+
const baseModelRef = model.baseModel
|
|
256
|
+
? code `: ${this.emitter.emitTypeReference(model.baseModel, this.emitter.getContext())}`
|
|
257
|
+
: "";
|
|
258
|
+
const baseClass = baseModelRef || (isErrorType ? ": HttpServiceException" : "");
|
|
157
259
|
const namespace = this.emitter.getContext().namespace;
|
|
260
|
+
const className = ensureCSharpIdentifier(this.emitter.getProgram(), model, name);
|
|
158
261
|
const doc = getDoc(this.emitter.getProgram(), model);
|
|
159
262
|
const attributes = getModelAttributes(this.emitter.getProgram(), model, className);
|
|
263
|
+
const exceptionConstructor = isErrorType
|
|
264
|
+
? this.getModelExceptionConstructor(this.emitter.getProgram(), model, name, className)
|
|
265
|
+
: "";
|
|
160
266
|
this.#metadateMap.set(model, new CSharpType({ name: className, namespace: namespace }));
|
|
161
|
-
const decl = this.emitter.result.declaration(className, code
|
|
162
|
-
|
|
163
|
-
${this.#emitUsings()}
|
|
164
|
-
|
|
267
|
+
const decl = this.emitter.result.declaration(className, code `
|
|
165
268
|
namespace ${namespace} {
|
|
166
269
|
|
|
167
|
-
${doc ? `${formatComment(doc)}\n` : ""}${`${attributes.map((attribute) => attribute.getApplicationString(this.emitter.getContext().scope)).join("\n")}${attributes?.length > 0 ? "\n" : ""}`}public partial class ${className} ${
|
|
168
|
-
${this.emitter.emitModelProperties(model)}
|
|
270
|
+
${doc ? `${formatComment(doc)}\n` : ""}${`${attributes.map((attribute) => attribute.getApplicationString(this.emitter.getContext().scope)).join("\n")}${attributes?.length > 0 ? "\n" : ""}`}public partial class ${className} ${baseClass} {
|
|
271
|
+
${exceptionConstructor ? `${exceptionConstructor}\n` : ""}${this.emitter.emitModelProperties(model)}
|
|
169
272
|
}
|
|
170
273
|
} `);
|
|
171
274
|
return decl;
|
|
172
275
|
}
|
|
276
|
+
getModelExceptionConstructor(program, model, modelName, className) {
|
|
277
|
+
if (!isErrorModel(program, model))
|
|
278
|
+
return undefined;
|
|
279
|
+
const constructor = this.getExceptionConstructorData(program, model, modelName);
|
|
280
|
+
const isParent = !!model.derivedModels?.length;
|
|
281
|
+
return `public ${className}(${constructor.properties}) : base(${constructor.statusCode?.value ?? `400`}${constructor.header ? `, \n\t\t headers: new(){${constructor.header}}` : ""}${constructor.value ? `, \n\t\t value: new{${constructor.value}}` : ""})
|
|
282
|
+
{ ${constructor.body ? `\n${constructor.body}` : ""}\n\t}${isParent ? `\npublic ${className}(int statusCode, object? value = null, Dictionary<string, string>? headers = default): base(statusCode, value, headers) {}\n` : ""}`;
|
|
283
|
+
}
|
|
284
|
+
isDuplicateExceptionName(name) {
|
|
285
|
+
const exceptionPropertyNames = [
|
|
286
|
+
"value",
|
|
287
|
+
"headers",
|
|
288
|
+
"stacktrace",
|
|
289
|
+
"source",
|
|
290
|
+
"message",
|
|
291
|
+
"innerexception",
|
|
292
|
+
"hresult",
|
|
293
|
+
"data",
|
|
294
|
+
"targetsite",
|
|
295
|
+
"helplink",
|
|
296
|
+
];
|
|
297
|
+
return exceptionPropertyNames.includes(name.toLowerCase());
|
|
298
|
+
}
|
|
299
|
+
getExceptionConstructorData(program, model, modelName) {
|
|
300
|
+
const allProperties = new ModelInfo().getAllProperties(program, model) ?? [];
|
|
301
|
+
const propertiesWithDefaults = allProperties.map((prop) => {
|
|
302
|
+
const { defaultValue: typeDefault } = this.#findPropertyType(prop);
|
|
303
|
+
const defaultValue = prop.defaultValue
|
|
304
|
+
? code `${JSON.stringify(serializeValueAsJson(program, prop.defaultValue, prop))}`
|
|
305
|
+
: typeDefault;
|
|
306
|
+
return { prop, defaultValue };
|
|
307
|
+
});
|
|
308
|
+
const sortedProperties = propertiesWithDefaults
|
|
309
|
+
.filter(({ prop }) => !isStatusCode(program, prop))
|
|
310
|
+
.sort(({ prop: a, defaultValue: aDefault }, { prop: b, defaultValue: bDefault }) => {
|
|
311
|
+
if (!a.optional && !aDefault && (b.optional || bDefault))
|
|
312
|
+
return -1;
|
|
313
|
+
if (!b.optional && !bDefault && (a.optional || aDefault))
|
|
314
|
+
return 1;
|
|
315
|
+
return 0;
|
|
316
|
+
});
|
|
317
|
+
const properties = [];
|
|
318
|
+
const body = [];
|
|
319
|
+
const header = [];
|
|
320
|
+
const value = [];
|
|
321
|
+
const statusCode = getStatusCode(program, model);
|
|
322
|
+
if (statusCode?.requiresConstructorArgument) {
|
|
323
|
+
properties.push(`int ${statusCode.value}`);
|
|
324
|
+
}
|
|
325
|
+
for (const { prop, defaultValue } of sortedProperties) {
|
|
326
|
+
let propertyName = ensureCSharpIdentifier(program, prop, prop.name);
|
|
327
|
+
if (modelName === propertyName || this.isDuplicateExceptionName(propertyName)) {
|
|
328
|
+
propertyName = `${propertyName}Prop`;
|
|
329
|
+
}
|
|
330
|
+
const type = getCSharpType(program, prop.type);
|
|
331
|
+
properties.push(`${type?.type.name} ${prop.name}${defaultValue ? ` = ${defaultValue}` : `${prop.optional ? " = default" : ""}`}`);
|
|
332
|
+
body.push(`\t\t${propertyName} = ${prop.name};`);
|
|
333
|
+
if (isHeader(program, prop)) {
|
|
334
|
+
const headerName = getHeaderFieldName(program, prop);
|
|
335
|
+
header.push(`{"${headerName}", ${prop.name}}`);
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
value.push(`${prop.name} = ${prop.name}`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return {
|
|
342
|
+
properties: properties.join(", "),
|
|
343
|
+
body: body.join("\n"),
|
|
344
|
+
header: header.join(", "),
|
|
345
|
+
value: value.join(","),
|
|
346
|
+
statusCode: statusCode,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
173
349
|
modelDeclarationContext(model, name) {
|
|
350
|
+
function getSourceModels(source, visited = new Set()) {
|
|
351
|
+
const result = [];
|
|
352
|
+
if (visited.has(source))
|
|
353
|
+
return [];
|
|
354
|
+
result.push(source);
|
|
355
|
+
visited.add(source);
|
|
356
|
+
for (const candidate of source.sourceModels) {
|
|
357
|
+
result.push(...getSourceModels(candidate.model, visited));
|
|
358
|
+
}
|
|
359
|
+
return result;
|
|
360
|
+
}
|
|
361
|
+
function getModelNamespace(model) {
|
|
362
|
+
const sourceModels = getSourceModels(model);
|
|
363
|
+
if (sourceModels.length === 0)
|
|
364
|
+
return undefined;
|
|
365
|
+
return sourceModels
|
|
366
|
+
.filter((m) => m.namespace !== undefined)
|
|
367
|
+
.flatMap((s) => s.namespace)
|
|
368
|
+
.reduce((c, n) => (c === n ? c : undefined));
|
|
369
|
+
}
|
|
174
370
|
if (this.#isMultipartModel(model))
|
|
175
|
-
return
|
|
371
|
+
return this.emitter.getContext();
|
|
176
372
|
const modelName = ensureCSharpIdentifier(this.emitter.getProgram(), model, name);
|
|
177
|
-
const modelFile = this.emitter.createSourceFile(`models/${modelName}.cs`);
|
|
373
|
+
const modelFile = this.emitter.createSourceFile(`generated/models/${modelName}.cs`);
|
|
178
374
|
modelFile.meta[this.#sourceTypeKey] = CSharpSourceType.Model;
|
|
179
|
-
const
|
|
375
|
+
const ns = model.namespace ?? getModelNamespace(model);
|
|
376
|
+
const modelNamespace = this.#getOrAddNamespace(ns);
|
|
180
377
|
return this.#createModelContext(modelNamespace, modelFile, modelName);
|
|
181
378
|
}
|
|
182
379
|
modelInstantiationContext(model) {
|
|
183
380
|
if (this.#isMultipartModel(model))
|
|
184
|
-
return
|
|
381
|
+
return this.emitter.getContext();
|
|
185
382
|
const modelName = getModelInstantiationName(this.emitter.getProgram(), model, model.name);
|
|
186
|
-
const sourceFile = this.emitter.createSourceFile(`models/${modelName}.cs`);
|
|
383
|
+
const sourceFile = this.emitter.createSourceFile(`generated/models/${modelName}.cs`);
|
|
187
384
|
sourceFile.meta[this.#sourceTypeKey] = CSharpSourceType.Model;
|
|
188
|
-
const modelNamespace =
|
|
385
|
+
const modelNamespace = this.#getOrAddNamespace(model.namespace);
|
|
189
386
|
const context = this.#createModelContext(modelNamespace, sourceFile, model.name);
|
|
190
387
|
context.instantiationName = modelName;
|
|
191
388
|
return context;
|
|
@@ -199,12 +396,33 @@ export async function $onEmit(context) {
|
|
|
199
396
|
const program = this.emitter.getProgram();
|
|
200
397
|
const recordType = getRecordType(program, model);
|
|
201
398
|
if (recordType !== undefined) {
|
|
202
|
-
|
|
399
|
+
const valueType = this.#getSimpleType(recordType);
|
|
400
|
+
return code `Dictionary<string, ${valueType ? valueType?.getTypeReference() : this.emitter.emitTypeReference(recordType)}>`;
|
|
203
401
|
}
|
|
204
402
|
const context = this.emitter.getContext();
|
|
205
403
|
const className = context.instantiationName ?? name;
|
|
206
404
|
return this.modelDeclaration(model, className);
|
|
207
405
|
}
|
|
406
|
+
#getSimpleType(type) {
|
|
407
|
+
switch (type.kind) {
|
|
408
|
+
case "Model":
|
|
409
|
+
if (!isRecord(type))
|
|
410
|
+
return undefined;
|
|
411
|
+
return getCSharpType(this.emitter.getProgram(), type)?.type;
|
|
412
|
+
case "Boolean":
|
|
413
|
+
case "Intrinsic":
|
|
414
|
+
case "ModelProperty":
|
|
415
|
+
case "Number":
|
|
416
|
+
case "Scalar":
|
|
417
|
+
case "String":
|
|
418
|
+
case "StringTemplate":
|
|
419
|
+
case "StringTemplateSpan":
|
|
420
|
+
case "Tuple":
|
|
421
|
+
return getCSharpType(this.emitter.getProgram(), type)?.type;
|
|
422
|
+
default:
|
|
423
|
+
return undefined;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
208
426
|
#getMultipartParts(model) {
|
|
209
427
|
const parts = [...model.properties.values()]
|
|
210
428
|
.flatMap((p) => getHttpPart(this.emitter.getProgram(), p.type)?.type)
|
|
@@ -243,9 +461,6 @@ export async function $onEmit(context) {
|
|
|
243
461
|
});
|
|
244
462
|
return this.modelDeclaration(model, modelName);
|
|
245
463
|
}
|
|
246
|
-
#isRecord(type) {
|
|
247
|
-
return type.kind === "Model" && type.name === "Record" && type.indexer !== undefined;
|
|
248
|
-
}
|
|
249
464
|
#isInheritedProperty(property) {
|
|
250
465
|
const visited = [];
|
|
251
466
|
function isInherited(model, propertyName) {
|
|
@@ -266,23 +481,33 @@ export async function $onEmit(context) {
|
|
|
266
481
|
modelPropertyLiteral(property) {
|
|
267
482
|
if (isStatusCode(this.emitter.getProgram(), property))
|
|
268
483
|
return "";
|
|
269
|
-
|
|
270
|
-
const
|
|
484
|
+
let propertyName = ensureCSharpIdentifier(this.emitter.getProgram(), property, property.name);
|
|
485
|
+
const { typeReference: typeName, defaultValue: typeDefault, nullableType: nullable, } = this.#findPropertyType(property);
|
|
271
486
|
const doc = getDoc(this.emitter.getProgram(), property);
|
|
272
|
-
const attributes = getModelAttributes(this.emitter.getProgram(), property, propertyName)
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
487
|
+
const attributes = new Map(getModelAttributes(this.emitter.getProgram(), property, propertyName).map((a) => [
|
|
488
|
+
a.type.name,
|
|
489
|
+
a,
|
|
490
|
+
]));
|
|
491
|
+
const modelName = this.emitter.getContext()["name"];
|
|
492
|
+
if (modelName === propertyName ||
|
|
493
|
+
(this.isDuplicateExceptionName(propertyName) &&
|
|
494
|
+
property.model &&
|
|
495
|
+
isErrorModel(this.emitter.getProgram(), property.model))) {
|
|
496
|
+
propertyName = `${propertyName}Prop`;
|
|
497
|
+
const attr = getEncodedNameAttribute(this.emitter.getProgram(), property, propertyName);
|
|
498
|
+
if (!attributes.has(attr.type.name))
|
|
499
|
+
attributes.set(attr.type.name, attr);
|
|
500
|
+
}
|
|
501
|
+
const defaultValue = this.#opHelpers.getDefaultValue(this.emitter.getProgram(), property.type, property.defaultValue) ?? typeDefault;
|
|
502
|
+
const attributeList = [...attributes.values()];
|
|
278
503
|
return this.emitter.result
|
|
279
|
-
.rawCode(code `${doc ? `${formatComment(doc)}\n` : ""}${`${
|
|
504
|
+
.rawCode(code `${doc ? `${formatComment(doc)}\n` : ""}${`${attributeList.map((attribute) => attribute.getApplicationString(this.emitter.getContext().scope)).join("\n")}${attributeList?.length > 0 ? "\n" : ""}`}public ${this.#isInheritedProperty(property) ? "new " : ""}${typeName}${isValueType(this.emitter.getProgram(), property.type) && (property.optional || nullable)
|
|
280
505
|
? "?"
|
|
281
506
|
: ""} ${propertyName} { get; ${typeDefault ? "}" : "set; }"}${defaultValue ? ` = ${defaultValue};\n` : "\n"}
|
|
282
507
|
`);
|
|
283
508
|
}
|
|
284
509
|
#findPropertyType(property) {
|
|
285
|
-
return this.#
|
|
510
|
+
return this.#opHelpers.getTypeInfo(this.emitter.getProgram(), property.type, property);
|
|
286
511
|
}
|
|
287
512
|
#isMultipartRequest(operation) {
|
|
288
513
|
const body = operation.parameters.body;
|
|
@@ -298,118 +523,6 @@ export async function $onEmit(context) {
|
|
|
298
523
|
}
|
|
299
524
|
return false;
|
|
300
525
|
}
|
|
301
|
-
#getTypeInfoForUnion(union) {
|
|
302
|
-
const propResult = this.#getNonNullableTsType(union);
|
|
303
|
-
if (propResult === undefined) {
|
|
304
|
-
return [
|
|
305
|
-
code `${emitter.emitTypeReference(union)}`,
|
|
306
|
-
undefined,
|
|
307
|
-
[...union.variants.values()].filter((v) => isNullType(v.type)).length > 0,
|
|
308
|
-
];
|
|
309
|
-
}
|
|
310
|
-
const [typeName, typeDefault, _] = this.#getTypeInfoForTsType(propResult.type);
|
|
311
|
-
return [typeName, typeDefault, propResult.nullable];
|
|
312
|
-
}
|
|
313
|
-
#getTypeInfoForTsType(tsType) {
|
|
314
|
-
function extractStringValue(type, span) {
|
|
315
|
-
switch (type.kind) {
|
|
316
|
-
case "String":
|
|
317
|
-
return type.value;
|
|
318
|
-
case "Boolean":
|
|
319
|
-
return `${type.value}`;
|
|
320
|
-
case "Number":
|
|
321
|
-
return type.valueAsString;
|
|
322
|
-
case "StringTemplateSpan":
|
|
323
|
-
if (type.isInterpolated) {
|
|
324
|
-
return extractStringValue(type.type, span);
|
|
325
|
-
}
|
|
326
|
-
else {
|
|
327
|
-
return type.type.value;
|
|
328
|
-
}
|
|
329
|
-
case "ModelProperty":
|
|
330
|
-
return extractStringValue(type.type, span);
|
|
331
|
-
case "EnumMember":
|
|
332
|
-
if (type.value === undefined)
|
|
333
|
-
return type.name;
|
|
334
|
-
if (typeof type.value === "string")
|
|
335
|
-
return type.value;
|
|
336
|
-
if (typeof type.value === "number")
|
|
337
|
-
return `${type.value}`;
|
|
338
|
-
}
|
|
339
|
-
reportDiagnostic(emitter.getProgram(), {
|
|
340
|
-
code: "invalid-interpolation",
|
|
341
|
-
target: span,
|
|
342
|
-
format: {},
|
|
343
|
-
});
|
|
344
|
-
return "";
|
|
345
|
-
}
|
|
346
|
-
switch (tsType.kind) {
|
|
347
|
-
case "String":
|
|
348
|
-
return [code `string`, `"${tsType.value}"`, false];
|
|
349
|
-
case "StringTemplate":
|
|
350
|
-
const template = tsType;
|
|
351
|
-
if (template.stringValue !== undefined)
|
|
352
|
-
return [code `string`, `"${template.stringValue}"`, false];
|
|
353
|
-
const spanResults = [];
|
|
354
|
-
for (const span of template.spans) {
|
|
355
|
-
spanResults.push(extractStringValue(span, span));
|
|
356
|
-
}
|
|
357
|
-
return [code `string`, `"${spanResults.join("")}"`, false];
|
|
358
|
-
case "Boolean":
|
|
359
|
-
return [code `bool`, `${tsType.value === true ? true : false}`, false];
|
|
360
|
-
case "Number":
|
|
361
|
-
const [type, value] = this.#findNumericType(tsType);
|
|
362
|
-
return [code `${type}`, `${value}`, false];
|
|
363
|
-
case "Tuple":
|
|
364
|
-
const defaults = [];
|
|
365
|
-
const [csharpType, isObject] = this.#coalesceTypes(tsType.values);
|
|
366
|
-
if (isObject)
|
|
367
|
-
return ["object[]", undefined, false];
|
|
368
|
-
for (const value of tsType.values) {
|
|
369
|
-
const [_, itemDefault] = this.#getTypeInfoForTsType(value);
|
|
370
|
-
defaults.push(itemDefault);
|
|
371
|
-
}
|
|
372
|
-
return [
|
|
373
|
-
code `${csharpType.getTypeReference()}[]`,
|
|
374
|
-
`[${defaults.join(", ")}]`,
|
|
375
|
-
csharpType.isNullable,
|
|
376
|
-
];
|
|
377
|
-
case "Object":
|
|
378
|
-
return [code `object`, undefined, false];
|
|
379
|
-
case "Model":
|
|
380
|
-
if (this.#isRecord(tsType)) {
|
|
381
|
-
return [code `JsonObject`, undefined, false];
|
|
382
|
-
}
|
|
383
|
-
return [code `${emitter.emitTypeReference(tsType)}`, undefined, false];
|
|
384
|
-
case "ModelProperty":
|
|
385
|
-
return this.#getTypeInfoForTsType(tsType.type);
|
|
386
|
-
case "Enum":
|
|
387
|
-
return [code `${emitter.emitTypeReference(tsType)}`, undefined, false];
|
|
388
|
-
case "EnumMember":
|
|
389
|
-
if (typeof tsType.value === "number") {
|
|
390
|
-
const stringValue = tsType.value.toString();
|
|
391
|
-
if (stringValue.includes(".") || stringValue.includes("e"))
|
|
392
|
-
return ["double", stringValue, false];
|
|
393
|
-
return ["int", stringValue, false];
|
|
394
|
-
}
|
|
395
|
-
if (typeof tsType.value === "string") {
|
|
396
|
-
return ["string", tsType.value, false];
|
|
397
|
-
}
|
|
398
|
-
return [code `object`, undefined, false];
|
|
399
|
-
case "Union":
|
|
400
|
-
return this.#getTypeInfoForUnion(tsType);
|
|
401
|
-
case "UnionVariant":
|
|
402
|
-
return this.#getTypeInfoForTsType(tsType.type);
|
|
403
|
-
default:
|
|
404
|
-
return [code `${emitter.emitTypeReference(tsType)}`, undefined, false];
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
#findNumericType(type) {
|
|
408
|
-
const stringValue = type.valueAsString;
|
|
409
|
-
if (stringValue.includes(".") || stringValue.includes("e"))
|
|
410
|
-
return ["double", stringValue];
|
|
411
|
-
return ["int", stringValue];
|
|
412
|
-
}
|
|
413
526
|
modelPropertyReference(property) {
|
|
414
527
|
return code `${this.emitter.emitTypeReference(property.type)}`;
|
|
415
528
|
}
|
|
@@ -423,10 +536,7 @@ export async function $onEmit(context) {
|
|
|
423
536
|
const doc = getDoc(this.emitter.getProgram(), iface);
|
|
424
537
|
const attributes = getModelAttributes(this.emitter.getProgram(), iface, ifaceName);
|
|
425
538
|
this.#metadateMap.set(iface, new CSharpType({ name: ifaceName, namespace: namespace }));
|
|
426
|
-
const decl = this.emitter.result.declaration(ifaceName, code
|
|
427
|
-
|
|
428
|
-
${this.#emitUsings()}
|
|
429
|
-
|
|
539
|
+
const decl = this.emitter.result.declaration(ifaceName, code `
|
|
430
540
|
namespace ${namespace} {
|
|
431
541
|
|
|
432
542
|
${doc ? `${formatComment(doc)}\n` : ""}${`${attributes.map((attribute) => attribute.getApplicationString(this.emitter.getContext().scope)).join("\n")}${attributes?.length > 0 ? "\n" : ""}`}public interface ${ifaceName} {
|
|
@@ -438,80 +548,66 @@ export async function $onEmit(context) {
|
|
|
438
548
|
interfaceDeclarationContext(iface, name) {
|
|
439
549
|
// set up interfaces file for declaration
|
|
440
550
|
const ifaceName = `I${ensureCSharpIdentifier(this.emitter.getProgram(), iface, name, NameCasingType.Class)}`;
|
|
441
|
-
const sourceFile = this.emitter.createSourceFile(`operations/${ifaceName}.cs`);
|
|
551
|
+
const sourceFile = this.emitter.createSourceFile(`generated/operations/${ifaceName}.cs`);
|
|
442
552
|
sourceFile.meta[this.#sourceTypeKey] = CSharpSourceType.Interface;
|
|
443
|
-
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
]);
|
|
452
|
-
context.file.imports.set("System.Threading.Tasks", ["System.Threading.Tasks"]);
|
|
453
|
-
context.file.imports.set("Microsoft.AspNetCore.Mvc", ["Microsoft.AspNetCore.Mvc"]);
|
|
553
|
+
sourceFile.meta["nullable"] = true;
|
|
554
|
+
const ifaceNamespace = this.#getOrAddNamespace(iface.namespace);
|
|
555
|
+
sourceFile.imports.set("System", ["System"]);
|
|
556
|
+
sourceFile.imports.set("System.Net", ["System.Net"]);
|
|
557
|
+
sourceFile.imports.set("System.Text.Json", ["System.Text.Json"]);
|
|
558
|
+
sourceFile.imports.set("System.Text.Json.Serialization", ["System.Text.Json.Serialization"]);
|
|
559
|
+
sourceFile.imports.set("System.Threading.Tasks", ["System.Threading.Tasks"]);
|
|
560
|
+
sourceFile.imports.set("Microsoft.AspNetCore.Mvc", ["Microsoft.AspNetCore.Mvc"]);
|
|
454
561
|
if (this.#hasMultipartOperation(iface)) {
|
|
455
|
-
|
|
562
|
+
sourceFile.imports.set("Microsoft.AspNetCore.WebUtilities", [
|
|
456
563
|
"Microsoft.AspNetCore.WebUtilities",
|
|
457
564
|
]);
|
|
458
565
|
}
|
|
459
|
-
|
|
460
|
-
return context;
|
|
566
|
+
return this.#createModelContext(ifaceNamespace, sourceFile, ifaceName);
|
|
461
567
|
}
|
|
462
568
|
interfaceDeclarationOperations(iface) {
|
|
463
569
|
// add in operations
|
|
464
570
|
const builder = new StringBuilder();
|
|
465
|
-
const metadata = new HttpMetadata();
|
|
466
571
|
const context = this.emitter.getContext();
|
|
467
572
|
const name = `${ensureCSharpIdentifier(this.emitter.getProgram(), iface, iface.name, NameCasingType.Class)}`;
|
|
468
|
-
const ifaceNamespace = this.#
|
|
469
|
-
const namespace =
|
|
573
|
+
const ifaceNamespace = this.#getOrAddNamespace(iface.namespace);
|
|
574
|
+
const namespace = ifaceNamespace;
|
|
470
575
|
const mock = {
|
|
471
576
|
className: name,
|
|
472
577
|
interfaceName: `I${name}`,
|
|
473
578
|
methods: [],
|
|
474
579
|
namespace: namespace,
|
|
475
|
-
usings: [
|
|
580
|
+
usings: [],
|
|
476
581
|
};
|
|
477
582
|
for (const [name, operation] of iface.operations) {
|
|
583
|
+
if (isTemplateDeclaration(operation))
|
|
584
|
+
continue;
|
|
478
585
|
const doc = getDoc(this.emitter.getProgram(), operation);
|
|
479
586
|
const returnTypes = [];
|
|
480
587
|
const [httpOp, _] = getHttpOperation(this.emitter.getProgram(), operation);
|
|
481
588
|
for (const response of httpOp.responses.filter((r) => !isErrorModel(this.emitter.getProgram(), r.type))) {
|
|
482
|
-
|
|
589
|
+
const [_, responseType] = this.#resolveOperationResponse(response, httpOp.operation);
|
|
590
|
+
returnTypes.push(responseType);
|
|
483
591
|
}
|
|
484
|
-
const returnInfo = coalesceTypes(this.emitter.getProgram(), returnTypes,
|
|
592
|
+
const returnInfo = coalesceTypes(this.emitter.getProgram(), returnTypes, namespace);
|
|
485
593
|
const returnType = returnInfo?.type || UnknownType;
|
|
486
594
|
const opName = ensureCSharpIdentifier(this.emitter.getProgram(), operation, name, NameCasingType.Method);
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
opDecl = this.emitter.result.declaration(opName, code `${doc ? `${formatComment(doc)}\n` : ""}${opImpl.returnType} ${opImpl.methodName}( ${opImpl.methodParams});`);
|
|
499
|
-
}
|
|
500
|
-
else {
|
|
501
|
-
opImpl = {
|
|
502
|
-
methodName: `${opName}Async`,
|
|
503
|
-
methodParams: `${this.#emitInterfaceOperationParameters(operation)}`,
|
|
504
|
-
returnType: `${returnType.name === "void" ? "Task" : `Task<${returnType.getTypeReference(context.scope)}>`}`,
|
|
505
|
-
instantiatedReturnType: returnType.name === "void"
|
|
506
|
-
? undefined
|
|
507
|
-
: `${returnType.getTypeReference(context.scope)}`,
|
|
508
|
-
};
|
|
509
|
-
opDecl = this.emitter.result.declaration(opName, code `${doc ? `${formatComment(doc)}\n` : ""}${opImpl.returnType} ${opImpl.methodName}( ${opImpl.methodParams});`);
|
|
510
|
-
}
|
|
595
|
+
const parameters = this.#opHelpers.getParameters(this.emitter.getProgram(), httpOp);
|
|
596
|
+
const opImpl = {
|
|
597
|
+
methodName: `${opName}Async`,
|
|
598
|
+
methodParams: `${getBusinessLogicDeclParameters(parameters)}`,
|
|
599
|
+
returnType: returnType,
|
|
600
|
+
returnTypeName: `${returnType.name === "void" ? "Task" : `Task<${returnType.getTypeReference(context.scope)}>`}`,
|
|
601
|
+
instantiatedReturnType: returnType.name === "void"
|
|
602
|
+
? undefined
|
|
603
|
+
: `${returnType.getTypeReference(context.scope)}`,
|
|
604
|
+
};
|
|
605
|
+
const opDecl = this.emitter.result.declaration(opName, code `${doc ? `${formatComment(doc)}\n` : ""}${opImpl.returnTypeName} ${opImpl.methodName}( ${opImpl.methodParams});`);
|
|
511
606
|
mock.methods.push(opImpl);
|
|
512
607
|
builder.push(code `${opDecl.value}\n`);
|
|
513
608
|
this.emitter.emitInterfaceOperation(operation);
|
|
514
609
|
}
|
|
610
|
+
mock.usings.push(...getImports(context.scope));
|
|
515
611
|
this.#mockRegistrations.set(mock.interfaceName, mock);
|
|
516
612
|
return builder.reduce();
|
|
517
613
|
}
|
|
@@ -529,9 +625,8 @@ export async function $onEmit(context) {
|
|
|
529
625
|
const doc = getDoc(this.emitter.getProgram(), operation);
|
|
530
626
|
const [httpOperation, _] = getHttpOperation(this.emitter.getProgram(), operation);
|
|
531
627
|
const multipart = this.#isMultipartRequest(httpOperation);
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
: this.#emitHttpOperationParameters(httpOperation, true);
|
|
628
|
+
const parameters = this.#opHelpers.getParameters(this.emitter.getProgram(), httpOperation);
|
|
629
|
+
const declParams = getHttpDeclParameters(parameters);
|
|
535
630
|
if (multipart) {
|
|
536
631
|
const context = this.emitter.getContext();
|
|
537
632
|
context.file.imports.set("Microsoft.AspNetCore.WebUtilities", [
|
|
@@ -551,7 +646,7 @@ export async function $onEmit(context) {
|
|
|
551
646
|
isValueType: false,
|
|
552
647
|
});
|
|
553
648
|
const hasResponseValue = response.name !== "void";
|
|
554
|
-
const
|
|
649
|
+
const returnStatement = getControllerReturnStatement(status, hasResponseValue);
|
|
555
650
|
if (!this.#isMultipartRequest(httpOperation)) {
|
|
556
651
|
return this.emitter.result.declaration(operation.name, code `
|
|
557
652
|
${doc ? `${formatComment(doc)}` : ""}
|
|
@@ -561,10 +656,10 @@ export async function $onEmit(context) {
|
|
|
561
656
|
public virtual async Task<IActionResult> ${operationName}(${declParams})
|
|
562
657
|
{
|
|
563
658
|
${hasResponseValue
|
|
564
|
-
? `var result = await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${
|
|
565
|
-
|
|
566
|
-
: `await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${
|
|
567
|
-
|
|
659
|
+
? `var result = await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${getBusinessLogicCallParameters(parameters)});
|
|
660
|
+
${returnStatement}`
|
|
661
|
+
: `await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${getBusinessLogicCallParameters(parameters)});
|
|
662
|
+
${returnStatement}`}
|
|
568
663
|
}`);
|
|
569
664
|
}
|
|
570
665
|
else {
|
|
@@ -585,10 +680,10 @@ export async function $onEmit(context) {
|
|
|
585
680
|
|
|
586
681
|
var reader = new MultipartReader(boundary, Request.Body);
|
|
587
682
|
${hasResponseValue
|
|
588
|
-
? `var result = await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${
|
|
589
|
-
|
|
590
|
-
: `await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${
|
|
591
|
-
|
|
683
|
+
? `var result = await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${getBusinessLogicCallParameters(parameters)});
|
|
684
|
+
${returnStatement}`
|
|
685
|
+
: `await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${getBusinessLogicCallParameters(parameters)});
|
|
686
|
+
${returnStatement}`}
|
|
592
687
|
}`);
|
|
593
688
|
}
|
|
594
689
|
}
|
|
@@ -603,7 +698,8 @@ export async function $onEmit(context) {
|
|
|
603
698
|
const operationName = ensureCSharpIdentifier(this.emitter.getProgram(), operation, name, NameCasingType.Method);
|
|
604
699
|
const doc = getDoc(this.emitter.getProgram(), operation);
|
|
605
700
|
const [httpOperation, _] = getHttpOperation(this.emitter.getProgram(), operation);
|
|
606
|
-
const
|
|
701
|
+
const parameters = this.#opHelpers.getParameters(this.emitter.getProgram(), httpOperation);
|
|
702
|
+
const declParams = getHttpDeclParameters(parameters);
|
|
607
703
|
const responseInfo = this.#getOperationResponse(httpOperation);
|
|
608
704
|
const status = responseInfo?.statusCode ?? 200;
|
|
609
705
|
const response = responseInfo?.resultType ??
|
|
@@ -614,7 +710,7 @@ export async function $onEmit(context) {
|
|
|
614
710
|
isValueType: false,
|
|
615
711
|
});
|
|
616
712
|
const hasResponseValue = response.name !== "void";
|
|
617
|
-
const
|
|
713
|
+
const returnStatement = getControllerReturnStatement(status, hasResponseValue);
|
|
618
714
|
return this.emitter.result.declaration(operation.name, code `
|
|
619
715
|
${doc ? `${formatComment(doc)}` : ""}
|
|
620
716
|
[${getOperationVerbDecorator(httpOperation)}]
|
|
@@ -623,10 +719,10 @@ export async function $onEmit(context) {
|
|
|
623
719
|
public virtual async Task<IActionResult> ${operationName}(${declParams})
|
|
624
720
|
{
|
|
625
721
|
${hasResponseValue
|
|
626
|
-
? `var result = await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${
|
|
627
|
-
|
|
628
|
-
: `await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${
|
|
629
|
-
|
|
722
|
+
? `var result = await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${getBusinessLogicCallParameters(parameters)});
|
|
723
|
+
${returnStatement}`
|
|
724
|
+
: `await ${this.emitter.getContext().resourceName}Impl.${operationName}Async(${getBusinessLogicCallParameters(parameters)});
|
|
725
|
+
${returnStatement}`}
|
|
630
726
|
}`);
|
|
631
727
|
}
|
|
632
728
|
operationReturnType(operation, returnType) {
|
|
@@ -636,6 +732,28 @@ export async function $onEmit(context) {
|
|
|
636
732
|
stringTemplate(stringTemplate) {
|
|
637
733
|
return this.emitter.result.rawCode(stringTemplate.stringValue || "");
|
|
638
734
|
}
|
|
735
|
+
#resolveOperationResponse(response, operation) {
|
|
736
|
+
function getName(sourceType, part) {
|
|
737
|
+
return ensureCSharpIdentifier(emitter.getProgram(), sourceType, part, NameCasingType.Class);
|
|
738
|
+
}
|
|
739
|
+
let responseType = new HttpMetadata().resolveLogicalResponseType(this.emitter.getProgram(), response);
|
|
740
|
+
if (responseType.kind === "Model" && !responseType.name) {
|
|
741
|
+
const modelName = `${getName(responseType, operation.interface.name)}${getName(responseType, operation.name)}Response}`;
|
|
742
|
+
const returnedType = this.emitter
|
|
743
|
+
.getProgram()
|
|
744
|
+
.checker.cloneType(responseType, { name: modelName });
|
|
745
|
+
responseType = returnedType;
|
|
746
|
+
}
|
|
747
|
+
// TemplateParameter types cannot be emitted (they are unresolved template placeholders).
|
|
748
|
+
// Template operations are filtered in interfaceDeclarationOperations, but this guard
|
|
749
|
+
// prevents crashes if a TemplateParameter response type is encountered via other paths.
|
|
750
|
+
if (responseType.kind !== "TemplateParameter") {
|
|
751
|
+
this.emitter.emitType(responseType);
|
|
752
|
+
}
|
|
753
|
+
const context = this.emitter.getContext();
|
|
754
|
+
const result = getCSharpType(this.emitter.getProgram(), responseType, context.namespace);
|
|
755
|
+
return [result?.type || UnknownType, responseType];
|
|
756
|
+
}
|
|
639
757
|
#getOperationResponse(operation) {
|
|
640
758
|
const validResponses = operation.responses.filter((r) => !isErrorModel(this.emitter.getProgram(), r.type) &&
|
|
641
759
|
getCSharpStatusCode(r.statusCodes) !== undefined);
|
|
@@ -656,106 +774,36 @@ export async function $onEmit(context) {
|
|
|
656
774
|
};
|
|
657
775
|
}
|
|
658
776
|
#emitOperationResponses(operation) {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
getCSharpStatusCode(r.statusCodes) !== undefined);
|
|
663
|
-
for (const response of validResponses) {
|
|
664
|
-
i++;
|
|
665
|
-
builder.push(code `${this.#emitOperationResponseDecorator(response)}`);
|
|
666
|
-
if (i < validResponses.length) {
|
|
667
|
-
builder.pushLiteralSegment("\n");
|
|
668
|
-
}
|
|
777
|
+
function isValid(program, response) {
|
|
778
|
+
return (!isErrorModel(program, response.type) &&
|
|
779
|
+
getCSharpStatusCode(response.statusCodes) !== undefined);
|
|
669
780
|
}
|
|
781
|
+
const builder = new StringBuilder();
|
|
670
782
|
for (const response of operation.responses) {
|
|
671
|
-
|
|
672
|
-
|
|
783
|
+
const [responseType, _] = this.#resolveOperationResponse(response, operation.operation);
|
|
784
|
+
if (isValid(this.emitter.getProgram(), response)) {
|
|
785
|
+
builder.push(code `${builder.segments.length > 0 ? "\n" : ""}${this.#emitOperationResponseDecorator(response, responseType)}`);
|
|
786
|
+
}
|
|
673
787
|
}
|
|
674
788
|
return builder.reduce();
|
|
675
789
|
}
|
|
676
|
-
#emitOperationResponseDecorator(response) {
|
|
677
|
-
|
|
678
|
-
return this.emitter.result.rawCode(code `[ProducesResponseType((int)${getCSharpStatusCode(response.statusCodes)}, Type = typeof(${this.#emitResponseType(responseType)}))]`);
|
|
790
|
+
#emitOperationResponseDecorator(response, result) {
|
|
791
|
+
return this.emitter.result.rawCode(code `[ProducesResponseType((int)${getCSharpStatusCode(response.statusCodes)}, Type = typeof(${this.#emitResponseType(result)}))]`);
|
|
679
792
|
}
|
|
680
793
|
#emitResponseType(type) {
|
|
681
794
|
const context = this.emitter.getContext();
|
|
682
|
-
|
|
683
|
-
const resultType = result?.type || UnknownType;
|
|
684
|
-
return resultType.getTypeReference(context.scope);
|
|
685
|
-
}
|
|
686
|
-
#emitInterfaceOperationParameters(operation, bodyParam) {
|
|
687
|
-
const signature = new StringBuilder();
|
|
688
|
-
const requiredParams = [];
|
|
689
|
-
const optionalParams = [];
|
|
690
|
-
let totalParams = 0;
|
|
691
|
-
if (bodyParam !== undefined)
|
|
692
|
-
totalParams++;
|
|
693
|
-
const validParams = [...operation.parameters.properties.entries()].filter(([_, p]) => isValidParameter(this.emitter.getProgram(), p));
|
|
694
|
-
for (const [_, parameter] of validParams) {
|
|
695
|
-
if (!isContentTypeHeader(this.emitter.getProgram(), parameter) &&
|
|
696
|
-
(bodyParam === undefined || isHttpMetadata(this.emitter.getProgram(), parameter))) {
|
|
697
|
-
if (parameter.optional || parameter.defaultValue)
|
|
698
|
-
optionalParams.push(parameter);
|
|
699
|
-
else
|
|
700
|
-
requiredParams.push(parameter);
|
|
701
|
-
totalParams++;
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
let i = 1;
|
|
705
|
-
for (const requiredParam of requiredParams) {
|
|
706
|
-
const [paramType, _, __] = this.#findPropertyType(requiredParam);
|
|
707
|
-
signature.push(code `${paramType} ${ensureCSharpIdentifier(this.emitter.getProgram(), requiredParam, requiredParam.name, NameCasingType.Parameter)}${i++ < totalParams ? ", " : ""}`);
|
|
708
|
-
}
|
|
709
|
-
if (bodyParam) {
|
|
710
|
-
signature.push(bodyParam);
|
|
711
|
-
if (i++ < totalParams)
|
|
712
|
-
signature.push(", ");
|
|
713
|
-
}
|
|
714
|
-
for (const optionalParam of optionalParams) {
|
|
715
|
-
const [paramType, _, __] = this.#findPropertyType(optionalParam);
|
|
716
|
-
signature.push(code `${paramType}? ${ensureCSharpIdentifier(this.emitter.getProgram(), optionalParam, optionalParam.name, NameCasingType.Parameter)}${i++ < totalParams ? ", " : ""}`);
|
|
717
|
-
}
|
|
718
|
-
return signature.reduce();
|
|
719
|
-
}
|
|
720
|
-
#emitHttpOperationParameters(operation, bodyParameter) {
|
|
721
|
-
const signature = new StringBuilder();
|
|
722
|
-
const bodyParam = operation.parameters.body;
|
|
723
|
-
let i = 0;
|
|
724
|
-
//const pathParameters = operation.parameters.parameters.filter((p) => p.type === "path");
|
|
725
|
-
const validParams = operation.parameters.parameters.filter((p) => isValidParameter(this.emitter.getProgram(), p.param));
|
|
726
|
-
const requiredParams = validParams.filter((p) => p.type === "path" || (!p.param.optional && p.param.defaultValue === undefined));
|
|
727
|
-
const optionalParams = validParams.filter((p) => p.type !== "path" && (p.param.optional || p.param.defaultValue !== undefined));
|
|
728
|
-
for (const parameter of requiredParams) {
|
|
729
|
-
signature.push(code `${this.#emitOperationSignatureParameter(operation, parameter)}${++i < requiredParams.length ? ", " : ""}`);
|
|
730
|
-
}
|
|
731
|
-
if (requiredParams.length > 0 &&
|
|
732
|
-
(optionalParams.length > 0 || (bodyParameter === undefined && bodyParam !== undefined))) {
|
|
733
|
-
signature.push(code `, `);
|
|
734
|
-
}
|
|
735
|
-
if (bodyParameter === undefined) {
|
|
736
|
-
if (bodyParam !== undefined) {
|
|
737
|
-
signature.push(code `${this.emitter.emitTypeReference(this.#metaInfo.getEffectivePayloadType(bodyParam.type, Visibility.Create || Visibility.Update))} body${requiredParams.length > 0 && optionalParams.length > 0 ? ", " : ""}`);
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
i = 0;
|
|
741
|
-
for (const parameter of optionalParams) {
|
|
742
|
-
signature.push(code `${this.#emitOperationSignatureParameter(operation, parameter)}${++i < optionalParams.length ? ", " : ""}`);
|
|
743
|
-
}
|
|
744
|
-
return signature.reduce();
|
|
795
|
+
return type.getTypeReference(context.scope);
|
|
745
796
|
}
|
|
746
797
|
unionDeclaration(union, name) {
|
|
747
|
-
const baseType =
|
|
748
|
-
if (
|
|
798
|
+
const baseType = coalesceUnionTypes(this.emitter.getProgram(), union);
|
|
799
|
+
if (isStringEnumType(this.emitter.getProgram(), union)) {
|
|
749
800
|
const program = this.emitter.getProgram();
|
|
750
801
|
const unionName = ensureCSharpIdentifier(program, union, name);
|
|
751
802
|
const namespace = this.emitter.getContext().namespace;
|
|
752
803
|
const doc = getDoc(this.emitter.getProgram(), union);
|
|
753
804
|
const attributes = getModelAttributes(program, union, unionName);
|
|
754
805
|
this.#metadateMap.set(union, new CSharpType({ name: unionName, namespace: namespace }));
|
|
755
|
-
return this.emitter.result.declaration(unionName, code
|
|
756
|
-
|
|
757
|
-
${this.#emitUsings()}
|
|
758
|
-
|
|
806
|
+
return this.emitter.result.declaration(unionName, code `
|
|
759
807
|
namespace ${namespace}
|
|
760
808
|
{
|
|
761
809
|
|
|
@@ -763,24 +811,19 @@ export async function $onEmit(context) {
|
|
|
763
811
|
${attributes.map((attribute) => attribute.getApplicationString(this.emitter.getContext().scope)).join("\n")}
|
|
764
812
|
public enum ${unionName}
|
|
765
813
|
{
|
|
766
|
-
${this.emitter.emitUnionVariants(union)}
|
|
814
|
+
${this.emitter.emitUnionVariants(union)}
|
|
767
815
|
}
|
|
768
816
|
} `);
|
|
769
817
|
}
|
|
770
|
-
return this.emitter.result.rawCode(code `${baseType.getTypeReference()}`);
|
|
818
|
+
return this.emitter.result.rawCode(code `${baseType.getTypeReference(this.emitter.getContext().scope)}`);
|
|
771
819
|
}
|
|
772
820
|
unionDeclarationContext(union) {
|
|
773
|
-
|
|
774
|
-
if (baseType.isBuiltIn && baseType.name === "string") {
|
|
821
|
+
if (isStringEnumType(this.emitter.getProgram(), union)) {
|
|
775
822
|
const unionName = ensureCSharpIdentifier(this.emitter.getProgram(), union, union.name || "Union");
|
|
776
|
-
const unionFile = this.emitter.createSourceFile(`models/${unionName}.cs`);
|
|
823
|
+
const unionFile = this.emitter.createSourceFile(`generated/models/${unionName}.cs`);
|
|
777
824
|
unionFile.meta[this.#sourceTypeKey] = CSharpSourceType.Model;
|
|
778
|
-
const unionNamespace =
|
|
779
|
-
return
|
|
780
|
-
namespace: unionNamespace,
|
|
781
|
-
file: unionFile,
|
|
782
|
-
scope: unionFile.globalScope,
|
|
783
|
-
};
|
|
825
|
+
const unionNamespace = this.#getOrAddNamespace(union.namespace);
|
|
826
|
+
return this.#createEnumContext(unionNamespace, unionFile, unionName);
|
|
784
827
|
}
|
|
785
828
|
else {
|
|
786
829
|
return this.emitter.getContext();
|
|
@@ -801,7 +844,9 @@ export async function $onEmit(context) {
|
|
|
801
844
|
const nameHint = name || variant.type.value;
|
|
802
845
|
const memberName = ensureCSharpIdentifier(this.emitter.getProgram(), variant, nameHint);
|
|
803
846
|
this.#metadateMap.set(variant, { name: memberName });
|
|
804
|
-
result.push(code
|
|
847
|
+
result.push(code `
|
|
848
|
+
[JsonStringEnumMemberName("${variant.type.value}")]
|
|
849
|
+
${ensureCSharpIdentifier(this.emitter.getProgram(), variant, nameHint, NameCasingType.Property)}`);
|
|
805
850
|
if (i < union.variants.size)
|
|
806
851
|
result.pushLiteralSegment(", ");
|
|
807
852
|
}
|
|
@@ -811,132 +856,59 @@ export async function $onEmit(context) {
|
|
|
811
856
|
unionVariantContext(union) {
|
|
812
857
|
return super.unionVariantContext(union);
|
|
813
858
|
}
|
|
814
|
-
#emitOperationSignatureParameter(operation, httpParam) {
|
|
815
|
-
const name = httpParam.param.name;
|
|
816
|
-
const parameter = httpParam.param;
|
|
817
|
-
const emittedName = ensureCSharpIdentifier(this.emitter.getProgram(), parameter, name, NameCasingType.Parameter);
|
|
818
|
-
let [emittedType, emittedDefault, _] = this.#findPropertyType(parameter);
|
|
819
|
-
if (emittedType.toString().endsWith("[]"))
|
|
820
|
-
emittedDefault = undefined;
|
|
821
|
-
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
822
|
-
const defaultValue = parameter.default
|
|
823
|
-
? // eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
824
|
-
code `${this.emitter.emitType(parameter.default)}`
|
|
825
|
-
: emittedDefault;
|
|
826
|
-
return this.emitter.result.rawCode(code `${httpParam.type !== "path" ? this.#emitParameterAttribute(httpParam) : ""}${emittedType} ${emittedName}${defaultValue === undefined ? "" : ` = ${defaultValue}`}`);
|
|
827
|
-
}
|
|
828
|
-
#getBodyParameters(operation) {
|
|
829
|
-
const bodyParam = operation.parameters.body;
|
|
830
|
-
if (bodyParam === undefined)
|
|
831
|
-
return undefined;
|
|
832
|
-
if (bodyParam.property !== undefined)
|
|
833
|
-
return [bodyParam.property];
|
|
834
|
-
if (bodyParam.type.kind !== "Model" || bodyParam.type.properties.size < 1)
|
|
835
|
-
return undefined;
|
|
836
|
-
return [...bodyParam.type.properties.values()];
|
|
837
|
-
}
|
|
838
|
-
#emitOperationCallParameters(operation, bodyParameter = "body") {
|
|
839
|
-
const signature = new StringBuilder();
|
|
840
|
-
let i = 0;
|
|
841
|
-
const bodyParameters = this.#getBodyParameters(operation);
|
|
842
|
-
//const pathParameters = operation.parameters.parameters.filter((p) => p.type === "path");
|
|
843
|
-
const valid = operation.parameters.parameters.filter((p) => isValidParameter(this.emitter.getProgram(), p.param));
|
|
844
|
-
const required = valid.filter((p) => p.type === "path" || (!p.param.optional && p.param.defaultValue === undefined));
|
|
845
|
-
const optional = valid.filter((p) => p.type !== "path" && (p.param.optional || p.param.defaultValue !== undefined));
|
|
846
|
-
for (const parameter of required) {
|
|
847
|
-
const contentType = isContentTypeHeader(this.emitter.getProgram(), parameter.param);
|
|
848
|
-
i++;
|
|
849
|
-
if (!isNeverType(parameter.param.type) &&
|
|
850
|
-
!isNullType(parameter.param.type) &&
|
|
851
|
-
!isVoidType(parameter.param.type) &&
|
|
852
|
-
!contentType) {
|
|
853
|
-
signature.push(code `${this.#emitOperationCallParameter(operation, parameter)}${i < valid.length || bodyParameters !== undefined ? ", " : ""}`);
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
if (bodyParameters !== undefined) {
|
|
857
|
-
if (bodyParameters.length === 1) {
|
|
858
|
-
signature.push(code `${bodyParameter}`);
|
|
859
|
-
}
|
|
860
|
-
else {
|
|
861
|
-
let j = 0;
|
|
862
|
-
for (const parameter of bodyParameters) {
|
|
863
|
-
j++;
|
|
864
|
-
const propertyName = ensureCSharpIdentifier(this.emitter.getProgram(), parameter, parameter.name, NameCasingType.Property);
|
|
865
|
-
signature.push(code `${bodyParameter}?.${propertyName}${j < bodyParameters.length || i < valid.length ? ", " : ""}`);
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
for (const parameter of optional) {
|
|
870
|
-
const contentType = isContentTypeHeader(this.emitter.getProgram(), parameter.param);
|
|
871
|
-
i++;
|
|
872
|
-
if (!isNeverType(parameter.param.type) &&
|
|
873
|
-
!isNullType(parameter.param.type) &&
|
|
874
|
-
!isVoidType(parameter.param.type) &&
|
|
875
|
-
!contentType) {
|
|
876
|
-
signature.push(code `${this.#emitOperationCallParameter(operation, parameter)}${i < valid.length ? ", " : ""}`);
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
return signature.reduce();
|
|
880
|
-
}
|
|
881
|
-
#emitOperationCallParameter(operation, httpParam) {
|
|
882
|
-
const name = httpParam.param.name;
|
|
883
|
-
const parameter = httpParam.param;
|
|
884
|
-
const emittedName = ensureCSharpIdentifier(this.emitter.getProgram(), parameter, name, NameCasingType.Parameter);
|
|
885
|
-
return this.emitter.result.rawCode(code `${emittedName}`);
|
|
886
|
-
}
|
|
887
|
-
#emitParameterAttribute(parameter) {
|
|
888
|
-
switch (parameter.type) {
|
|
889
|
-
case "header":
|
|
890
|
-
return code `[FromHeader(Name="${parameter.name}")] `;
|
|
891
|
-
case "query":
|
|
892
|
-
return code `[FromQuery(Name="${parameter.name}")] `;
|
|
893
|
-
default:
|
|
894
|
-
return "";
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
859
|
#createModelContext(namespace, file, name) {
|
|
860
|
+
file.imports.set("System", ["System"]);
|
|
861
|
+
file.imports.set("System.Text.Json", ["System.Text.Json"]);
|
|
862
|
+
file.imports.set("System.Text.Json.Serialization", ["System.Text.Json.Serialization"]);
|
|
863
|
+
file.imports.set("TypeSpec.Helpers.JsonConverters", ["TypeSpec.Helpers.JsonConverters"]);
|
|
864
|
+
file.imports.set("TypeSpec.Helpers", ["TypeSpec.Helpers"]);
|
|
865
|
+
file.meta[this.#nsKey] = namespace;
|
|
898
866
|
const context = {
|
|
899
867
|
namespace: namespace,
|
|
900
868
|
name: name,
|
|
901
869
|
file: file,
|
|
902
870
|
scope: file.globalScope,
|
|
903
871
|
};
|
|
904
|
-
context.file.imports.set("System", ["System"]);
|
|
905
|
-
context.file.imports.set("System.Text.Json", ["System.Text.Json"]);
|
|
906
|
-
context.file.imports.set("System.Text.Json.Serialization", [
|
|
907
|
-
"System.Text.Json.Serialization",
|
|
908
|
-
]);
|
|
909
872
|
return context;
|
|
910
873
|
}
|
|
874
|
+
#createEnumContext(namespace, file, name) {
|
|
875
|
+
file.imports.set("System.Text.Json", ["System.Text.Json"]);
|
|
876
|
+
file.imports.set("System.Text.Json.Serialization", ["System.Text.Json.Serialization"]);
|
|
877
|
+
file.meta[this.#nsKey] = namespace;
|
|
878
|
+
return {
|
|
879
|
+
namespace: namespace,
|
|
880
|
+
name: name,
|
|
881
|
+
file: file,
|
|
882
|
+
scope: file.globalScope,
|
|
883
|
+
};
|
|
884
|
+
}
|
|
911
885
|
#createOrGetResourceContext(name, operation, resource) {
|
|
912
|
-
|
|
886
|
+
name = ensureCSharpIdentifier(this.emitter.getProgram(), operation, name, NameCasingType.Class);
|
|
887
|
+
const namespace = this.#getOrAddNamespace(operation.namespace);
|
|
888
|
+
let context = controllers.get(`${namespace}.${name}`);
|
|
913
889
|
if (context !== undefined)
|
|
914
890
|
return context;
|
|
915
|
-
const sourceFile = this.emitter.createSourceFile(`controllers/${name}Controller.cs`);
|
|
916
|
-
const namespace = this.#getOrSetBaseNamespace(operation);
|
|
917
|
-
const modelNamespace = `${namespace}.Models`;
|
|
891
|
+
const sourceFile = this.emitter.createSourceFile(`generated/controllers/${name}Controller.cs`);
|
|
918
892
|
sourceFile.meta[this.#sourceTypeKey] = CSharpSourceType.Controller;
|
|
919
893
|
sourceFile.meta["resourceName"] = name;
|
|
920
894
|
sourceFile.meta["resource"] = `${name}Controller`;
|
|
921
895
|
sourceFile.meta["namespace"] = namespace;
|
|
896
|
+
sourceFile.imports.set("System", ["System"]);
|
|
897
|
+
sourceFile.imports.set("System.Net", ["System.Net"]);
|
|
898
|
+
sourceFile.imports.set("System.Threading.Tasks", ["System.Threading.Tasks"]);
|
|
899
|
+
sourceFile.imports.set("System.Text.Json", ["System.Text.Json"]);
|
|
900
|
+
sourceFile.imports.set("System.Text.Json.Serialization", ["System.Text.Json.Serialization"]);
|
|
901
|
+
sourceFile.imports.set("Microsoft.AspNetCore.Mvc", ["Microsoft.AspNetCore.Mvc"]);
|
|
902
|
+
sourceFile.imports.set(namespace, [namespace]);
|
|
903
|
+
sourceFile.meta[this.#nsKey] = namespace;
|
|
922
904
|
context = {
|
|
923
|
-
namespace:
|
|
905
|
+
namespace: namespace,
|
|
924
906
|
file: sourceFile,
|
|
925
907
|
resourceName: name,
|
|
926
908
|
scope: sourceFile.globalScope,
|
|
927
909
|
resourceType: resource,
|
|
928
910
|
};
|
|
929
|
-
|
|
930
|
-
context.file.imports.set("System.Net", ["System.Net"]);
|
|
931
|
-
context.file.imports.set("System.Threading.Tasks", ["System.Threading.Tasks"]);
|
|
932
|
-
context.file.imports.set("System.Text.Json", ["System.Text.Json"]);
|
|
933
|
-
context.file.imports.set("System.Text.Json.Serialization", [
|
|
934
|
-
"System.Text.Json.Serialization",
|
|
935
|
-
]);
|
|
936
|
-
context.file.imports.set("Microsoft.AspNetCore.Mvc", ["Microsoft.AspNetCore.Mvc"]);
|
|
937
|
-
context.file.imports.set(modelNamespace, [modelNamespace]);
|
|
938
|
-
context.file.imports.set(namespace, [namespace]);
|
|
939
|
-
controllers.set(name, context);
|
|
911
|
+
controllers.set(`${namespace}.${name}`, context);
|
|
940
912
|
return context;
|
|
941
913
|
}
|
|
942
914
|
// eslint-disable-next-line no-unused-private-class-members
|
|
@@ -946,8 +918,8 @@ export async function $onEmit(context) {
|
|
|
946
918
|
: "TypeSpec";
|
|
947
919
|
}
|
|
948
920
|
reference(targetDeclaration, pathUp, pathDown, commonScope) {
|
|
949
|
-
|
|
950
|
-
return super.reference(targetDeclaration, pathUp, pathDown, commonScope);
|
|
921
|
+
const resolved = resolveReferenceFromScopes(targetDeclaration, pathDown, pathUp);
|
|
922
|
+
return resolved ?? super.reference(targetDeclaration, pathUp, pathDown, commonScope);
|
|
951
923
|
}
|
|
952
924
|
scalarInstantiation(scalar, name) {
|
|
953
925
|
const scalarType = getCSharpTypeForScalar(this.emitter.getProgram(), scalar);
|
|
@@ -958,21 +930,8 @@ export async function $onEmit(context) {
|
|
|
958
930
|
return scalarType.getTypeReference(this.emitter.getContext().scope);
|
|
959
931
|
}
|
|
960
932
|
sourceFile(sourceFile) {
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
return libFile.emitted;
|
|
964
|
-
}
|
|
965
|
-
if (this.#emitMocks === "all") {
|
|
966
|
-
for (const helper of this.#mockHelpers) {
|
|
967
|
-
if (sourceFile === helper.source)
|
|
968
|
-
return helper.emitted;
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
if (this.#mockFiles.length > 0) {
|
|
972
|
-
for (const mock of this.#mockFiles) {
|
|
973
|
-
if (sourceFile === mock.source)
|
|
974
|
-
return mock.emitted;
|
|
975
|
-
}
|
|
933
|
+
if (sourceFile.meta.emitted) {
|
|
934
|
+
return sourceFile.meta.emitted;
|
|
976
935
|
}
|
|
977
936
|
const emittedSourceFile = {
|
|
978
937
|
path: sourceFile.path,
|
|
@@ -990,15 +949,26 @@ export async function $onEmit(context) {
|
|
|
990
949
|
}
|
|
991
950
|
#emitCodeContents(file) {
|
|
992
951
|
const contents = new StringBuilder();
|
|
952
|
+
contents.pushLiteralSegment(`${file.meta["nullable"] ? this.#generatedFileHeaderWithNullable : this.#generatedFileHeader}
|
|
953
|
+
|
|
954
|
+
${this.#emitUsings(file)}
|
|
955
|
+
`);
|
|
993
956
|
for (const decl of file.globalScope.declarations) {
|
|
994
957
|
contents.push(decl.value);
|
|
995
958
|
}
|
|
959
|
+
for (const child of file.globalScope.childScopes) {
|
|
960
|
+
if (child.declarations) {
|
|
961
|
+
for (const decl of child.declarations) {
|
|
962
|
+
contents.push(decl.value);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
996
966
|
return contents.segments.join("\n") + "\n";
|
|
997
967
|
}
|
|
998
968
|
#emitControllerContents(file) {
|
|
999
969
|
const namespace = file.meta.namespace;
|
|
1000
970
|
const contents = new StringBuilder();
|
|
1001
|
-
contents.push(`${this.#
|
|
971
|
+
contents.push(`${this.#generatedFileHeaderWithNullable}\n\n`);
|
|
1002
972
|
contents.push(code `${this.#emitUsings(file)}\n`);
|
|
1003
973
|
contents.push("\n");
|
|
1004
974
|
contents.push(`namespace ${namespace}.Controllers\n`);
|
|
@@ -1063,92 +1033,18 @@ export async function $onEmit(context) {
|
|
|
1063
1033
|
return params.reduce();
|
|
1064
1034
|
return "";
|
|
1065
1035
|
}
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
#getNonNullableTsType(union) {
|
|
1071
|
-
const types = [...union.variants.values()];
|
|
1072
|
-
const nulls = types.flatMap((v) => v.type).filter((t) => isNullType(t));
|
|
1073
|
-
const nonNulls = types.flatMap((v) => v.type).filter((t) => !isNullType(t));
|
|
1074
|
-
if (nonNulls.length === 1)
|
|
1075
|
-
return { type: nonNulls[0], nullable: nulls.length > 0 };
|
|
1076
|
-
return undefined;
|
|
1077
|
-
}
|
|
1078
|
-
#coalesceTypes(types) {
|
|
1079
|
-
const defaultValue = [
|
|
1080
|
-
new CSharpType({
|
|
1081
|
-
name: "object",
|
|
1082
|
-
namespace: "System",
|
|
1083
|
-
isValueType: false,
|
|
1084
|
-
}),
|
|
1085
|
-
true,
|
|
1086
|
-
];
|
|
1087
|
-
let current = undefined;
|
|
1088
|
-
let nullable = false;
|
|
1089
|
-
for (const type of types) {
|
|
1090
|
-
let candidate = undefined;
|
|
1091
|
-
switch (type.kind) {
|
|
1092
|
-
case "Boolean":
|
|
1093
|
-
candidate = new CSharpType({ name: "bool", namespace: "System", isValueType: true });
|
|
1094
|
-
break;
|
|
1095
|
-
case "StringTemplate":
|
|
1096
|
-
case "String":
|
|
1097
|
-
candidate = new CSharpType({ name: "string", namespace: "System", isValueType: false });
|
|
1098
|
-
break;
|
|
1099
|
-
case "Number":
|
|
1100
|
-
const stringValue = type.valueAsString;
|
|
1101
|
-
if (stringValue.includes(".") || stringValue.includes("e")) {
|
|
1102
|
-
candidate = new CSharpType({
|
|
1103
|
-
name: "double",
|
|
1104
|
-
namespace: "System",
|
|
1105
|
-
isValueType: true,
|
|
1106
|
-
});
|
|
1107
|
-
}
|
|
1108
|
-
else {
|
|
1109
|
-
candidate = new CSharpType({ name: "int", namespace: "System", isValueType: true });
|
|
1110
|
-
}
|
|
1111
|
-
break;
|
|
1112
|
-
case "Union":
|
|
1113
|
-
candidate = this.#coalesceUnionTypes(type);
|
|
1114
|
-
break;
|
|
1115
|
-
case "Scalar":
|
|
1116
|
-
candidate = getCSharpTypeForScalar(this.emitter.getProgram(), type);
|
|
1117
|
-
break;
|
|
1118
|
-
case "Intrinsic":
|
|
1119
|
-
if (isNullType(type)) {
|
|
1120
|
-
nullable = true;
|
|
1121
|
-
candidate = current;
|
|
1122
|
-
}
|
|
1123
|
-
else {
|
|
1124
|
-
return defaultValue;
|
|
1125
|
-
}
|
|
1126
|
-
break;
|
|
1127
|
-
default:
|
|
1128
|
-
return defaultValue;
|
|
1129
|
-
}
|
|
1130
|
-
current = current ?? candidate;
|
|
1131
|
-
if (current === undefined || (candidate !== undefined && !candidate.equals(current)))
|
|
1132
|
-
return defaultValue;
|
|
1133
|
-
}
|
|
1134
|
-
if (current !== undefined && nullable)
|
|
1135
|
-
current.isNullable = true;
|
|
1136
|
-
return current === undefined ? defaultValue : [current, false];
|
|
1137
|
-
}
|
|
1138
|
-
writeOutput(sourceFiles) {
|
|
1139
|
-
for (const source of this.#libraryFiles) {
|
|
1140
|
-
sourceFiles.push(source.source);
|
|
1141
|
-
}
|
|
1142
|
-
if (this.#emitMocks === "all") {
|
|
1143
|
-
for (const helper of this.#mockHelpers) {
|
|
1144
|
-
sourceFiles.push(helper.source);
|
|
1145
|
-
}
|
|
1036
|
+
async writeOutput(sourceFiles) {
|
|
1037
|
+
sourceFiles.push(...getSerializationSourceFiles(this.emitter).flatMap((l) => l.source));
|
|
1038
|
+
sourceFiles.push(...getProjectDocs(this.emitter, this.#useSwagger, this.#mockRegistrations).flatMap((l) => l.source));
|
|
1039
|
+
if (this.#emitMocks === "mocks-and-project-files" || this.#emitMocks === "mocks-only") {
|
|
1146
1040
|
if (this.#mockRegistrations.size > 0) {
|
|
1147
|
-
const mocks = getBusinessLogicImplementations(this.emitter, this.#mockRegistrations);
|
|
1148
|
-
this.#mockFiles.push(...mocks);
|
|
1041
|
+
const mocks = getBusinessLogicImplementations(this.emitter, this.#mockRegistrations, this.#useSwagger, this.#openapiPath);
|
|
1149
1042
|
sourceFiles.push(...mocks.flatMap((l) => l.source));
|
|
1150
1043
|
}
|
|
1151
1044
|
}
|
|
1045
|
+
async function shouldWrite(source, exists) {
|
|
1046
|
+
return (!source.meta.conditional || options.overwrite === true || !(await exists(source.path)));
|
|
1047
|
+
}
|
|
1152
1048
|
const emittedSourceFiles = [];
|
|
1153
1049
|
for (const source of sourceFiles) {
|
|
1154
1050
|
switch (this.#emitterOutputType) {
|
|
@@ -1159,51 +1055,22 @@ export async function $onEmit(context) {
|
|
|
1159
1055
|
// do nothing
|
|
1160
1056
|
break;
|
|
1161
1057
|
default:
|
|
1162
|
-
|
|
1058
|
+
if (await shouldWrite(source, this.#fileExists)) {
|
|
1059
|
+
emittedSourceFiles.push(source);
|
|
1060
|
+
}
|
|
1163
1061
|
break;
|
|
1164
1062
|
}
|
|
1165
1063
|
}
|
|
1166
1064
|
break;
|
|
1167
1065
|
default:
|
|
1168
|
-
|
|
1066
|
+
if (await shouldWrite(source, this.#fileExists)) {
|
|
1067
|
+
emittedSourceFiles.push(source);
|
|
1068
|
+
}
|
|
1169
1069
|
break;
|
|
1170
1070
|
}
|
|
1171
1071
|
}
|
|
1172
1072
|
return super.writeOutput(emittedSourceFiles);
|
|
1173
1073
|
}
|
|
1174
|
-
#getOrSetBaseNamespace(type) {
|
|
1175
|
-
if (this.#baseNamespace === undefined) {
|
|
1176
|
-
if (type.namespace !== undefined) {
|
|
1177
|
-
this.#baseNamespace = `${type.namespace
|
|
1178
|
-
? ensureCSharpIdentifier(this.emitter.getProgram(), type.namespace, getNamespaceFullName(type.namespace))
|
|
1179
|
-
: "TypeSpec"}.Service`;
|
|
1180
|
-
}
|
|
1181
|
-
else {
|
|
1182
|
-
this.#baseNamespace = "TypeSpec.Service";
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
return this.#baseNamespace;
|
|
1186
|
-
}
|
|
1187
|
-
}
|
|
1188
|
-
function isEmptyResponseModel(program, model) {
|
|
1189
|
-
if (model.kind !== "Model")
|
|
1190
|
-
return false;
|
|
1191
|
-
return model.properties.size === 1 && isStatusCode(program, [...model.properties.values()][0]);
|
|
1192
|
-
}
|
|
1193
|
-
function isContentTypeHeader(program, parameter) {
|
|
1194
|
-
return (isHeader(program, parameter) &&
|
|
1195
|
-
(parameter.name === "contentType" ||
|
|
1196
|
-
getHeaderFieldName(program, parameter) === "Content-type"));
|
|
1197
|
-
}
|
|
1198
|
-
function isValidParameter(program, parameter) {
|
|
1199
|
-
return (!isContentTypeHeader(program, parameter) &&
|
|
1200
|
-
(parameter.type.kind !== "Intrinsic" || parameter.type.name !== "never"));
|
|
1201
|
-
}
|
|
1202
|
-
/** Determine whether the given parameter is http metadata */
|
|
1203
|
-
function isHttpMetadata(program, property) {
|
|
1204
|
-
return (isPathParam(program, property) ||
|
|
1205
|
-
isHeader(program, property) ||
|
|
1206
|
-
isQueryParam(program, property));
|
|
1207
1074
|
}
|
|
1208
1075
|
function processNameSpace(program, target, service) {
|
|
1209
1076
|
if (!service)
|
|
@@ -1234,7 +1101,6 @@ export async function $onEmit(context) {
|
|
|
1234
1101
|
}
|
|
1235
1102
|
}
|
|
1236
1103
|
const iface = program.checker.createAndFinishType({
|
|
1237
|
-
node: undefined,
|
|
1238
1104
|
sourceInterfaces: [],
|
|
1239
1105
|
decorators: [],
|
|
1240
1106
|
operations: createRekeyableMap(nsOps),
|
|
@@ -1268,17 +1134,60 @@ export async function $onEmit(context) {
|
|
|
1268
1134
|
}
|
|
1269
1135
|
}
|
|
1270
1136
|
const emitter = createAssetEmitter(context.program, CSharpCodeEmitter, context);
|
|
1271
|
-
const ns = context.program.
|
|
1137
|
+
const ns = context.program.getGlobalNamespaceType();
|
|
1272
1138
|
const options = emitter.getOptions();
|
|
1273
1139
|
processNameSpace(context.program, ns);
|
|
1274
1140
|
if (!doNotEmit) {
|
|
1275
|
-
|
|
1141
|
+
const outputDir = options.emitterOutputDir;
|
|
1142
|
+
const generatedDir = path.join(outputDir, "generated");
|
|
1143
|
+
await ensureCleanDirectory(context.program, generatedDir);
|
|
1144
|
+
function normalizeSlashes(path) {
|
|
1145
|
+
return path.replaceAll("\\", "/");
|
|
1146
|
+
}
|
|
1147
|
+
async function getOpenApiPath() {
|
|
1148
|
+
if (options["openapi-path"])
|
|
1149
|
+
return options["openapi-path"];
|
|
1150
|
+
const openApiSettings = await getOpenApiConfig(context.program);
|
|
1151
|
+
const projectDir = resolvePath(context.program.projectRoot, outputDir);
|
|
1152
|
+
if (openApiSettings.outputDir) {
|
|
1153
|
+
const openApiPath = resolvePath(openApiSettings.outputDir, openApiSettings.fileName || "openapi.yaml");
|
|
1154
|
+
return normalizeSlashes(path.relative(projectDir, openApiPath));
|
|
1155
|
+
}
|
|
1156
|
+
if (openApiSettings.emitted) {
|
|
1157
|
+
const baseDir = context.program.compilerOptions.outputDir ||
|
|
1158
|
+
resolvePath(context.program.projectRoot, "tsp-output");
|
|
1159
|
+
const openApiPath = resolvePath(baseDir, "@typespec", "openapi3", openApiSettings.fileName || "openapi.yaml");
|
|
1160
|
+
return normalizeSlashes(path.relative(projectDir, openApiPath));
|
|
1161
|
+
}
|
|
1162
|
+
return "";
|
|
1163
|
+
}
|
|
1164
|
+
const openApiPath = await getOpenApiPath();
|
|
1165
|
+
const UseSwaggerUI = openApiPath !== "" && options["use-swaggerui"] === true;
|
|
1166
|
+
let httpPort;
|
|
1167
|
+
let httpsPort;
|
|
1168
|
+
if (options["emit-mocks"] !== "none") {
|
|
1169
|
+
getScaffoldingHelpers(emitter, UseSwaggerUI, openApiPath, true);
|
|
1170
|
+
}
|
|
1171
|
+
if (options["emit-mocks"] === "mocks-and-project-files") {
|
|
1172
|
+
httpPort = options["http-port"] || (await getFreePort(5000, 5999));
|
|
1173
|
+
httpsPort = options["https-port"] || (await getFreePort(7000, 7999));
|
|
1174
|
+
getProjectHelpers(emitter, options["project-name"] || "ServiceProject", options["use-swaggerui"] || false, httpPort, httpsPort);
|
|
1175
|
+
}
|
|
1276
1176
|
await emitter.writeOutput();
|
|
1177
|
+
const projectDir = normalizeSlashes(path.relative(process.cwd(), resolvePath(outputDir)));
|
|
1178
|
+
function trace(message) {
|
|
1179
|
+
context.program.trace("http-server-csharp", `hscs-msg: ${message}`);
|
|
1180
|
+
}
|
|
1181
|
+
trace(`Your project was successfully created at "${projectDir}"`);
|
|
1182
|
+
trace(`You can build and start the project using 'dotnet run --project "${projectDir}"'`);
|
|
1183
|
+
if (options["use-swaggerui"] === true && httpsPort) {
|
|
1184
|
+
trace(`You can browse the swagger UI to test your service using 'start https://localhost:${httpsPort}/swagger/' `);
|
|
1185
|
+
}
|
|
1277
1186
|
if (options["skip-format"] === undefined || options["skip-format"] === false) {
|
|
1278
1187
|
await execFile("dotnet", [
|
|
1279
1188
|
"format",
|
|
1280
1189
|
"whitespace",
|
|
1281
|
-
|
|
1190
|
+
outputDir,
|
|
1282
1191
|
"--include-generated",
|
|
1283
1192
|
"--folder",
|
|
1284
1193
|
]);
|