1 | 'use strict';
|
2 |
|
3 | Object.defineProperty(exports, '__esModule', { value: true });
|
4 |
|
5 | const graphql = require('graphql');
|
6 | const visitorPluginCommon = require('@graphql-codegen/visitor-plugin-common');
|
7 | const changeCaseAll = require('change-case-all');
|
8 |
|
9 | const C_SHARP_SCALARS = {
|
10 | ID: 'string',
|
11 | String: 'string',
|
12 | Boolean: 'bool',
|
13 | Int: 'int',
|
14 | Float: 'float',
|
15 | Date: 'DateTime',
|
16 | };
|
17 |
|
18 |
|
19 | const csharpNativeValueTypes = [
|
20 | 'bool',
|
21 | 'byte',
|
22 | 'sbyte',
|
23 | 'char',
|
24 | 'decimal',
|
25 | 'double',
|
26 | 'float',
|
27 | 'int',
|
28 | 'uint',
|
29 | 'long',
|
30 | 'ulong',
|
31 | 'short',
|
32 | 'ushort',
|
33 | ];
|
34 |
|
35 | function transformComment(comment, indentLevel = 0) {
|
36 | if (!comment) {
|
37 | return '';
|
38 | }
|
39 | if (isStringValueNode(comment)) {
|
40 | comment = comment.value;
|
41 | }
|
42 | comment = comment.trimStart().split('*/').join('*\\/');
|
43 | let lines = comment.split('\n');
|
44 | lines = ['/// <summary>', ...lines.map(line => `/// ${line}`), '/// </summary>'];
|
45 | return lines
|
46 | .map(line => visitorPluginCommon.indent(line, indentLevel))
|
47 | .concat('')
|
48 | .join('\n');
|
49 | }
|
50 | function isStringValueNode(node) {
|
51 | return node && typeof node === 'object' && node.kind === graphql.Kind.STRING;
|
52 | }
|
53 | function isValueType(type) {
|
54 |
|
55 |
|
56 | return csharpNativeValueTypes.includes(type);
|
57 | }
|
58 | function getListTypeField(typeNode) {
|
59 | if (typeNode.kind === graphql.Kind.LIST_TYPE) {
|
60 | return {
|
61 | required: false,
|
62 | type: getListTypeField(typeNode.type),
|
63 | };
|
64 | }
|
65 | else if (typeNode.kind === graphql.Kind.NON_NULL_TYPE && typeNode.type.kind === graphql.Kind.LIST_TYPE) {
|
66 | return Object.assign(getListTypeField(typeNode.type), {
|
67 | required: true,
|
68 | });
|
69 | }
|
70 | else if (typeNode.kind === graphql.Kind.NON_NULL_TYPE) {
|
71 | return getListTypeField(typeNode.type);
|
72 | }
|
73 | else {
|
74 | return undefined;
|
75 | }
|
76 | }
|
77 | function getListInnerTypeNode(typeNode) {
|
78 | if (typeNode.kind === graphql.Kind.LIST_TYPE) {
|
79 | return getListInnerTypeNode(typeNode.type);
|
80 | }
|
81 | else if (typeNode.kind === graphql.Kind.NON_NULL_TYPE && typeNode.type.kind === graphql.Kind.LIST_TYPE) {
|
82 | return getListInnerTypeNode(typeNode.type);
|
83 | }
|
84 | else {
|
85 | return typeNode;
|
86 | }
|
87 | }
|
88 | function wrapFieldType(fieldType, listTypeField, listType = 'IEnumerable') {
|
89 | if (listTypeField) {
|
90 | const innerType = wrapFieldType(fieldType, listTypeField.type, listType);
|
91 | return `${listType}<${innerType}>`;
|
92 | }
|
93 | else {
|
94 | return fieldType.innerTypeName;
|
95 | }
|
96 | }
|
97 |
|
98 | class CSharpDeclarationBlock {
|
99 | constructor() {
|
100 | this._name = null;
|
101 | this._extendStr = [];
|
102 | this._implementsStr = [];
|
103 | this._kind = null;
|
104 | this._access = 'public';
|
105 | this._final = false;
|
106 | this._static = false;
|
107 | this._block = null;
|
108 | this._comment = null;
|
109 | this._nestedClasses = [];
|
110 | }
|
111 | nestedClass(nstCls) {
|
112 | this._nestedClasses.push(nstCls);
|
113 | return this;
|
114 | }
|
115 | access(access) {
|
116 | this._access = access;
|
117 | return this;
|
118 | }
|
119 | asKind(kind) {
|
120 | this._kind = kind;
|
121 | return this;
|
122 | }
|
123 | final() {
|
124 | this._final = true;
|
125 | return this;
|
126 | }
|
127 | static() {
|
128 | this._static = true;
|
129 | return this;
|
130 | }
|
131 | withComment(comment) {
|
132 | if (comment) {
|
133 | this._comment = transformComment(comment, 1);
|
134 | }
|
135 | return this;
|
136 | }
|
137 | withBlock(block) {
|
138 | this._block = block;
|
139 | return this;
|
140 | }
|
141 | extends(extendStr) {
|
142 | this._extendStr = extendStr;
|
143 | return this;
|
144 | }
|
145 | implements(implementsStr) {
|
146 | this._implementsStr = implementsStr;
|
147 | return this;
|
148 | }
|
149 | withName(name) {
|
150 | this._name = typeof name === 'object' ? name.value : name;
|
151 | return this;
|
152 | }
|
153 | get string() {
|
154 | let result = '';
|
155 | if (this._kind) {
|
156 | let name = '';
|
157 | if (this._name) {
|
158 | name = this._name;
|
159 | }
|
160 | if (this._kind === 'namespace') {
|
161 | result += `${this._kind} ${name} `;
|
162 | }
|
163 | else {
|
164 | let extendStr = '';
|
165 | let implementsStr = '';
|
166 | const final = this._final ? ' final' : '';
|
167 | const isStatic = this._static ? ' static' : '';
|
168 | if (this._extendStr.length > 0) {
|
169 | extendStr = ` : ${this._extendStr.join(', ')}`;
|
170 | }
|
171 | if (this._implementsStr.length > 0) {
|
172 | implementsStr = ` : ${this._implementsStr.join(', ')}`;
|
173 | }
|
174 | result += `${this._access}${isStatic}${final} ${this._kind} ${name}${extendStr}${implementsStr} `;
|
175 | }
|
176 | }
|
177 | const nestedClasses = this._nestedClasses.length
|
178 | ? this._nestedClasses.map(c => visitorPluginCommon.indentMultiline(c.string)).join('\n\n')
|
179 | : null;
|
180 | const before = '{';
|
181 | const after = '}';
|
182 | const block = [before, nestedClasses, this._block, after].filter(f => f).join('\n');
|
183 | result += block;
|
184 | return (this._comment ? this._comment : '') + result + '\n';
|
185 | }
|
186 | }
|
187 |
|
188 | class CSharpFieldType {
|
189 | constructor(fieldType) {
|
190 | Object.assign(this, fieldType);
|
191 | }
|
192 | get innerTypeName() {
|
193 | const nullable = this.baseType.valueType && !this.baseType.required ? '?' : '';
|
194 | return `${this.baseType.type}${nullable}`;
|
195 | }
|
196 | get isOuterTypeRequired() {
|
197 | return this.listType ? this.listType.required : this.baseType.required;
|
198 | }
|
199 | }
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 | const csharpKeywords = [
|
206 | 'abstract',
|
207 | 'as',
|
208 | 'base',
|
209 | 'bool',
|
210 | 'break',
|
211 | 'byte',
|
212 | 'case',
|
213 | 'catch',
|
214 | 'char',
|
215 | 'checked',
|
216 | 'class',
|
217 | 'const',
|
218 | 'continue',
|
219 | 'decimal',
|
220 | 'default',
|
221 | 'delegate',
|
222 | 'do',
|
223 | 'double',
|
224 | 'else',
|
225 | 'enum',
|
226 | 'event',
|
227 | 'explicit',
|
228 | 'extern',
|
229 | 'false',
|
230 | 'finally',
|
231 | 'fixed',
|
232 | 'float',
|
233 | 'for',
|
234 | 'foreach',
|
235 | 'goto',
|
236 | 'if',
|
237 | 'implicit',
|
238 | 'in',
|
239 | 'int',
|
240 | 'interface',
|
241 | 'internal',
|
242 | 'is',
|
243 | 'lock',
|
244 | 'long',
|
245 | 'namespace',
|
246 | 'new',
|
247 | 'null',
|
248 | 'object',
|
249 | 'operator',
|
250 | 'out',
|
251 | 'override',
|
252 | 'params',
|
253 | 'private',
|
254 | 'protected',
|
255 | 'public',
|
256 | 'readonly',
|
257 | 'record',
|
258 | 'ref',
|
259 | 'return',
|
260 | 'sbyte',
|
261 | 'sealed',
|
262 | 'short',
|
263 | 'sizeof',
|
264 | 'stackalloc',
|
265 | 'static',
|
266 | 'string',
|
267 | 'struct',
|
268 | 'switch',
|
269 | 'this',
|
270 | 'throw',
|
271 | 'true',
|
272 | 'try',
|
273 | 'typeof',
|
274 | 'uint',
|
275 | 'ulong',
|
276 | 'unchecked',
|
277 | 'unsafe',
|
278 | 'ushort',
|
279 | 'using',
|
280 | 'virtual',
|
281 | 'void',
|
282 | 'volatile',
|
283 | 'while',
|
284 | ];
|
285 |
|
286 | class CSharpResolversVisitor extends visitorPluginCommon.BaseVisitor {
|
287 | constructor(rawConfig, _schema) {
|
288 | super(rawConfig, {
|
289 | enumValues: rawConfig.enumValues || {},
|
290 | listType: rawConfig.listType || 'List',
|
291 | namespaceName: rawConfig.namespaceName || 'GraphQLCodeGen',
|
292 | className: rawConfig.className || 'Types',
|
293 | emitRecords: rawConfig.emitRecords || false,
|
294 | scalars: visitorPluginCommon.buildScalarsFromConfig(_schema, rawConfig, C_SHARP_SCALARS),
|
295 | });
|
296 | this._schema = _schema;
|
297 | this.keywords = new Set(csharpKeywords);
|
298 | }
|
299 | |
300 |
|
301 |
|
302 |
|
303 |
|
304 |
|
305 |
|
306 |
|
307 |
|
308 |
|
309 | convertSafeName(node) {
|
310 | const name = typeof node === 'string' ? node : node.value;
|
311 | return this.keywords.has(name) ? `@${name}` : name;
|
312 | }
|
313 | getImports() {
|
314 | const allImports = ['System', 'System.Collections.Generic', 'Newtonsoft.Json', 'GraphQL'];
|
315 | return allImports.map(i => `using ${i};`).join('\n') + '\n';
|
316 | }
|
317 | wrapWithNamespace(content) {
|
318 | return new CSharpDeclarationBlock()
|
319 | .asKind('namespace')
|
320 | .withName(this.config.namespaceName)
|
321 | .withBlock(visitorPluginCommon.indentMultiline(content)).string;
|
322 | }
|
323 | wrapWithClass(content) {
|
324 | return new CSharpDeclarationBlock()
|
325 | .access('public')
|
326 | .asKind('class')
|
327 | .withName(this.convertSafeName(this.config.className))
|
328 | .withBlock(visitorPluginCommon.indentMultiline(content)).string;
|
329 | }
|
330 | getEnumValue(enumName, enumOption) {
|
331 | if (this.config.enumValues[enumName] &&
|
332 | typeof this.config.enumValues[enumName] === 'object' &&
|
333 | this.config.enumValues[enumName][enumOption]) {
|
334 | return this.config.enumValues[enumName][enumOption];
|
335 | }
|
336 | return enumOption;
|
337 | }
|
338 | EnumValueDefinition(node) {
|
339 | return (enumName) => {
|
340 | const enumHeader = this.getFieldHeader(node);
|
341 | const enumOption = this.convertSafeName(node.name);
|
342 | return enumHeader + visitorPluginCommon.indent(this.getEnumValue(enumName, enumOption));
|
343 | };
|
344 | }
|
345 | EnumTypeDefinition(node) {
|
346 | const enumName = this.convertName(node.name);
|
347 | const enumValues = node.values.map(enumValue => enumValue(node.name.value)).join(',\n');
|
348 | const enumBlock = [enumValues].join('\n');
|
349 | return new CSharpDeclarationBlock()
|
350 | .access('public')
|
351 | .asKind('enum')
|
352 | .withComment(node.description)
|
353 | .withName(enumName)
|
354 | .withBlock(enumBlock).string;
|
355 | }
|
356 | getFieldHeader(node, fieldType) {
|
357 | var _a;
|
358 | const attributes = [];
|
359 | const commentText = transformComment((_a = node.description) === null || _a === void 0 ? void 0 : _a.value);
|
360 | const deprecationDirective = node.directives.find(v => { var _a; return ((_a = v.name) === null || _a === void 0 ? void 0 : _a.value) === 'deprecated'; });
|
361 | if (deprecationDirective) {
|
362 | const deprecationReason = this.getDeprecationReason(deprecationDirective);
|
363 | attributes.push(`[Obsolete("${deprecationReason}")]`);
|
364 | }
|
365 | if (node.kind === graphql.Kind.FIELD_DEFINITION) {
|
366 | attributes.push(`[JsonProperty("${node.name.value}")]`);
|
367 | }
|
368 | if (node.kind === graphql.Kind.INPUT_VALUE_DEFINITION && fieldType.isOuterTypeRequired) {
|
369 | attributes.push(`[JsonRequired]`);
|
370 | }
|
371 | if (commentText || attributes.length > 0) {
|
372 | const summary = commentText ? visitorPluginCommon.indentMultiline(commentText.trimRight()) + '\n' : '';
|
373 | const attributeLines = attributes.length > 0
|
374 | ? attributes
|
375 | .map(attr => visitorPluginCommon.indent(attr))
|
376 | .concat('')
|
377 | .join('\n')
|
378 | : '';
|
379 | return summary + attributeLines;
|
380 | }
|
381 | return '';
|
382 | }
|
383 | getDeprecationReason(directive) {
|
384 | if (directive.name.value !== 'deprecated') {
|
385 | return '';
|
386 | }
|
387 | const hasArguments = directive.arguments.length > 0;
|
388 | let reason = 'Field no longer supported';
|
389 | if (hasArguments && directive.arguments[0].value.kind === graphql.Kind.STRING) {
|
390 | reason = directive.arguments[0].value.value;
|
391 | }
|
392 | return reason;
|
393 | }
|
394 | resolveInputFieldType(typeNode, hasDefaultValue = false) {
|
395 | const innerType = visitorPluginCommon.getBaseTypeNode(typeNode);
|
396 | const schemaType = this._schema.getType(innerType.name.value);
|
397 | const listType = getListTypeField(typeNode);
|
398 | const required = getListInnerTypeNode(typeNode).kind === graphql.Kind.NON_NULL_TYPE;
|
399 | let result = null;
|
400 | if (graphql.isScalarType(schemaType)) {
|
401 | if (this.scalars[schemaType.name]) {
|
402 | const baseType = this.scalars[schemaType.name];
|
403 | result = new CSharpFieldType({
|
404 | baseType: {
|
405 | type: baseType,
|
406 | required,
|
407 | valueType: isValueType(baseType),
|
408 | },
|
409 | listType,
|
410 | });
|
411 | }
|
412 | else {
|
413 | result = new CSharpFieldType({
|
414 | baseType: {
|
415 | type: 'object',
|
416 | required,
|
417 | valueType: false,
|
418 | },
|
419 | listType,
|
420 | });
|
421 | }
|
422 | }
|
423 | else if (graphql.isInputObjectType(schemaType)) {
|
424 | result = new CSharpFieldType({
|
425 | baseType: {
|
426 | type: `${this.convertName(schemaType.name)}`,
|
427 | required,
|
428 | valueType: false,
|
429 | },
|
430 | listType,
|
431 | });
|
432 | }
|
433 | else if (graphql.isEnumType(schemaType)) {
|
434 | result = new CSharpFieldType({
|
435 | baseType: {
|
436 | type: this.convertName(schemaType.name),
|
437 | required,
|
438 | valueType: true,
|
439 | },
|
440 | listType,
|
441 | });
|
442 | }
|
443 | else {
|
444 | result = new CSharpFieldType({
|
445 | baseType: {
|
446 | type: `${schemaType.name}`,
|
447 | required,
|
448 | valueType: false,
|
449 | },
|
450 | listType,
|
451 | });
|
452 | }
|
453 | if (hasDefaultValue) {
|
454 |
|
455 | (result.listType || result.baseType).required = false;
|
456 | }
|
457 | return result;
|
458 | }
|
459 | buildRecord(name, description, inputValueArray, interfaces) {
|
460 | const classSummary = transformComment(description === null || description === void 0 ? void 0 : description.value);
|
461 | const interfaceImpl = interfaces && interfaces.length > 0 ? ` : ${interfaces.map(ntn => ntn.name.value).join(', ')}` : '';
|
462 | const recordMembers = inputValueArray
|
463 | .map(arg => {
|
464 | const fieldType = this.resolveInputFieldType(arg.type);
|
465 | const fieldHeader = this.getFieldHeader(arg, fieldType);
|
466 | const fieldName = this.convertSafeName(changeCaseAll.pascalCase(this.convertName(arg.name)));
|
467 | const csharpFieldType = wrapFieldType(fieldType, fieldType.listType, this.config.listType);
|
468 | return fieldHeader + visitorPluginCommon.indent(`public ${csharpFieldType} ${fieldName} { get; init; } = ${fieldName};`);
|
469 | })
|
470 | .join('\n\n');
|
471 | const recordInitializer = inputValueArray
|
472 | .map(arg => {
|
473 | const fieldType = this.resolveInputFieldType(arg.type);
|
474 | const fieldName = this.convertSafeName(changeCaseAll.pascalCase(this.convertName(arg.name)));
|
475 | const csharpFieldType = wrapFieldType(fieldType, fieldType.listType, this.config.listType);
|
476 | return `${csharpFieldType} ${fieldName}`;
|
477 | })
|
478 | .join(', ');
|
479 | return `
|
480 | #region ${name}
|
481 | ${classSummary}public record ${this.convertSafeName(name)}(${recordInitializer})${interfaceImpl} {
|
482 | #region members
|
483 | ${recordMembers}
|
484 | #endregion
|
485 | }
|
486 | #endregion`;
|
487 | }
|
488 | buildClass(name, description, inputValueArray, interfaces) {
|
489 | const classSummary = transformComment(description === null || description === void 0 ? void 0 : description.value);
|
490 | const interfaceImpl = interfaces && interfaces.length > 0 ? ` : ${interfaces.map(ntn => ntn.name.value).join(', ')}` : '';
|
491 | const classMembers = inputValueArray
|
492 | .map(arg => {
|
493 | const fieldType = this.resolveInputFieldType(arg.type);
|
494 | const fieldHeader = this.getFieldHeader(arg, fieldType);
|
495 | const fieldName = this.convertSafeName(arg.name);
|
496 | const csharpFieldType = wrapFieldType(fieldType, fieldType.listType, this.config.listType);
|
497 | return fieldHeader + visitorPluginCommon.indent(`public ${csharpFieldType} ${fieldName} { get; set; }`);
|
498 | })
|
499 | .join('\n\n');
|
500 | return `
|
501 | #region ${name}
|
502 | ${classSummary}public class ${this.convertSafeName(name)}${interfaceImpl} {
|
503 | #region members
|
504 | ${classMembers}
|
505 | #endregion
|
506 | }
|
507 | #endregion`;
|
508 | }
|
509 | buildInterface(name, description, inputValueArray) {
|
510 | const classSummary = transformComment(description === null || description === void 0 ? void 0 : description.value);
|
511 | const classMembers = inputValueArray
|
512 | .map(arg => {
|
513 | const fieldType = this.resolveInputFieldType(arg.type);
|
514 | const fieldHeader = this.getFieldHeader(arg, fieldType);
|
515 | let fieldName;
|
516 | let getterSetter;
|
517 | if (this.config.emitRecords) {
|
518 |
|
519 | fieldName = this.convertSafeName(changeCaseAll.pascalCase(this.convertName(arg.name)));
|
520 | getterSetter = '{ get; }';
|
521 | }
|
522 | else {
|
523 |
|
524 | fieldName = this.convertSafeName(arg.name);
|
525 | getterSetter = '{ get; set; }';
|
526 | }
|
527 | const csharpFieldType = wrapFieldType(fieldType, fieldType.listType, this.config.listType);
|
528 | return fieldHeader + visitorPluginCommon.indent(`public ${csharpFieldType} ${fieldName} ${getterSetter}`);
|
529 | })
|
530 | .join('\n\n');
|
531 | return `
|
532 | ${classSummary}public interface ${this.convertSafeName(name)} {
|
533 | ${classMembers}
|
534 | }`;
|
535 | }
|
536 | buildInputTransformer(name, description, inputValueArray) {
|
537 | const classSummary = transformComment(description === null || description === void 0 ? void 0 : description.value);
|
538 | const classMembers = inputValueArray
|
539 | .map(arg => {
|
540 | const fieldType = this.resolveInputFieldType(arg.type, !!arg.defaultValue);
|
541 | const fieldHeader = this.getFieldHeader(arg, fieldType);
|
542 | const fieldName = this.convertSafeName(arg.name);
|
543 | const csharpFieldType = wrapFieldType(fieldType, fieldType.listType, this.config.listType);
|
544 | return fieldHeader + visitorPluginCommon.indent(`public ${csharpFieldType} ${fieldName} { get; set; }`);
|
545 | })
|
546 | .join('\n\n');
|
547 | return `
|
548 | #region ${name}
|
549 | ${classSummary}public class ${this.convertSafeName(name)} {
|
550 | #region members
|
551 | ${classMembers}
|
552 | #endregion
|
553 |
|
554 | #region methods
|
555 | public dynamic GetInputObject()
|
556 | {
|
557 | IDictionary<string, object> d = new System.Dynamic.ExpandoObject();
|
558 |
|
559 | var properties = GetType().GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
|
560 | foreach (var propertyInfo in properties)
|
561 | {
|
562 | var value = propertyInfo.GetValue(this);
|
563 | var defaultValue = propertyInfo.PropertyType.IsValueType ? Activator.CreateInstance(propertyInfo.PropertyType) : null;
|
564 |
|
565 | var requiredProp = propertyInfo.GetCustomAttributes(typeof(JsonRequiredAttribute), false).Length > 0;
|
566 | if (requiredProp || value != defaultValue)
|
567 | {
|
568 | d[propertyInfo.Name] = value;
|
569 | }
|
570 | }
|
571 | return d;
|
572 | }
|
573 | #endregion
|
574 | }
|
575 | #endregion`;
|
576 | }
|
577 | InputObjectTypeDefinition(node) {
|
578 | const name = `${this.convertName(node)}`;
|
579 | return this.buildInputTransformer(name, node.description, node.fields);
|
580 | }
|
581 | ObjectTypeDefinition(node) {
|
582 | if (this.config.emitRecords) {
|
583 | return this.buildRecord(node.name.value, node.description, node.fields, node.interfaces);
|
584 | }
|
585 | return this.buildClass(node.name.value, node.description, node.fields, node.interfaces);
|
586 | }
|
587 | InterfaceTypeDefinition(node) {
|
588 | return this.buildInterface(node.name.value, node.description, node.fields);
|
589 | }
|
590 | }
|
591 |
|
592 | const plugin = async (schema, documents, config) => {
|
593 | const visitor = new CSharpResolversVisitor(config, schema);
|
594 | const printedSchema = graphql.printSchema(schema);
|
595 | const astNode = graphql.parse(printedSchema);
|
596 | const visitorResult = graphql.visit(astNode, { leave: visitor });
|
597 | const imports = visitor.getImports();
|
598 | const blockContent = visitorResult.definitions.filter(d => typeof d === 'string').join('\n');
|
599 | const wrappedBlockContent = visitor.wrapWithClass(blockContent);
|
600 | const wrappedContent = visitor.wrapWithNamespace(wrappedBlockContent);
|
601 | return [imports, wrappedContent].join('\n');
|
602 | };
|
603 |
|
604 | exports.plugin = plugin;
|
605 |
|