Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/c sharp operations member name casing #816

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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