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. 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 52% 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..5876efaff 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,12 +1,25 @@ import { camelCase, pascalCase } from 'change-case-all'; import { NameNode } from 'graphql'; -import { CSharpResolversPluginRawConfig } from './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-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..f944dc28b 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, ); @@ -323,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', ); } @@ -359,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', ); } @@ -412,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', ); }) @@ -591,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', ); }) @@ -614,7 +630,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..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 @@ -1265,6 +1265,116 @@ 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 + } + `); + }); + + 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', () => { it('#4221 - suffix query mutation subscription', async () => { const schema = buildSchema(/* GraphQL */ ` 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'; } 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;