From 39da669b37babebd06d0dbef6ae261bf3379e23d Mon Sep 17 00:00:00 2001 From: Marius Muntean Date: Tue, 6 Aug 2024 21:54:49 +0200 Subject: [PATCH 1/5] Moved member-naming.ts to the c-sharp common package --- packages/plugins/c-sharp/c-sharp-common/src/index.ts | 1 + .../c-sharp/{c-sharp => c-sharp-common}/src/member-naming.ts | 2 +- packages/plugins/c-sharp/c-sharp/src/visitor.ts | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) rename packages/plugins/c-sharp/{c-sharp => c-sharp-common}/src/member-naming.ts (91%) diff --git a/packages/plugins/c-sharp/c-sharp-common/src/index.ts b/packages/plugins/c-sharp/c-sharp-common/src/index.ts index bc6f871f1..35be86323 100644 --- a/packages/plugins/c-sharp/c-sharp-common/src/index.ts +++ b/packages/plugins/c-sharp/c-sharp-common/src/index.ts @@ -3,3 +3,4 @@ export * from './scalars.js'; export * from './utils.js'; export * from './c-sharp-field-types.js'; export * from './keywords.js'; +export * from './member-naming.js'; diff --git a/packages/plugins/c-sharp/c-sharp/src/member-naming.ts b/packages/plugins/c-sharp/c-sharp-common/src/member-naming.ts similarity index 91% rename from packages/plugins/c-sharp/c-sharp/src/member-naming.ts rename to packages/plugins/c-sharp/c-sharp-common/src/member-naming.ts index 88754870a..590dd7267 100644 --- a/packages/plugins/c-sharp/c-sharp/src/member-naming.ts +++ b/packages/plugins/c-sharp/c-sharp-common/src/member-naming.ts @@ -1,6 +1,6 @@ import { camelCase, pascalCase } from 'change-case-all'; import { NameNode } from 'graphql'; -import { CSharpResolversPluginRawConfig } from './config'; +import { CSharpResolversPluginRawConfig } from '../../c-sharp/src/config'; type MemberNamingFunctionInput = string | NameNode; diff --git a/packages/plugins/c-sharp/c-sharp/src/visitor.ts b/packages/plugins/c-sharp/c-sharp/src/visitor.ts index 648150a20..9390ed5a6 100644 --- a/packages/plugins/c-sharp/c-sharp/src/visitor.ts +++ b/packages/plugins/c-sharp/c-sharp/src/visitor.ts @@ -23,7 +23,9 @@ import { CSharpFieldType, getListInnerTypeNode, getListTypeField, + getMemberNamingFunction, isValueType, + MemberNamingFn, transformComment, wrapFieldType, } from '@graphql-codegen/c-sharp-common'; @@ -42,7 +44,6 @@ import { JsonAttributesSource, JsonAttributesSourceConfiguration, } from './json-attributes.js'; -import { getMemberNamingFunction, MemberNamingFn } from './member-naming.js'; export interface CSharpResolverParsedConfig extends ParsedConfig { namespaceName: string; From 5734b90d209539f0d158c232215454c7d284d323 Mon Sep 17 00:00:00 2001 From: Marius Muntean Date: Tue, 6 Aug 2024 22:55:07 +0200 Subject: [PATCH 2/5] avoid raw plugin config repetition by defining a dedicated type for the naming convention --- .../c-sharp-common/src/member-naming.ts | 19 +++++++++++++++--- .../plugins/c-sharp/c-sharp/src/config.ts | 20 ++----------------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/packages/plugins/c-sharp/c-sharp-common/src/member-naming.ts b/packages/plugins/c-sharp/c-sharp-common/src/member-naming.ts index 590dd7267..5876efaff 100644 --- a/packages/plugins/c-sharp/c-sharp-common/src/member-naming.ts +++ b/packages/plugins/c-sharp/c-sharp-common/src/member-naming.ts @@ -1,12 +1,25 @@ import { camelCase, pascalCase } from 'change-case-all'; import { NameNode } from 'graphql'; -import { CSharpResolversPluginRawConfig } from '../../c-sharp/src/config'; -type MemberNamingFunctionInput = string | NameNode; +/** + * @description Configuration for member naming conventions. + */ +export type MemberNameConventionConfig = { + memberNameConvention?: 'camelCase' | 'pascalCase'; +}; +type MemberNamingFunctionInput = string | NameNode; +/** + * @description Type func signature of a function is responsible for transforming the name of a member (property, method) to a valid C# identifier. + */ export type MemberNamingFn = (nameOrNameNode: MemberNamingFunctionInput) => string; -export function getMemberNamingFunction(rawConfig: CSharpResolversPluginRawConfig): MemberNamingFn { +/** + * @description Get the member naming function based on the provided configuration. + * @param rawConfig Config to decide which concrete naming function to return. Fallback to camelCase if not provided. + * @returns + */ +export function getMemberNamingFunction(rawConfig: MemberNameConventionConfig): MemberNamingFn { switch (rawConfig.memberNameConvention) { case 'camelCase': return (input: MemberNamingFunctionInput) => diff --git a/packages/plugins/c-sharp/c-sharp/src/config.ts b/packages/plugins/c-sharp/c-sharp/src/config.ts index 893a39e54..b5d3c16c7 100644 --- a/packages/plugins/c-sharp/c-sharp/src/config.ts +++ b/packages/plugins/c-sharp/c-sharp/src/config.ts @@ -1,10 +1,11 @@ +import { MemberNameConventionConfig } from '@graphql-codegen/c-sharp-common'; import { EnumValuesMap, RawConfig } from '@graphql-codegen/visitor-plugin-common'; import { JsonAttributesSource } from './json-attributes.js'; /** * @description This plugin generates C# `class` identifier for your schema types. */ -export interface CSharpResolversPluginRawConfig extends RawConfig { +export interface CSharpResolversPluginRawConfig extends RawConfig, MemberNameConventionConfig { /** * @description Overrides the default value of enum values declared in your GraphQL schema. * @exampleMarkdown @@ -110,21 +111,4 @@ export interface CSharpResolversPluginRawConfig extends RawConfig { * ``` */ jsonAttributesSource?: JsonAttributesSource; - - /** - * @default camelCase - * Supported: camelCase, pascalCase - * @description Allows you to customize the naming convention for interface/class/record members. - * - * @exampleMarkdown - * ```yaml - * generates: - * src/main/c-sharp/my-org/my-app/MyTypes.cs: - * plugins: - * - c-sharp - * config: - * fieldNameConvention: pascalCase - * ``` - */ - memberNameConvention?: 'camelCase' | 'pascalCase'; } From 2f4d6d12c17b073ceaf834aad782d6ef75e3ffc5 Mon Sep 17 00:00:00 2001 From: Marius Muntean Date: Tue, 6 Aug 2024 23:29:25 +0200 Subject: [PATCH 3/5] Generate enums with pascal cased values when so configured --- .../c-sharp/c-sharp-operations/src/config.ts | 5 ++- .../c-sharp/c-sharp-operations/src/visitor.ts | 10 +++++- .../test/c-sharp-operations.spec.ts | 36 +++++++++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/packages/plugins/c-sharp/c-sharp-operations/src/config.ts b/packages/plugins/c-sharp/c-sharp-operations/src/config.ts index 2c715238d..968d5722c 100644 --- a/packages/plugins/c-sharp/c-sharp-operations/src/config.ts +++ b/packages/plugins/c-sharp/c-sharp-operations/src/config.ts @@ -1,9 +1,12 @@ +import { MemberNameConventionConfig } from '@graphql-codegen/c-sharp-common'; import { RawClientSideBasePluginConfig } from '@graphql-codegen/visitor-plugin-common'; /** * @description This plugin generates C# `class` based on your GraphQL operations. */ -export interface CSharpOperationsRawPluginConfig extends RawClientSideBasePluginConfig { +export interface CSharpOperationsRawPluginConfig + extends RawClientSideBasePluginConfig, + MemberNameConventionConfig { /** * @default GraphQLCodeGen * @description Allow you to customize the namespace name. diff --git a/packages/plugins/c-sharp/c-sharp-operations/src/visitor.ts b/packages/plugins/c-sharp/c-sharp-operations/src/visitor.ts index 776cb2311..bd33b4756 100644 --- a/packages/plugins/c-sharp/c-sharp-operations/src/visitor.ts +++ b/packages/plugins/c-sharp/c-sharp-operations/src/visitor.ts @@ -26,7 +26,9 @@ import { getListInnerTypeNode, getListTypeDepth, getListTypeField, + getMemberNamingFunction, isValueType, + MemberNamingFn, wrapFieldType, } from '@graphql-codegen/c-sharp-common'; import { getCachedDocumentNodeFromSchema, Types } from '@graphql-codegen/plugin-helpers'; @@ -56,6 +58,7 @@ export interface CSharpOperationsPluginConfig extends ClientSideBasePluginConfig mutationSuffix: string; subscriptionSuffix: string; typesafeOperation: boolean; + memberNamingFunction: MemberNamingFn; } export class CSharpOperationsVisitor extends ClientSideBaseVisitor< @@ -90,6 +93,7 @@ export class CSharpOperationsVisitor extends ClientSideBaseVisitor< subscriptionSuffix: rawConfig.subscriptionSuffix || defaultSuffix, scalars: buildScalarsFromConfig(schema, rawConfig, C_SHARP_SCALARS), typesafeOperation: rawConfig.typesafeOperation || false, + memberNamingFunction: getMemberNamingFunction(rawConfig), }, documents, ); @@ -614,7 +618,11 @@ ${this._getOperationMethod(node)} .access('public') .asKind('enum') .withName(convertSafeName(this.convertName(node.name))) - .withBlock(indentMultiline(node.values?.map(v => v.name.value).join(',\n'))).string; + .withBlock( + indentMultiline( + node.values?.map(v => this._parsedConfig.memberNamingFunction(v.name.value)).join(',\n'), + ), + ).string; return indentMultiline(enumDefinition, 2); } diff --git a/packages/plugins/c-sharp/c-sharp-operations/test/c-sharp-operations.spec.ts b/packages/plugins/c-sharp/c-sharp-operations/test/c-sharp-operations.spec.ts index 9adf8b058..ec81355af 100644 --- a/packages/plugins/c-sharp/c-sharp-operations/test/c-sharp-operations.spec.ts +++ b/packages/plugins/c-sharp/c-sharp-operations/test/c-sharp-operations.spec.ts @@ -1265,6 +1265,42 @@ describe('C# Operations', () => { }); }); + describe('MemberNamingConfig', () => { + it('Should generate enums with pascal case values', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + myQuery: MyEnum! + } + enum MyEnum { + Value1 + value2 + anotherValue + LastValue + } + `); + const operation = parse(/* GraphQL */ ` + query GetMyQuery { + myQuery + } + `); + + const result = (await plugin( + schema, + [{ location: '', document: operation }], + { typesafeOperation: true, memberNameConvention: 'pascalCase' }, + { outputFile: '' }, + )) as Types.ComplexPluginOutput; + expect(result.content).toBeSimilarStringTo(` + public enum MyEnum { + Value1, + Value2, + AnotherValue, + LastValue + } + `); + }); + }); + describe('Issues', () => { it('#4221 - suffix query mutation subscription', async () => { const schema = buildSchema(/* GraphQL */ ` From fe5035bc5995adcece74e84179fe4ba5d55c06f6 Mon Sep 17 00:00:00 2001 From: Marius Muntean Date: Thu, 8 Aug 2024 22:36:20 +0200 Subject: [PATCH 4/5] generating property names that respect the configured naming convention --- .../c-sharp/c-sharp-operations/src/visitor.ts | 20 ++++- .../test/c-sharp-operations.spec.ts | 74 +++++++++++++++++++ 2 files changed, 90 insertions(+), 4 deletions(-) diff --git a/packages/plugins/c-sharp/c-sharp-operations/src/visitor.ts b/packages/plugins/c-sharp/c-sharp-operations/src/visitor.ts index bd33b4756..f944dc28b 100644 --- a/packages/plugins/c-sharp/c-sharp-operations/src/visitor.ts +++ b/packages/plugins/c-sharp/c-sharp-operations/src/visitor.ts @@ -327,10 +327,13 @@ export class CSharpOperationsVisitor extends ClientSideBaseVisitor< responseType.listType, 'System.Collections.Generic.List', ); + const propertyName = convertSafeName( + this._parsedConfig.memberNamingFunction(node.name.value), + ); return indentMultiline( [ `[JsonProperty("${node.name.value}")]`, - `public ${responseTypeName} ${convertSafeName(node.name.value)} { get; set; }`, + `public ${responseTypeName} ${propertyName} { get; set; }`, ].join('\n') + '\n', ); } @@ -363,11 +366,14 @@ export class CSharpOperationsVisitor extends ClientSideBaseVisitor< }) .join('\n'), ).string; + const propertyName = convertSafeName( + this._parsedConfig.memberNamingFunction(node.name.value), + ); return indentMultiline( [ innerClassDefinition, `[JsonProperty("${node.name.value}")]`, - `public ${selectionTypeName} ${convertSafeName(node.name.value)} { get; set; }`, + `public ${selectionTypeName} ${propertyName} { get; set; }`, ].join('\n') + '\n', ); } @@ -416,10 +422,13 @@ export class CSharpOperationsVisitor extends ClientSideBaseVisitor< inputType.listType, 'System.Collections.Generic.List', ); + const propertyName = convertSafeName( + this._parsedConfig.memberNamingFunction(v.variable.name.value), + ); return indentMultiline( [ `[JsonProperty("${v.variable.name.value}")]`, - `public ${inputTypeName} ${convertSafeName(v.variable.name.value)} { get; set; }`, + `public ${inputTypeName} ${propertyName} { get; set; }`, ].join('\n') + '\n', ); }) @@ -595,10 +604,13 @@ ${this._getOperationMethod(node)} inputType.listType, 'System.Collections.Generic.List', ); + const propertyName = convertSafeName( + this._parsedConfig.memberNamingFunction(f.name.value), + ); return indentMultiline( [ `[JsonProperty("${f.name.value}")]`, - `public ${inputTypeName} ${convertSafeName(f.name.value)} { get; set; }`, + `public ${inputTypeName} ${propertyName} { get; set; }`, ].join('\n') + '\n', ); }) diff --git a/packages/plugins/c-sharp/c-sharp-operations/test/c-sharp-operations.spec.ts b/packages/plugins/c-sharp/c-sharp-operations/test/c-sharp-operations.spec.ts index ec81355af..4fd829977 100644 --- a/packages/plugins/c-sharp/c-sharp-operations/test/c-sharp-operations.spec.ts +++ b/packages/plugins/c-sharp/c-sharp-operations/test/c-sharp-operations.spec.ts @@ -1299,6 +1299,80 @@ describe('C# Operations', () => { } `); }); + + it('Should generate input classes with pascal case property names', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + myQuery(filter: MyQueryFilter): [MyData] + } + type MyData { + id: ID! + name: String! + } + input MyQueryFilter { + nameFilter: String! + } + `); + const operation = parse(/* GraphQL */ ` + query GetMyQuery { + myQuery + } + `); + + const result = (await plugin( + schema, + [{ location: '', document: operation }], + { typesafeOperation: true, memberNameConvention: 'pascalCase' }, + { outputFile: '' }, + )) as Types.ComplexPluginOutput; + expect(result.content).toBeSimilarStringTo(` + public class MyQueryFilter { + [JsonProperty("nameFilter")] + public string NameFilter { get; set; } + } + `); + }); + + it('Should generate output classes with pascal case property names', async () => { + const schema = buildSchema(/* GraphQL */ ` + type Query { + myQuery: [MyData] + } + type MyData { + id: ID! + firstName: String! + lastName: String! + } + `); + const operation = parse(/* GraphQL */ ` + query GetMyQuery { + myQuery { + id + firstName + lastName + } + } + `); + + const result = (await plugin( + schema, + [{ location: '', document: operation }], + { typesafeOperation: true, memberNameConvention: 'pascalCase' }, + { outputFile: '' }, + )) as Types.ComplexPluginOutput; + expect(result.content).toBeSimilarStringTo(` + public class MyDataSelection { + [JsonProperty("id")] + public string Id { get; set; } + + [JsonProperty("firstName")] + public string FirstName { get; set; } + + [JsonProperty("lastName")] + public string LastName { get; set; } + } + `); + }); }); describe('Issues', () => { From c096c635c27ef9e82ea893269030d8e6e47f1687 Mon Sep 17 00:00:00 2001 From: Marius Muntean Date: Thu, 8 Aug 2024 22:55:49 +0200 Subject: [PATCH 5/5] Added changeset --- .changeset/quick-insects-matter.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .changeset/quick-insects-matter.md diff --git a/.changeset/quick-insects-matter.md b/.changeset/quick-insects-matter.md new file mode 100644 index 000000000..aec87bfc0 --- /dev/null +++ b/.changeset/quick-insects-matter.md @@ -0,0 +1,9 @@ +--- +'@graphql-codegen/c-sharp-operations': minor +'@graphql-codegen/c-sharp-common': minor +'@graphql-codegen/c-sharp': minor +--- + +Added support for the new configuration option `memberNameConvention` to the c-sharp-operations +plugin. Now both C# plugins can generate C# code with standard member casing. The default is still +camel case, to avoid generating code that breaks user's existing code base.