@gqlts/cli
Version:
Generate a client sdk from your GraphQl API
149 lines (128 loc) • 4.7 kB
text/typescript
import { ASTNode } from 'graphql/language/ast';
import { visit } from 'graphql/language/visitor';
import { prettify } from './helpers/prettify';
/**
* Converts an AST into a string, using one set of reasonable
* formatting rules.
*/
export type PrintOptions = {
clientVarName?: string;
transformVariableName?: (x: string) => string;
thenCode?: string;
};
export function print(ast: ASTNode, options: PrintOptions = {}) {
return visit(ast, {
leave: (node) => {
const fn = printDocASTReducer(options);
const visitor = fn[node.kind];
if (visitor) {
return visitor(node);
}
return null;
},
});
}
function printDocASTReducer({ clientVarName = 'client', transformVariableName = (x) => x, thenCode }: PrintOptions) {
return {
Name: (node) => node.value,
Variable: (node) => transformVariableName(node.name),
NamedType: ({ name }) => name,
ListType: ({ type }) => '[' + type + ']',
NonNullType: ({ type }) => type,
Directive: ({ name, arguments: args }) => '',
IntValue: ({ value }) => value,
FloatValue: ({ value }) => value,
StringValue: ({ value, block: isBlockString }, key) => JSON.stringify(value),
BooleanValue: ({ value }) => (value ? 'true' : 'false'),
NullValue: () => 'null',
EnumValue: ({ value }) => `'${value}'`,
ListValue: ({ values }) => '[' + join(values, ', ') + ']',
ObjectValue: ({ fields }) => '{' + join(fields, ', ') + '}',
ObjectField: ({ name, value }) => name + ': ' + value,
// Document
Document: (node) => join(node.definitions, '\n\n') + '\n',
OperationDefinition(node) {
const selectionSet = node.selectionSet;
// Anonymous queries with no directives or variable definitions can use
// the query short form.
let code = join(node.variableDefinitions, '\n');
if (node.variableDefinitions.length) {
code = '// variables\n' + code;
code += '\n\n';
}
code += `${clientVarName}.${node.operation}(` + selectionSet + ')';
if (thenCode) {
code += `.then(${thenCode})`;
}
return prettify(code, 'typescript');
},
VariableDefinition: ({ variable, type, defaultValue, directives }) => {
return 'var ' + variable.replace('$', '');
},
SelectionSet: ({ selections }) => block(selections),
Field: ({ alias, name, arguments: args, directives, selectionSet }) => {
if (args.length == 0 && !join([selectionSet])) {
return name + ': true';
}
if (args.length == 0) {
return name + ': ' + join([selectionSet]);
}
const argsAndFields = join([block(args), ',', selectionSet]);
return name + ': ' + wrap('[', argsAndFields, ']');
},
// join(directives, ' '),
Argument: ({ name, value = '' }) => {
if (typeof value === 'string') {
return name + ': ' + transformVariableName(value.replace('$', ''));
}
console.error(`unhandled type, received ${JSON.stringify(value)} as Argument`);
return '';
},
// Fragments
FragmentSpread: ({ name, directives }) => {
// TODO FragmentSpread
return '...' + name + ',';
},
InlineFragment: ({ typeCondition, directives, selectionSet }) => {
// console.log({ selectionSet, directives, typeCondition });
return join(['', wrap('on_', typeCondition), ':', selectionSet], ' ');
},
FragmentDefinition: ({ name, typeCondition, variableDefinitions, directives, selectionSet }) => {
// TODO FragmentDefinition
// Note: fragment variable definitions are experimental and may be changed
// or removed in the future.
return `const ${name} = ` + selectionSet;
},
// Directive
};
}
/**
* Given maybeArray, print an empty string if it is null or empty, otherwise
* print all items together separated by separator if provided
*/
function join(maybeArray: Array<string>, separator = '') {
return maybeArray?.filter((x) => x).join(separator) ?? '';
}
/**
* Given array, print each item on its own line, wrapped in an
* indented "{ }" block.
*/
function block(array) {
return array && array.length !== 0 ? '{\n' + indent(join(array, ',\n')) + '\n}' : '';
}
/**
* If maybeString is not null or empty, then wrap with start and end, otherwise
* print an empty string.
*/
function wrap(start, maybeString, end = '') {
return maybeString ? start + maybeString + end : '';
}
function indent(maybeString) {
return maybeString && ' ' + maybeString.replace(/\n/g, '\n ');
}
function isMultiline(string) {
return string.indexOf('\n') !== -1;
}
function hasMultilineItems(maybeArray) {
return maybeArray && maybeArray.some(isMultiline);
}