@graphql-codegen/visitor-plugin-common
Version:
174 lines (173 loc) • 8.71 kB
JavaScript
import autoBind from 'auto-bind';
import { pascalCase } from 'change-case-all';
import { BaseVisitor } from './base-visitor.js';
import { normalizeOperationAvoidOptionals, } from './operation-avoid-optionals.js';
import { normalizeOperationDeclarationKind, } from './operation-declaration-kinds.js';
import { DEFAULT_INPUT_SCALARS } from './scalars.js';
import { buildScalarsFromConfig, DeclarationBlock, getConfigValue, } from './utils.js';
import { OperationVariablesToObject } from './variables-to-object.js';
export class BaseDocumentsVisitor extends BaseVisitor {
_schema;
_unnamedCounter = 1;
_variablesTransfomer;
_selectionSetToObject;
_globalDeclarations = new Set();
constructor(rawConfig, additionalConfig, _schema, defaultScalars = DEFAULT_INPUT_SCALARS) {
const importSchemaTypesFrom = getConfigValue(rawConfig.importSchemaTypesFrom, '');
const extractAllFieldsToTypes = getConfigValue(rawConfig.extractAllFieldsToTypes, false) ||
getConfigValue(rawConfig.extractAllFieldsToTypesCompact, false);
const declarationKind = normalizeOperationDeclarationKind(getConfigValue(rawConfig.declarationKind, 'type'));
if (extractAllFieldsToTypes && declarationKind.result === 'interface') {
// eslint-disable-next-line no-console
console.warn("`declarationKind.result` has been set to `'type'` because `extractAllFieldsToTypes` or `extractAllFieldsToTypesCompact` is true");
declarationKind.result = 'type';
}
super(rawConfig, {
avoidOptionals: normalizeOperationAvoidOptionals(getConfigValue(rawConfig.avoidOptionals, false)),
exportFragmentSpreadSubTypes: getConfigValue(rawConfig.exportFragmentSpreadSubTypes, false),
dedupeOperationSuffix: getConfigValue(rawConfig.dedupeOperationSuffix, false),
omitOperationSuffix: getConfigValue(rawConfig.omitOperationSuffix, false),
skipTypeNameForRoot: getConfigValue(rawConfig.skipTypeNameForRoot, false),
nonOptionalTypename: getConfigValue(rawConfig.nonOptionalTypename, false),
experimentalFragmentVariables: getConfigValue(rawConfig.experimentalFragmentVariables, false),
globalNamespace: !!rawConfig.globalNamespace,
operationResultSuffix: getConfigValue(rawConfig.operationResultSuffix, ''),
scalars: buildScalarsFromConfig(_schema, rawConfig, defaultScalars),
customDirectives: getConfigValue(rawConfig.customDirectives, { apolloUnmask: false }),
generateOperationTypes: getConfigValue(rawConfig.generateOperationTypes, true),
importSchemaTypesFrom,
namespacedImportName: getConfigValue(rawConfig.namespacedImportName, importSchemaTypesFrom ? 'Types' : null),
extractAllFieldsToTypes,
extractAllFieldsToTypesCompact: getConfigValue(rawConfig.extractAllFieldsToTypesCompact, false),
declarationKind,
...(additionalConfig || {}),
});
this._schema = _schema;
autoBind(this);
this._variablesTransfomer = new OperationVariablesToObject(this.scalars, this.convertName, this.config.namespacedImportName);
}
getGlobalDeclarations(noExport = false) {
return Array.from(this._globalDeclarations).map(t => (noExport ? t : `export ${t}`));
}
setSelectionSetHandler(handler) {
this._selectionSetToObject = handler;
}
setDeclarationBlockConfig(config) {
this._declarationBlockConfig = config;
}
setVariablesTransformer(variablesTransfomer) {
this._variablesTransfomer = variablesTransfomer;
}
get schema() {
return this._schema;
}
handleAnonymousOperation(node) {
const name = node.name?.value;
if (name) {
return this.convertName(name, {
useTypesPrefix: false,
useTypesSuffix: false,
});
}
return this.convertName(String(this._unnamedCounter++), {
prefix: 'Unnamed_',
suffix: '_',
useTypesPrefix: false,
useTypesSuffix: false,
});
}
FragmentDefinition(node) {
if (!this.config.generateOperationTypes) {
return null;
}
const fragmentRootType = this._schema.getType(node.typeCondition.name.value);
const selectionSet = this._selectionSetToObject.createNext(fragmentRootType, node.selectionSet);
const fragmentSuffix = this.getFragmentSuffix(node);
return [
selectionSet.transformFragmentSelectionSetToTypes(node.name.value, fragmentSuffix, this._declarationBlockConfig),
this.config.experimentalFragmentVariables
? new DeclarationBlock({
...this._declarationBlockConfig,
blockTransformer: t => this.applyVariablesWrapper(t),
})
.export()
.asKind('type')
.withName(this.convertName(node.name.value, {
suffix: fragmentSuffix + 'Variables',
}))
.withBlock(this._variablesTransfomer.transform(node.variableDefinitions)).string
: undefined,
]
.filter(r => r)
.join('\n\n');
}
applyVariablesWrapper(variablesBlock, _operationType) {
return variablesBlock;
}
OperationDefinition(node) {
if (!this.config.generateOperationTypes) {
return null;
}
const name = this.handleAnonymousOperation(node);
const operationRootType = getRootType(node.operation, this._schema);
if (!operationRootType) {
throw new Error(`Unable to find root schema type for operation type "${node.operation}"!`);
}
const selectionSet = this._selectionSetToObject.createNext(operationRootType, node.selectionSet);
const visitedOperationVariables = this._variablesTransfomer.transform(node.variableDefinitions);
const operationType = pascalCase(node.operation);
const operationTypeSuffix = this.getOperationSuffix(name, operationType);
const selectionSetObjects = selectionSet.transformSelectionSet(this.convertName(name, {
suffix: operationTypeSuffix,
}));
const operationResultName = this.convertName(name, {
suffix: operationTypeSuffix + this._parsedConfig.operationResultSuffix,
});
// When extractAllFieldsToTypes creates a root type with the same name as the operation result,
// we only need the extracted type and can skip the alias to avoid duplicates
const shouldSkipOperationResult = this._parsedConfig.extractAllFieldsToTypesCompact &&
operationResultName === selectionSetObjects.mergedTypeString;
const operationResult = shouldSkipOperationResult
? ''
: new DeclarationBlock(this._declarationBlockConfig)
.export()
.asKind(this.config.declarationKind.result)
.withName(operationResultName)
.withContent(selectionSetObjects.mergedTypeString).string;
const operationVariables = new DeclarationBlock({
...this._declarationBlockConfig,
blockTransformer: t => this.applyVariablesWrapper(t, operationType),
})
.export()
.asKind('type') // Variables must always be `'type'` because it is an alias of `Exact<Something>`
.withName(this.convertName(name, {
suffix: operationTypeSuffix + 'Variables',
}))
.withBlock(visitedOperationVariables).string;
const dependentTypesContent = this._parsedConfig.extractAllFieldsToTypes
? selectionSetObjects.dependentTypes.map(i => new DeclarationBlock(this._declarationBlockConfig)
.export()
.asKind('type') // dependentTypes must always be `'type'` because they are alias types
.withName(i.name)
.withContent(i.content).string)
: [];
return [
...(dependentTypesContent.length > 0 ? [dependentTypesContent.join('\n')] : []),
operationVariables,
operationResult,
]
.filter(r => r)
.join('\n\n');
}
}
function getRootType(operation, schema) {
switch (operation) {
case 'query':
return schema.getQueryType();
case 'mutation':
return schema.getMutationType();
case 'subscription':
return schema.getSubscriptionType();
}
throw new Error(`Unknown operation type: ${operation}`);
}