diff --git a/.changeset/brown-things-jump.md b/.changeset/brown-things-jump.md new file mode 100644 index 00000000000..115b47b37f1 --- /dev/null +++ b/.changeset/brown-things-jump.md @@ -0,0 +1,27 @@ +--- +'@graphql-codegen/visitor-plugin-common': major +'@graphql-codegen/typescript-operations': major +'@graphql-codegen/typescript': major +'@graphql-codegen/typescript-resolvers': major +--- + +BREAKING CHANGE: visitors' config option are moved based on their use case + +- addTypename/skipTypename: is only a types-visitor concern. This is moved to types-visitor from base-visitor +- nonOptionalTypename: is a documents-visitor and types-visitor concern. Moved from base-visitor there +- extractAllFieldsToTypes: is a documents-visitor concern. Moved from base-visitor there +- enumPrefix and enumSuffix: need to be in base-visitor as all 3 types of visitors need this to correctly sync the enum type names. This is moved to base visitor +- ignoreEnumValuesFromSchema: is a documents-visitor and types-visitor concern. Moved from base-visitor there. +- globalNamespace: is a documents-visitor concern. Moved from base-visitor there + +Refactors + +- documents-visitor no longer extends types-visitor _option types_ as they have two distinct usages now. The types now extend base-visitor types. This is now consistent with documents-visitor extending base-visitor +- Classes now handle config parsing and types at the same level e.g. if typescript-operations plugin parses configOne, then the types for configOne must be in that class, rather than in base-documents-visitor + +Note: These visitors are rolled up into one type for simplicity + +- base-visitor: includes `base-visitor` +- documents-visitor: includes `base-documents-visitor` and `typescript-operations` visitor +- types-visitor: includes `base-types-visitor` and `typescript` visitor +- resolvers-visitor: includes `base-resolvers-visitor` and `typescript-resolvers` visitor diff --git a/packages/plugins/other/visitor-plugin-common/src/base-documents-visitor.ts b/packages/plugins/other/visitor-plugin-common/src/base-documents-visitor.ts index 77045df5c9e..e0c512dc7c4 100644 --- a/packages/plugins/other/visitor-plugin-common/src/base-documents-visitor.ts +++ b/packages/plugins/other/visitor-plugin-common/src/base-documents-visitor.ts @@ -7,35 +7,23 @@ import { OperationTypeNode, VariableDefinitionNode, } from 'graphql'; -import { ParsedTypesConfig, RawTypesConfig } from './base-types-visitor.js'; -import { BaseVisitor } from './base-visitor.js'; +import { BaseVisitor, type RawConfig, type ParsedConfig } from './base-visitor.js'; import { DEFAULT_SCALARS } from './scalars.js'; import { SelectionSetToObject } from './selection-set-to-object.js'; import { NormalizedScalarsMap, CustomDirectivesConfig } from './types.js'; import { buildScalarsFromConfig, DeclarationBlock, DeclarationBlockConfig, getConfigValue } from './utils.js'; import { OperationVariablesToObject } from './variables-to-object.js'; -function getRootType(operation: OperationTypeNode, schema: GraphQLSchema) { - switch (operation) { - case 'query': - return schema.getQueryType(); - case 'mutation': - return schema.getMutationType(); - case 'subscription': - return schema.getSubscriptionType(); - } - throw new Error(`Unknown operation type: ${operation}`); -} - -export interface ParsedDocumentsConfig extends ParsedTypesConfig { +export interface ParsedDocumentsConfig extends ParsedConfig { extractAllFieldsToTypes: boolean; - globalNamespace: boolean; operationResultSuffix: string; dedupeOperationSuffix: boolean; omitOperationSuffix: boolean; namespacedImportName: string | null; exportFragmentSpreadSubTypes: boolean; skipTypeNameForRoot: boolean; + nonOptionalTypename: boolean; + globalNamespace: boolean; experimentalFragmentVariables: boolean; mergeFragmentTypes: boolean; customDirectives: CustomDirectivesConfig; @@ -43,7 +31,7 @@ export interface ParsedDocumentsConfig extends ParsedTypesConfig { importSchemaTypesFrom: string; } -export interface RawDocumentsConfig extends RawTypesConfig { +export interface RawDocumentsConfig extends RawConfig { /** * @default false * @description Avoid adding `__typename` for root types. This is ignored when a selection explicitly specifies `__typename`. @@ -67,6 +55,30 @@ export interface RawDocumentsConfig extends RawTypesConfig { * ``` */ skipTypeNameForRoot?: boolean; + /** + * @default false + * @description Automatically adds `__typename` field to the generated types, even when they are not specified + * in the selection set, and makes it non-optional + * + * @exampleMarkdown + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * nonOptionalTypename: true + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + nonOptionalTypename?: boolean; /** * @default false * @description Puts all generated code under `global` namespace. Useful for Stencil integration. @@ -199,6 +211,14 @@ export interface RawDocumentsConfig extends RawTypesConfig { * ``` */ importSchemaTypesFrom?: string; + /** + * @default false + * @description Extract all field types to their own types, instead of inlining them. + * This helps to reduce type duplication, and makes type errors more readable. + * It can also significantly reduce the size of the generated code, the generation time, + * and the typechecking time. + */ + extractAllFieldsToTypes?: boolean; } export class BaseDocumentsVisitor< @@ -218,11 +238,10 @@ export class BaseDocumentsVisitor< ) { super(rawConfig, { exportFragmentSpreadSubTypes: getConfigValue(rawConfig.exportFragmentSpreadSubTypes, false), - enumPrefix: getConfigValue(rawConfig.enumPrefix, true), - enumSuffix: getConfigValue(rawConfig.enumSuffix, true), dedupeOperationSuffix: getConfigValue(rawConfig.dedupeOperationSuffix, false), omitOperationSuffix: getConfigValue(rawConfig.omitOperationSuffix, false), skipTypeNameForRoot: getConfigValue(rawConfig.skipTypeNameForRoot, false), + nonOptionalTypename: getConfigValue(rawConfig.nonOptionalTypename, false), namespacedImportName: getConfigValue(rawConfig.namespacedImportName, null), experimentalFragmentVariables: getConfigValue(rawConfig.experimentalFragmentVariables, false), globalNamespace: !!rawConfig.globalNamespace, @@ -231,6 +250,7 @@ export class BaseDocumentsVisitor< customDirectives: getConfigValue(rawConfig.customDirectives, { apolloUnmask: false }), generatesOperationTypes: getConfigValue(rawConfig.generatesOperationTypes, true), importSchemaTypesFrom: getConfigValue(rawConfig.importSchemaTypesFrom, ''), + extractAllFieldsToTypes: getConfigValue(rawConfig.extractAllFieldsToTypes, false), ...((additionalConfig || {}) as any), }); @@ -380,3 +400,15 @@ export class BaseDocumentsVisitor< .join('\n\n'); } } + +function getRootType(operation: OperationTypeNode, schema: GraphQLSchema) { + switch (operation) { + case 'query': + return schema.getQueryType(); + case 'mutation': + return schema.getMutationType(); + case 'subscription': + return schema.getSubscriptionType(); + } + throw new Error(`Unknown operation type: ${operation}`); +} diff --git a/packages/plugins/other/visitor-plugin-common/src/base-resolvers-visitor.ts b/packages/plugins/other/visitor-plugin-common/src/base-resolvers-visitor.ts index bb8cef98b18..0bdcb2a877a 100644 --- a/packages/plugins/other/visitor-plugin-common/src/base-resolvers-visitor.ts +++ b/packages/plugins/other/visitor-plugin-common/src/base-resolvers-visitor.ts @@ -69,8 +69,6 @@ export interface ParsedResolversConfig extends ParsedConfig { enumValues: ParsedEnumValuesMap; resolverTypeWrapperSignature: string; federation: boolean; - enumPrefix: boolean; - enumSuffix: boolean; optionalResolveType: boolean; immutableTypes: boolean; namespacedImportName: string; @@ -509,59 +507,7 @@ export interface RawResolversConfig extends RawConfig { * @description Supports Apollo Federation */ federation?: boolean; - /** - * @default true - * @description Allow you to disable prefixing for generated enums, works in combination with `typesPrefix`. - * - * @exampleMarkdown - * ## Disable enum prefixes - * - * ```ts filename="codegen.ts" - * import type { CodegenConfig } from '@graphql-codegen/cli'; - * - * const config: CodegenConfig = { - * // ... - * generates: { - * 'path/to/file': { - * plugins: ['typescript', 'typescript-resolver'], - * config: { - * typesPrefix: 'I', - * enumPrefix: false - * }, - * }, - * }, - * }; - * export default config; - * ``` - */ - enumPrefix?: boolean; - /** - * @default true - * @description Allow you to disable suffixing for generated enums, works in combination with `typesSuffix`. - * - * @exampleMarkdown - * ## Disable enum suffixes - * - * ```ts filename="codegen.ts" - * import type { CodegenConfig } from '@graphql-codegen/cli'; - * - * const config: CodegenConfig = { - * // ... - * generates: { - * 'path/to/file': { - * plugins: ['typescript', 'typescript-resolver'], - * config: { - * typesSuffix: 'I', - * enumSuffix: false - * }, - * }, - * }, - * }; - * export default config; - * ``` - */ - enumSuffix?: boolean; /** * @description Configures behavior for custom directives from various GraphQL libraries. * @exampleMarkdown @@ -795,8 +741,6 @@ export class BaseResolversVisitor< super(rawConfig, { immutableTypes: getConfigValue(rawConfig.immutableTypes, false), optionalResolveType: getConfigValue(rawConfig.optionalResolveType, false), - enumPrefix: getConfigValue(rawConfig.enumPrefix, true), - enumSuffix: getConfigValue(rawConfig.enumSuffix, true), federation: getConfigValue(rawConfig.federation, false), resolverTypeWrapperSignature: getConfigValue(rawConfig.resolverTypeWrapperSignature, 'Promise | T'), enumValues: parseEnumValues({ diff --git a/packages/plugins/other/visitor-plugin-common/src/base-types-visitor.ts b/packages/plugins/other/visitor-plugin-common/src/base-types-visitor.ts index b4d9a405e85..7bab67a08dd 100644 --- a/packages/plugins/other/visitor-plugin-common/src/base-types-visitor.ts +++ b/packages/plugins/other/visitor-plugin-common/src/base-types-visitor.ts @@ -16,12 +16,12 @@ import { ScalarTypeDefinitionNode, UnionTypeDefinitionNode, } from 'graphql'; -import { BaseVisitor, ParsedConfig, RawConfig } from './base-visitor.js'; +import { BaseVisitor, type ParsedConfig, type RawConfig } from './base-visitor.js'; import { normalizeDeclarationKind } from './declaration-kinds.js'; import { parseEnumValues } from './enum-values.js'; import { transformDirectiveArgumentAndInputFieldMappings } from './mappers.js'; import { DEFAULT_SCALARS } from './scalars.js'; -import { +import type { DeclarationKind, DeclarationKindConfig, DirectiveArgumentAndInputFieldMappings, @@ -47,18 +47,18 @@ import { buildTypeImport, getEnumsImports } from './imports.js'; export interface ParsedTypesConfig extends ParsedConfig { enumValues: ParsedEnumValuesMap; + ignoreEnumValuesFromSchema: boolean; declarationKind: DeclarationKindConfig; addUnderscoreToArgsType: boolean; onlyEnums: boolean; onlyOperationTypes: boolean; - enumPrefix: boolean; - enumSuffix: boolean; fieldWrapperValue: string; wrapFieldDefinitions: boolean; entireFieldWrapperValue: string; wrapEntireDefinitions: boolean; - ignoreEnumValuesFromSchema: boolean; directiveArgumentAndInputFieldMappings: ParsedDirectiveArgumentAndInputFieldMappings; + addTypename: boolean; + nonOptionalTypename: boolean; } export interface RawTypesConfig extends RawConfig { @@ -155,29 +155,11 @@ export interface RawTypesConfig extends RawConfig { */ enumValues?: EnumValuesMap; /** - * @description Overrides the default output for various GraphQL elements. + * @description This will cause the generator to ignore enum values defined in GraphQLSchema + * @default false * * @exampleMarkdown - * ## Override all declarations - * - * ```ts filename="codegen.ts" - * import type { CodegenConfig } from '@graphql-codegen/cli'; - * - * const config: CodegenConfig = { - * // ... - * generates: { - * 'path/to/file': { - * // plugins... - * config: { - * declarationKind: 'interface' - * }, - * }, - * }, - * }; - * export default config; - * ``` - * - * ## Override only specific declarations + * ## Ignore enum values from schema * * ```ts filename="codegen.ts" * import type { CodegenConfig } from '@graphql-codegen/cli'; @@ -188,10 +170,7 @@ export interface RawTypesConfig extends RawConfig { * 'path/to/file': { * // plugins... * config: { - * declarationKind: { - * type: 'interface', - * input: 'interface' - * } + * ignoreEnumValuesFromSchema: true, * }, * }, * }, @@ -199,13 +178,12 @@ export interface RawTypesConfig extends RawConfig { * export default config; * ``` */ - declarationKind?: DeclarationKind | DeclarationKindConfig; + ignoreEnumValuesFromSchema?: boolean; /** - * @default true - * @description Allow you to disable prefixing for generated enums, works in combination with `typesPrefix`. + * @description Overrides the default output for various GraphQL elements. * * @exampleMarkdown - * ## Disable enum prefixes + * ## Override all declarations * * ```ts filename="codegen.ts" * import type { CodegenConfig } from '@graphql-codegen/cli'; @@ -216,22 +194,15 @@ export interface RawTypesConfig extends RawConfig { * 'path/to/file': { * // plugins... * config: { - * typesPrefix: 'I', - * enumPrefix: false + * declarationKind: 'interface' * }, * }, * }, * }; * export default config; * ``` - */ - enumPrefix?: boolean; - /** - * @default true - * @description Allow you to disable suffixing for generated enums, works in combination with `typesSuffix`. * - * @exampleMarkdown - * ## Disable enum suffixes + * ## Override only specific declarations * * ```ts filename="codegen.ts" * import type { CodegenConfig } from '@graphql-codegen/cli'; @@ -242,8 +213,10 @@ export interface RawTypesConfig extends RawConfig { * 'path/to/file': { * // plugins... * config: { - * typesSuffix: 'I', - * enumSuffix: false + * declarationKind: { + * type: 'interface', + * input: 'interface' + * } * }, * }, * }, @@ -251,7 +224,7 @@ export interface RawTypesConfig extends RawConfig { * export default config; * ``` */ - enumSuffix?: boolean; + declarationKind?: DeclarationKind | DeclarationKindConfig; /** * @description Allow you to add wrapper for field type, use T as the generic value. Make sure to set `wrapFieldDefinitions` to `true` in order to make this flag work. * @default T @@ -354,31 +327,6 @@ export interface RawTypesConfig extends RawConfig { * ``` */ onlyOperationTypes?: boolean; - /** - * @description This will cause the generator to ignore enum values defined in GraphQLSchema - * @default false - * - * @exampleMarkdown - * ## Ignore enum values from schema - * - * ```ts filename="codegen.ts" - * import type { CodegenConfig } from '@graphql-codegen/cli'; - * - * const config: CodegenConfig = { - * // ... - * generates: { - * 'path/to/file': { - * // plugins... - * config: { - * ignoreEnumValuesFromSchema: true, - * }, - * }, - * }, - * }; - * export default config; - * ``` - */ - ignoreEnumValuesFromSchema?: boolean; /** * @name wrapEntireFieldDefinitions * @type boolean @@ -492,6 +440,53 @@ export interface RawTypesConfig extends RawConfig { * ``` */ directiveArgumentAndInputFieldMappingTypeSuffix?: string; + /** + * @default false + * @description Does not add `__typename` to the generated types, unless it was specified in the selection set. + * + * @exampleMarkdown + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * skipTypename: true + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + skipTypename?: boolean; + /** + * @default false + * @description Automatically adds `__typename` field to the generated types, even when they are not specified + * in the selection set, and makes it non-optional + * + * @exampleMarkdown + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * nonOptionalTypename: true + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + nonOptionalTypename?: boolean; } export class BaseTypesVisitor< @@ -507,8 +502,6 @@ export class BaseTypesVisitor< defaultScalars: NormalizedScalarsMap = DEFAULT_SCALARS ) { super(rawConfig, { - enumPrefix: getConfigValue(rawConfig.enumPrefix, true), - enumSuffix: getConfigValue(rawConfig.enumSuffix, true), onlyEnums: getConfigValue(rawConfig.onlyEnums, false), onlyOperationTypes: getConfigValue(rawConfig.onlyOperationTypes, false), addUnderscoreToArgsType: getConfigValue(rawConfig.addUnderscoreToArgsType, false), @@ -517,17 +510,19 @@ export class BaseTypesVisitor< mapOrStr: rawConfig.enumValues, ignoreEnumValuesFromSchema: rawConfig.ignoreEnumValuesFromSchema, }), + ignoreEnumValuesFromSchema: getConfigValue(rawConfig.ignoreEnumValuesFromSchema, false), declarationKind: normalizeDeclarationKind(rawConfig.declarationKind), scalars: buildScalarsFromConfig(_schema, rawConfig, defaultScalars), fieldWrapperValue: getConfigValue(rawConfig.fieldWrapperValue, 'T'), wrapFieldDefinitions: getConfigValue(rawConfig.wrapFieldDefinitions, false), entireFieldWrapperValue: getConfigValue(rawConfig.entireFieldWrapperValue, 'T'), wrapEntireDefinitions: getConfigValue(rawConfig.wrapEntireFieldDefinitions, false), - ignoreEnumValuesFromSchema: getConfigValue(rawConfig.ignoreEnumValuesFromSchema, false), directiveArgumentAndInputFieldMappings: transformDirectiveArgumentAndInputFieldMappings( rawConfig.directiveArgumentAndInputFieldMappings ?? {}, rawConfig.directiveArgumentAndInputFieldMappingTypeSuffix ), + addTypename: !rawConfig.skipTypename, + nonOptionalTypename: getConfigValue(rawConfig.nonOptionalTypename, false), ...additionalConfig, }); diff --git a/packages/plugins/other/visitor-plugin-common/src/base-visitor.ts b/packages/plugins/other/visitor-plugin-common/src/base-visitor.ts index 5f83d587e6c..796326531fa 100644 --- a/packages/plugins/other/visitor-plugin-common/src/base-visitor.ts +++ b/packages/plugins/other/visitor-plugin-common/src/base-visitor.ts @@ -12,7 +12,7 @@ import { ParsedScalarsMap, ScalarsMap, } from './types.js'; -import { DeclarationBlockConfig } from './utils.js'; +import { DeclarationBlockConfig, getConfigValue } from './utils.js'; export interface BaseVisitorConvertOptions { useTypesPrefix?: boolean; @@ -26,9 +26,8 @@ export interface ParsedConfig { convert: ConvertFn; typesPrefix: string; typesSuffix: string; - addTypename: boolean; - nonOptionalTypename: boolean; - extractAllFieldsToTypes: boolean; + enumPrefix: boolean; + enumSuffix: boolean; externalFragments: LoadedFragment[]; fragmentImports: ImportDeclaration[]; immutableTypes: boolean; @@ -259,10 +258,12 @@ export interface RawConfig { */ typesSuffix?: string; /** - * @default false - * @description Does not add `__typename` to the generated types, unless it was specified in the selection set. + * @default true + * @description Allow you to disable prefixing for generated enums, works in combination with `typesPrefix`. * * @exampleMarkdown + * ## Disable enum prefixes + * * ```ts filename="codegen.ts" * import type { CodegenConfig } from '@graphql-codegen/cli'; * @@ -272,7 +273,8 @@ export interface RawConfig { * 'path/to/file': { * // plugins... * config: { - * skipTypename: true + * typesPrefix: 'I', + * enumPrefix: false * }, * }, * }, @@ -280,13 +282,14 @@ export interface RawConfig { * export default config; * ``` */ - skipTypename?: boolean; + enumPrefix?: boolean; /** - * @default false - * @description Automatically adds `__typename` field to the generated types, even when they are not specified - * in the selection set, and makes it non-optional + * @default true + * @description Allow you to disable suffixing for generated enums, works in combination with `typesSuffix`. * * @exampleMarkdown + * ## Disable enum suffixes + * * ```ts filename="codegen.ts" * import type { CodegenConfig } from '@graphql-codegen/cli'; * @@ -296,7 +299,8 @@ export interface RawConfig { * 'path/to/file': { * // plugins... * config: { - * nonOptionalTypename: true + * typesSuffix: 'I', + * enumSuffix: false * }, * }, * }, @@ -304,7 +308,7 @@ export interface RawConfig { * export default config; * ``` */ - nonOptionalTypename?: boolean; + enumSuffix?: boolean; /** * @name useTypeImports * @type boolean @@ -341,10 +345,6 @@ export interface RawConfig { * @ignore */ fragmentImports?: ImportDeclaration[]; - /** - * @ignore - */ - globalNamespace?: boolean; /** * @ignore */ @@ -366,15 +366,6 @@ export interface RawConfig { */ emitLegacyCommonJSImports?: boolean; - /** - * @default false - * @description Extract all field types to their own types, instead of inlining them. - * This helps to reduce type duplication, and makes type errors more readable. - * It can also significantly reduce the size of the generated code, the generation time, - * and the typechecking time. - */ - extractAllFieldsToTypes?: boolean; - /** * @default false * @description If you prefer to have each field in generated types printed on a new line, set this to true. @@ -401,16 +392,15 @@ export class BaseVisitor; export type SelectionSetProcessorConfig = { namespacedImportName: string | null; convertName: ConvertNameFn; - enumPrefix: boolean | null; - enumSuffix: boolean | null; + enumPrefix: boolean; + enumSuffix: boolean; scalars: NormalizedScalarsMap; formatNamedField(params: { name: string; isOptional?: boolean }): string; wrapTypeWithModifiers(baseType: string, type: GraphQLOutputType | GraphQLNamedType): string; diff --git a/packages/plugins/other/visitor-plugin-common/src/selection-set-to-object.ts b/packages/plugins/other/visitor-plugin-common/src/selection-set-to-object.ts index 626390972f0..22c573072dd 100644 --- a/packages/plugins/other/visitor-plugin-common/src/selection-set-to-object.ts +++ b/packages/plugins/other/visitor-plugin-common/src/selection-set-to-object.ts @@ -502,9 +502,6 @@ export class SelectionSetToObject< // The keys here will be used to generate intermediary // fragment names. To avoid blowing up the type name on large // unions, calculate a stable hash here instead. - // - // Also use fragment hashing if skipTypename is true, since we - // then don't have a typename for naming the fragment. acc[ selectedTypes.length <= 3 ? // Remove quote marks to produce a valid type name diff --git a/packages/plugins/typescript/operations/src/config.ts b/packages/plugins/typescript/operations/src/config.ts index 44fb213b786..9dcbcb6ce01 100644 --- a/packages/plugins/typescript/operations/src/config.ts +++ b/packages/plugins/typescript/operations/src/config.ts @@ -186,7 +186,6 @@ export interface TypeScriptDocumentsPluginConfig extends RawDocumentsConfig { * ``` */ noExport?: boolean; - globalNamespace?: boolean; /** * @name addOperationExport * @type boolean @@ -364,7 +363,6 @@ export interface TypeScriptDocumentsPluginConfig extends RawDocumentsConfig { * export default config */ enumType?: ConvertSchemaEnumToDeclarationBlockString['outputType']; - /** * @description Overrides the default value of enum values declared in your GraphQL schema. * You can also map the entire enum to an external type by providing a string that of `module#type`. @@ -433,7 +431,31 @@ export interface TypeScriptDocumentsPluginConfig extends RawDocumentsConfig { * ``` */ enumValues?: EnumValuesMap; - + /** + * @description This will cause the generator to ignore enum values defined in GraphQLSchema + * @default false + * + * @exampleMarkdown + * ## Ignore enum values from schema + * + * ```ts filename="codegen.ts" + * import type { CodegenConfig } from '@graphql-codegen/cli'; + * + * const config: CodegenConfig = { + * // ... + * generates: { + * 'path/to/file': { + * // plugins... + * config: { + * ignoreEnumValuesFromSchema: true, + * }, + * }, + * }, + * }; + * export default config; + * ``` + */ + ignoreEnumValuesFromSchema?: boolean; /** * @description This option controls whether or not a catch-all entry is added to enum type definitions for values that may be added in the future. * This is useful if you are using `relay`. diff --git a/packages/plugins/typescript/operations/src/index.ts b/packages/plugins/typescript/operations/src/index.ts index cbc79218331..6dd44646e80 100644 --- a/packages/plugins/typescript/operations/src/index.ts +++ b/packages/plugins/typescript/operations/src/index.ts @@ -5,8 +5,6 @@ import { concatAST, GraphQLSchema } from 'graphql'; import { TypeScriptDocumentsPluginConfig } from './config.js'; import { TypeScriptDocumentsVisitor } from './visitor.js'; -export { TypeScriptDocumentsPluginConfig } from './config.js'; - export const plugin: PluginFunction = async ( inputSchema, rawDocuments, diff --git a/packages/plugins/typescript/operations/src/visitor.ts b/packages/plugins/typescript/operations/src/visitor.ts index f7ca9946591..5c80c82058e 100644 --- a/packages/plugins/typescript/operations/src/visitor.ts +++ b/packages/plugins/typescript/operations/src/visitor.ts @@ -55,8 +55,9 @@ export interface TypeScriptDocumentsParsedConfig extends ParsedDocumentsConfig { maybeValue: string; allowUndefinedQueryVariables: boolean; enumType: ConvertSchemaEnumToDeclarationBlockString['outputType']; - futureProofEnums: boolean; enumValues: ParsedEnumValuesMap; + ignoreEnumValuesFromSchema: boolean; + futureProofEnums: boolean; } type UsedNamedInputTypes = Record< @@ -95,6 +96,7 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< mapOrStr: config.enumValues, ignoreEnumValuesFromSchema: config.ignoreEnumValuesFromSchema, }), + ignoreEnumValuesFromSchema: getConfigValue(config.ignoreEnumValuesFromSchema, false), futureProofEnums: getConfigValue(config.futureProofEnums, false), } as TypeScriptDocumentsParsedConfig, schema @@ -140,10 +142,10 @@ export class TypeScriptDocumentsVisitor extends BaseDocumentsVisitor< }, printFieldsOnNewLines: this.config.printFieldsOnNewLines, }; - const processor = new PreResolveTypesProcessor(processorConfig); + this.setSelectionSetHandler( new SelectionSetToObject( - processor, + new PreResolveTypesProcessor(processorConfig), this.scalars, this.schema, this.convertName.bind(this), diff --git a/packages/plugins/typescript/operations/tests/ts-documents.spec.ts b/packages/plugins/typescript/operations/tests/ts-documents.spec.ts index 3976a631b8c..30c7261d463 100644 --- a/packages/plugins/typescript/operations/tests/ts-documents.spec.ts +++ b/packages/plugins/typescript/operations/tests/ts-documents.spec.ts @@ -785,7 +785,7 @@ describe('TypeScript Operations Plugin', () => { await validate(content); }); - it('Should add __typename correctly with nonOptionalTypename=false,skipTypename=true and explicit field', async () => { + it('Should add __typename correctly with nonOptionalTypename=false and explicit field', async () => { const testSchema = buildSchema(/* GraphQL */ ` type Search { search: [SearchResult!]! @@ -840,10 +840,7 @@ describe('TypeScript Operations Plugin', () => { const { content } = await plugin( testSchema, [{ location: 'test-file.ts', document: ast }], - { - nonOptionalTypename: false, - skipTypename: true, - }, + { nonOptionalTypename: false }, { outputFile: '' } ); @@ -864,24 +861,6 @@ export type Q2Query = { search: Array< await validate(content); }); - it('Should skip __typename when skipTypename is set to true', async () => { - const ast = parse(/* GraphQL */ ` - query { - dummy - } - `); - - const { content } = await plugin( - schema, - [{ location: 'test-file.ts', document: ast }], - { skipTypename: true }, - { outputFile: '' } - ); - - expect(content).not.toContain(`__typename`); - await validate(content); - }); - it('Should add __typename when dealing with fragments', async () => { const testSchema = buildSchema(/* GraphQL */ ` interface Node { @@ -1004,31 +983,6 @@ export type Q2Query = { search: Array< await validate(content); }); - it('Should add __typename as non-optional when its explictly specified, even if skipTypename is true', async () => { - const ast = parse(/* GraphQL */ ` - query { - __typename - dummy - } - `); - - const { content } = await plugin( - schema, - [{ location: 'test-file.ts', document: ast }], - { skipTypename: true }, - { outputFile: '' } - ); - - expect(content).toMatchInlineSnapshot(` - "export type Unnamed_1_QueryVariables = Exact<{ [key: string]: never; }>; - - - export type Unnamed_1_Query = { __typename: 'Query', dummy: string | null }; - " - `); - await validate(content); - }); - it('Should add __typename correctly when unions are in use and nonOptionalTypename=true', async () => { const ast = parse(/* GraphQL */ ` query unionTest { @@ -1167,12 +1121,7 @@ export type Q2Query = { search: Array< } `); - const { content } = await plugin( - schema, - [{ location: 'test-file.ts', document: ast }], - { skipTypename: true }, - { outputFile: '' } - ); + const { content } = await plugin(schema, [{ location: 'test-file.ts', document: ast }], {}, { outputFile: '' }); expect(content).toMatchInlineSnapshot(` "export type Unnamed_1_QueryVariables = Exact<{ [key: string]: never; }>; @@ -1201,12 +1150,7 @@ export type Q2Query = { search: Array< } `); - const { content } = await plugin( - schema, - [{ location: 'test-file.ts', document: ast }], - { skipTypename: true }, - { outputFile: '' } - ); + const { content } = await plugin(schema, [{ location: 'test-file.ts', document: ast }], {}, { outputFile: '' }); expect(content).toMatchInlineSnapshot(` "export type Unnamed_1_QueryVariables = Exact<{ [key: string]: never; }>; @@ -1642,12 +1586,7 @@ export type Q2Query = { search: Array< } `); - const { content } = await plugin( - schema, - [{ location: 'test-file.ts', document: ast }], - { skipTypename: true }, - { outputFile: '' } - ); + const { content } = await plugin(schema, [{ location: 'test-file.ts', document: ast }], {}, { outputFile: '' }); expect(content).toMatchInlineSnapshot(` "export type Role = | 'USER' @@ -1681,12 +1620,7 @@ export type Q2Query = { search: Array< } `); - const { content } = await plugin( - schema, - [{ location: 'test-file.ts', document: ast }], - { skipTypename: true }, - { outputFile: '' } - ); + const { content } = await plugin(schema, [{ location: 'test-file.ts', document: ast }], {}, { outputFile: '' }); expect(content).toMatchInlineSnapshot(` "export type UserFieldsFragment = { id: string, profile: { age: number | null } | null }; @@ -1721,12 +1655,7 @@ export type Q2Query = { search: Array< } `); - const { content } = await plugin( - schema, - [{ location: 'test-file.ts', document: ast }], - { skipTypename: false }, - { outputFile: '' } - ); + const { content } = await plugin(schema, [{ location: 'test-file.ts', document: ast }], {}, { outputFile: '' }); expect(content).toMatchInlineSnapshot(` "export type UserFieldsFragment = { id: string }; @@ -2205,7 +2134,7 @@ export type Q2Query = { search: Array< await validate(content); }); - it('Should support merging identical fragment union types with skipTypename', async () => { + it('Should support merging identical fragment union types', async () => { const ast = parse(/* GraphQL */ ` query test { notifications { @@ -2221,7 +2150,10 @@ export type Q2Query = { search: Array< const { content } = await plugin( schema, [{ location: 'test-file.ts', document: ast }], - { skipTypename: true, mergeFragmentTypes: true, namingConvention: 'keep' }, + { + mergeFragmentTypes: true, + namingConvention: 'keep', + }, { outputFile: '' } ); @@ -2237,7 +2169,7 @@ export type Q2Query = { search: Array< await validate(content); }); - it('Should support computing correct names for merged fragment union types with skipTypename', async () => { + it('Should support computing correct names for merged fragment union types', async () => { const ast = parse(/* GraphQL */ ` fragment N on Notifiction { id @@ -2250,7 +2182,10 @@ export type Q2Query = { search: Array< const { content } = await plugin( schema, [{ location: 'test-file.ts', document: ast }], - { skipTypename: true, mergeFragmentTypes: true, namingConvention: 'keep' }, + { + mergeFragmentTypes: true, + namingConvention: 'keep', + }, { outputFile: '' } ); @@ -2325,12 +2260,7 @@ export type Q2Query = { search: Array< } `); - const { content } = await plugin( - schema, - [{ location: 'test-file.ts', document: ast }], - { skipTypename: true }, - { outputFile: '' } - ); + const { content } = await plugin(schema, [{ location: 'test-file.ts', document: ast }], {}, { outputFile: '' }); expect(content).toMatchInlineSnapshot(` "export type CurrentUserQueryVariables = Exact<{ [key: string]: never; }>; @@ -2363,7 +2293,7 @@ export type Q2Query = { search: Array< const { content } = await plugin( gitHuntSchema, [{ location: 'test-file.ts', document: ast }], - { skipTypename: true }, + {}, { outputFile: '' } ); @@ -2378,15 +2308,6 @@ export type Q2Query = { search: Array< " ` ); - expect(content).toMatchInlineSnapshot(` - "export type MeQueryVariables = Exact<{ - repoFullName: string; - }>; - - - export type MeQuery = { currentUser: { login: string, html_url: string } | null, entry: { id: number, createdAt: number, postedBy: { login: string, html_url: string } } | null }; - " - `); await validate(content); }); @@ -2620,12 +2541,7 @@ export type Q2Query = { search: Array< } `); - const { content } = await plugin( - schema, - [{ location: 'test-file.ts', document: ast }], - { skipTypename: true }, - { outputFile: '' } - ); + const { content } = await plugin(schema, [{ location: 'test-file.ts', document: ast }], {}, { outputFile: '' }); expect(content).toMatchInlineSnapshot(` "export type DummyQueryVariables = Exact<{ [key: string]: never; }>; @@ -2647,12 +2563,7 @@ export type Q2Query = { search: Array< } `); - const { content } = await plugin( - schema, - [{ location: 'test-file.ts', document: ast }], - { skipTypename: true }, - { outputFile: '' } - ); + const { content } = await plugin(schema, [{ location: 'test-file.ts', document: ast }], {}, { outputFile: '' }); expect(content).toMatchInlineSnapshot(` "export type DummyQueryVariables = Exact<{ [key: string]: never; }>; @@ -2678,12 +2589,7 @@ export type Q2Query = { search: Array< } `); - const { content } = await plugin( - schema, - [{ location: 'test-file.ts', document: ast }], - { skipTypename: true }, - { outputFile: '' } - ); + const { content } = await plugin(schema, [{ location: 'test-file.ts', document: ast }], {}, { outputFile: '' }); expect(content).toMatchInlineSnapshot(` "export type Role = @@ -2712,12 +2618,7 @@ export type Q2Query = { search: Array< } `); - const { content } = await plugin( - schema, - [{ location: 'test-file.ts', document: ast }], - { skipTypename: true }, - { outputFile: '' } - ); + const { content } = await plugin(schema, [{ location: 'test-file.ts', document: ast }], {}, { outputFile: '' }); expect(content).toMatchInlineSnapshot(` "export type UserFieldsFragment = { id: string, username: string, profile: { age: number | null } | null }; @@ -2741,12 +2642,7 @@ export type Q2Query = { search: Array< } `); - const { content } = await plugin( - schema, - [{ location: 'test-file.ts', document: ast }], - { skipTypename: true }, - { outputFile: '' } - ); + const { content } = await plugin(schema, [{ location: 'test-file.ts', document: ast }], {}, { outputFile: '' }); expect(content).toMatchInlineSnapshot(` "export type LoginMutationVariables = Exact<{ [key: string]: never; }>; @@ -2765,12 +2661,7 @@ export type Q2Query = { search: Array< } `); - const { content } = await plugin( - schema, - [{ location: 'test-file.ts', document: ast }], - { skipTypename: false }, - { outputFile: '' } - ); + const { content } = await plugin(schema, [{ location: 'test-file.ts', document: ast }], {}, { outputFile: '' }); expect(content).toMatchInlineSnapshot(` "export type TestQueryVariables = Exact<{ [key: string]: never; }>; @@ -2791,12 +2682,7 @@ export type Q2Query = { search: Array< } `); - const { content } = await plugin( - schema, - [{ location: 'test-file.ts', document: ast }], - { skipTypename: true }, - { outputFile: '' } - ); + const { content } = await plugin(schema, [{ location: 'test-file.ts', document: ast }], {}, { outputFile: '' }); expect(content).toMatchInlineSnapshot(` "export type TestSubscriptionVariables = Exact<{ [key: string]: never; }>; @@ -2824,12 +2710,7 @@ export type Q2Query = { search: Array< } `); - const { content } = await plugin( - schema, - [{ location: 'test-file.ts', document: ast }], - { skipTypename: true }, - { outputFile: '' } - ); + const { content } = await plugin(schema, [{ location: 'test-file.ts', document: ast }], {}, { outputFile: '' }); expect(content).toMatchInlineSnapshot( ` @@ -2863,12 +2744,7 @@ export type Q2Query = { search: Array< } `); - const { content } = await plugin( - schema, - [{ location: 'test-file.ts', document: ast }], - { skipTypename: true }, - { outputFile: '' } - ); + const { content } = await plugin(schema, [{ location: 'test-file.ts', document: ast }], {}, { outputFile: '' }); expect(content).toMatchInlineSnapshot( ` @@ -2891,12 +2767,7 @@ export type Q2Query = { search: Array< } `); - const { content } = await plugin( - schema, - [{ location: 'test-file.ts', document: ast }], - { skipTypename: true }, - { outputFile: '' } - ); + const { content } = await plugin(schema, [{ location: 'test-file.ts', document: ast }], {}, { outputFile: '' }); expect(content).toMatchInlineSnapshot(` "export type TestQueryQueryVariables = Exact<{ [key: string]: never; }>; @@ -4529,10 +4400,7 @@ export type Q2Query = { search: Array< const { content } = await plugin( testSchema, [{ location: '', document: query }], - { - skipTypename: true, - namespacedImportName: 'Types', - }, + { namespacedImportName: 'Types' }, { outputFile: 'graphql.ts' } ); @@ -4971,76 +4839,7 @@ function test(q: GetEntityBrandDataQuery): void { `); }); - it('#3950 - Invalid output with fragments and skipTypename: true', async () => { - const schema = buildSchema(/* GraphQL */ ` - type Query { - animals: [Animal!]! - } - - interface Animal { - id: ID! - } - type Duck implements Animal { - id: ID! - } - type Lion implements Animal { - id: ID! - } - type Puma implements Animal { - id: ID! - } - type Wolf implements Animal { - id: ID! - } - `); - - const query = parse(/* GraphQL */ ` - fragment CatFragment on Animal { - ... on Lion { - id - } - ... on Puma { - id - } - } - - query kitty { - animals { - ...CatFragment - } - } - `); - - const { content } = await plugin( - schema, - [{ location: '', document: query }], - { skipTypename: true }, - { outputFile: 'graphql.ts' } - ); - - expect(content).toMatchInlineSnapshot(` - "type CatFragment_Lion_Fragment = { id: string }; - - type CatFragment_Puma_Fragment = { id: string }; - - export type CatFragmentFragment = - | CatFragment_Lion_Fragment - | CatFragment_Puma_Fragment - ; - - export type KittyQueryVariables = Exact<{ [key: string]: never; }>; - - - export type KittyQuery = { animals: Array< - | { id: string } - | { id: string } - | Record - > }; - " - `); - }); - - it('#3950 - Invalid output with fragments and skipTypename: false', async () => { + it('#3950 - Invalid output with fragments', async () => { const schema = buildSchema(/* GraphQL */ ` type Query { animals: [Animal!]! @@ -5080,12 +4879,7 @@ function test(q: GetEntityBrandDataQuery): void { } `); - const { content } = await plugin( - schema, - [{ location: '', document: query }], - { skipTypename: false }, - { outputFile: 'graphql.ts' } - ); + const { content } = await plugin(schema, [{ location: '', document: query }], {}, { outputFile: 'graphql.ts' }); expect(content).toMatchInlineSnapshot(` "type CatFragment_Lion_Fragment = { id: string }; @@ -5138,12 +4932,7 @@ function test(q: GetEntityBrandDataQuery): void { } } `); - const { content } = await plugin( - schema, - [{ location: '', document: query }], - { skipTypename: true }, - { outputFile: 'graphql.ts' } - ); + const { content } = await plugin(schema, [{ location: '', document: query }], {}, { outputFile: 'graphql.ts' }); expect(content).toMatchInlineSnapshot(` "export type UserQueryVariables = Exact<{ [key: string]: never; }>;