Skip to content

Commit

Permalink
Fix/c sharp operations member name casing (#816)
Browse files Browse the repository at this point in the history
* Moved member-naming.ts to the c-sharp common package

* avoid raw plugin config repetition by defining a dedicated type for the naming convention

* Generate enums with pascal cased values when so configured

* generating property names that respect the configured naming convention

* Added changeset
  • Loading branch information
mariusmuntean committed Aug 9, 2024
1 parent a563726 commit b1ec118
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 28 deletions.
9 changes: 9 additions & 0 deletions .changeset/quick-insects-matter.md
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions packages/plugins/c-sharp/c-sharp-common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Original file line number Diff line number Diff line change
@@ -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) =>
Expand Down
5 changes: 4 additions & 1 deletion packages/plugins/c-sharp/c-sharp-operations/src/config.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
30 changes: 25 additions & 5 deletions packages/plugins/c-sharp/c-sharp-operations/src/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -56,6 +58,7 @@ export interface CSharpOperationsPluginConfig extends ClientSideBasePluginConfig
mutationSuffix: string;
subscriptionSuffix: string;
typesafeOperation: boolean;
memberNamingFunction: MemberNamingFn;
}

export class CSharpOperationsVisitor extends ClientSideBaseVisitor<
Expand Down Expand Up @@ -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,
);
Expand Down Expand Up @@ -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',
);
}
Expand Down Expand Up @@ -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',
);
}
Expand Down Expand Up @@ -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',
);
})
Expand Down Expand Up @@ -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',
);
})
Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 */ `
Expand Down
20 changes: 2 additions & 18 deletions packages/plugins/c-sharp/c-sharp/src/config.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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';
}
3 changes: 2 additions & 1 deletion packages/plugins/c-sharp/c-sharp/src/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ import {
CSharpFieldType,
getListInnerTypeNode,
getListTypeField,
getMemberNamingFunction,
isValueType,
MemberNamingFn,
transformComment,
wrapFieldType,
} from '@graphql-codegen/c-sharp-common';
Expand All @@ -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;
Expand Down

0 comments on commit b1ec118

Please sign in to comment.