dotansimha / graphql-code-generator

A tool for generating code based on a GraphQL schema and GraphQL operations (query/mutation/subscription), with flexible support for custom plugins.
https://the-guild.dev/graphql/codegen/
MIT License
10.85k stars 1.33k forks source link

Duplicate identifier on Typescript type generation due to _ prefix #6925

Closed majdi closed 2 years ago

majdi commented 3 years ago

Describe the bug On generating GraphQL schema (using Typescript + Typescript-Operations plugins), duplicate fields are generated: TS2300: Duplicate identifier error

To Reproduce Having a graphql endpoint and then run the command yarn graphql-codegen --config codegen.yml -r dotenv/config (or using npm)

The bug happen when a GraphQL schema has key like _createdAt_ASC and createdAt_ASC, the _ is ignore and the generated type definition create two createdAt_ASC

  1. My GraphQL schema:
_createdAt_ASC
_createdAt_DESC
createdAt_ASC
createdAt_DESC
id_ASC
id_DESC
_firstPublishedAt_ASC
_firstPublishedAt_DESC
_publicationScheduledAt_ASC
_publicationScheduledAt_DESC
_unpublishingScheduledAt_ASC
_unpublishingScheduledAt_DESC
_publishedAt_ASC
_publishedAt_DESC
_status_ASC
_status_DESC
_updatedAt_ASC
_updatedAt_DESC
updatedAt_ASC
updatedAt_DESC
_isValid_ASC
_isValid_DESC
title_ASC
title_DESC
  1. My codegen.yml config file:
overwrite: true

schema:
  - '${GRAPHQL_API_ENDPOINT}':
      headers:
        Authorization: 'Bearer ${GRAPHQL_API_TOKEN}'

documents: core/queries/**/*.ts
generates:
  core/queries/schema.ts:
    plugins:
      - 'typescript'
      - 'typescript-operations'
  1. My schema output:
    export enum ArticleModelOrderBy {
    CreatedAtAsc = '_createdAt_ASC',
    CreatedAtDesc = '_createdAt_DESC',
    FirstPublishedAtAsc = '_firstPublishedAt_ASC',
    FirstPublishedAtDesc = '_firstPublishedAt_DESC',
    IsValidAsc = '_isValid_ASC',
    IsValidDesc = '_isValid_DESC',
    PublicationScheduledAtAsc = '_publicationScheduledAt_ASC',
    PublicationScheduledAtDesc = '_publicationScheduledAt_DESC',
    PublishedAtAsc = '_publishedAt_ASC',
    PublishedAtDesc = '_publishedAt_DESC',
    StatusAsc = '_status_ASC',
    StatusDesc = '_status_DESC',
    UnpublishingScheduledAtAsc = '_unpublishingScheduledAt_ASC',
    UnpublishingScheduledAtDesc = '_unpublishingScheduledAt_DESC',
    UpdatedAtAsc = '_updatedAt_ASC', 
    UpdatedAtDesc = '_updatedAt_DESC',
    CreatedAtAsc = 'createdAt_ASC', // duplicate
    CreatedAtDesc = 'createdAt_DESC',  // duplicate
    IdAsc = 'id_ASC',
    IdDesc = 'id_DESC',
    TitleAsc = 'title_ASC',
    TitleDesc = 'title_DESC',
    UpdatedAtAsc = 'updatedAt_ASC',  // duplicate
    UpdatedAtDesc = 'updatedAt_DESC'  // duplicate
    }

Expected behavior

Environment: Darwin Majdis-MacBook-Pro.local 20.6.0 Darwin Kernel Version 20.6.0: Mon Aug 30 06:12:21 PDT 2021; root:xnu-7195.141.6~3/RELEASE_X86_64 x86_64

Additional context

Thanks ✌️

Urigo commented 3 years ago

Hi @majdi and thank you for the report!

Sorry but I'm not adding a lot here but just labeling it according to our new Contribution Guide and issue flow.

It seems already got into stage 0.

Now in order to advance to stage 1 we'll need a simple reproduction, maybe in code-sandbox?

Later to progress to stage 2, a failing test would be needed, would be great if someone could help progress the issues through the stages.

I think here maybe you could skip directly to stage 2 and try to create a failing test?

Thank you and sorry that this comment is not a complete solution (yet).

ristomatti commented 2 years ago

Just chiming in that I ran into this exact same issue with DatoCMS schema. The output looks pretty much identical.

ristomatti commented 2 years ago

Is this perhaps related https://github.com/dotansimha/graphql-code-generator/issues/605? It would seem the underscores are handled fine with types but not enums:

export type PageModelFilter = {
  OR?: InputMaybe<Array<InputMaybe<PageModelFilter>>>;
  _createdAt?: InputMaybe<CreatedAtFilter>;
  _firstPublishedAt?: InputMaybe<PublishedAtFilter>;
  _isValid?: InputMaybe<BooleanFilter>;
  _publicationScheduledAt?: InputMaybe<PublishedAtFilter>;
  _publishedAt?: InputMaybe<PublishedAtFilter>;
  _status?: InputMaybe<StatusFilter>;
  _unpublishingScheduledAt?: InputMaybe<PublishedAtFilter>;
  _updatedAt?: InputMaybe<UpdatedAtFilter>;
  createdAt?: InputMaybe<CreatedAtFilter>;
  id?: InputMaybe<ItemIdFilter>;
  pageTitle?: InputMaybe<StringFilter>;
  title?: InputMaybe<StringFilter>;
  updatedAt?: InputMaybe<UpdatedAtFilter>;
};

export enum PageModelOrderBy {
  CreatedAtAsc = '_createdAt_ASC', // duplicate
  CreatedAtDesc = '_createdAt_DESC', // duplicate
  FirstPublishedAtAsc = '_firstPublishedAt_ASC',
  FirstPublishedAtDesc = '_firstPublishedAt_DESC',
  IsValidAsc = '_isValid_ASC',
  IsValidDesc = '_isValid_DESC',
  PublicationScheduledAtAsc = '_publicationScheduledAt_ASC',
  PublicationScheduledAtDesc = '_publicationScheduledAt_DESC',
  PublishedAtAsc = '_publishedAt_ASC',
  PublishedAtDesc = '_publishedAt_DESC',
  StatusAsc = '_status_ASC',
  StatusDesc = '_status_DESC',
  UnpublishingScheduledAtAsc = '_unpublishingScheduledAt_ASC',
  UnpublishingScheduledAtDesc = '_unpublishingScheduledAt_DESC',
  UpdatedAtAsc = '_updatedAt_ASC', // duplicate
  UpdatedAtDesc = '_updatedAt_DESC', // duplicate
  CreatedAtAsc = 'createdAt_ASC', // duplicate
  CreatedAtDesc = 'createdAt_DESC', // duplicate
  IdAsc = 'id_ASC',
  IdDesc = 'id_DESC',
  PageTitleAsc = 'pageTitle_ASC',
  PageTitleDesc = 'pageTitle_DESC',
  TitleAsc = 'title_ASC',
  TitleDesc = 'title_DESC',
  UpdatedAtAsc = 'updatedAt_ASC', // duplicate
  UpdatedAtDesc = 'updatedAt_DESC', // duplicate
}

This also makes me wonder why Dato's schema has these with and without underscore :thinking:? I'm just getting into the whole thing so don't know for sure but I'd bet the values are same.

ristomatti commented 2 years ago

I played around with the different naming conventions. From the ones I tried, this issue occurs with pascalCase and camelCase but not with titleCase, upperCase or upperCaseFirst. For this particular use case using upperCaseFirst only for enums gives a good enough workaround for me.

Configuration:

generates:
  ./models/datocms/index.ts:
    plugins:
      - typescript
    config:
      namingConvention:
        enumValues: change-case-all#upperCaseFirst

Output:

export enum PageModelOrderBy {
  _createdAt_ASC = '_createdAt_ASC',
  _createdAt_DESC = '_createdAt_DESC',
  _firstPublishedAt_ASC = '_firstPublishedAt_ASC',
  _firstPublishedAt_DESC = '_firstPublishedAt_DESC',
  _isValid_ASC = '_isValid_ASC',
  _isValid_DESC = '_isValid_DESC',
  _publicationScheduledAt_ASC = '_publicationScheduledAt_ASC',
  _publicationScheduledAt_DESC = '_publicationScheduledAt_DESC',
  _publishedAt_ASC = '_publishedAt_ASC',
  _publishedAt_DESC = '_publishedAt_DESC',
  _status_ASC = '_status_ASC',
  _status_DESC = '_status_DESC',
  _unpublishingScheduledAt_ASC = '_unpublishingScheduledAt_ASC',
  _unpublishingScheduledAt_DESC = '_unpublishingScheduledAt_DESC',
  _updatedAt_ASC = '_updatedAt_ASC',
  _updatedAt_DESC = '_updatedAt_DESC',
  CreatedAt_ASC = 'createdAt_ASC',
  CreatedAt_DESC = 'createdAt_DESC',
  Id_ASC = 'id_ASC',
  Id_DESC = 'id_DESC',
  PageTitle_ASC = 'pageTitle_ASC',
  PageTitle_DESC = 'pageTitle_DESC',
  Title_ASC = 'title_ASC',
  Title_DESC = 'title_DESC',
  UpdatedAt_ASC = 'updatedAt_ASC',
  UpdatedAt_DESC = 'updatedAt_DESC'
}

Aside: I noticed some other unexpected behaviors. For example transformUnderscores did not seem to do anything with some of the naming conventions. Another one was that with this config:

    config:
      namingConvention:
        enumValues: change-case-all#titleCase

...enum names remained untouched but underscores appeared to some type names:

/** The query root for this schema */
export type Query_SiteArgs = {
  locale?: InputMaybe<SiteLocale>;
};

/** The query root for this schema */
export type QueryAllPagesArgs = {
  filter?: InputMaybe<PageModelFilter>;
  first?: InputMaybe<Scalars['IntType']>;
  locale?: InputMaybe<SiteLocale>;
  orderBy?: InputMaybe<Array<InputMaybe<PageModelOrderBy>>>;
  skip?: InputMaybe<Scalars['IntType']>;
};
majdi commented 2 years ago

Thanks for the follow-up @ristomatti. I changed the tooling since the issue but yes I don't understand too the need to have fields with and without underscore 🥲

dotansimha commented 2 years ago

Closing. if this is still relevant for someone else - please provide an actual reproduction in a sandbox and we'll try to address it. From a quick look it seems like tweaking nameingConvenions should solve this issue.

cjpete commented 2 years ago

I had the same issue, and solved this with a slight tweak to the suggested workaround in the graphql codegen docs. Using a custom naming function but handling the leading underscores directly.

https://www.the-guild.dev/graphql/codegen/docs/config-reference/naming-convention#providing-your-own-naming-function

const {pascalCase} = require('change-case-all');

function fixedTitleCase(str) {
  const result = pascalCase(str);

  if (!result) {
    return str;
  }

  // if there is a leading underscore but it's not in the converted string, add it
  if (str.indexOf('_') === 0 && result.substring(0, 1) !== '_') {
    return `_${result}`;
  }
  return result;
}

module.exports = {
  titleCase: fixedTitleCase,
};
stefanoverna commented 2 years ago

In DatoCMS createdAt is a legacy field that will be soon removed, while _createdAt is the new name. They have exactly the same meaning.

Thanks @cjpete, I'll add a complete example here that solves the issue:

https://github.com/Tonel/typescript-type-generation-graphql-example/pull/1/files

ronaldevers commented 8 months ago

I'm still running in to this issue with e.g. createdAt_ASC and _createdAt_ASC both mapping to CreatedAtAsc. I have solved it by using the keep naming convention. No custom naming conventions needed.

generates:
  graphql/generated.ts:
    config:
      namingConvention: keep

https://the-guild.dev/graphql/codegen/docs/config-reference/naming-convention#keep-names-as-is