Skip to content

Commit

Permalink
add support for fragment arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
JoviDeCroock committed Mar 22, 2024
1 parent 3f3b440 commit cb52a99
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 1 deletion.
5 changes: 4 additions & 1 deletion src/__tests__/__snapshots__/parser.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ exports[`parse > parses the kitchen sink document like graphql.js does 1`] = `
"selectionSet": undefined,
},
{
"arguments": [],
"directives": [
{
"arguments": [],
Expand Down Expand Up @@ -644,7 +645,8 @@ exports[`parse > parses the kitchen sink document like graphql.js does 1`] = `
"value": {
"block": true,
"kind": "StringValue",
"value": "block string uses """",
"value": "block string uses """
",
},
},
],
Expand All @@ -669,6 +671,7 @@ exports[`parse > parses the kitchen sink document like graphql.js does 1`] = `
"value": "Friend",
},
},
"variableDefinitions": undefined,
},
{
"directives": [],
Expand Down
84 changes: 84 additions & 0 deletions src/__tests__/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,90 @@ describe('parse', () => {
expect(() => parse('fragment Name on Type { field }')).not.toThrow();
});

it('parses fragment variable-definitions', () => {
expect(parse('fragment x on Type ($var: Int = 1) { field }').definitions[0]).toEqual({
kind: Kind.FRAGMENT_DEFINITION,
directives: [],
name: {
kind: Kind.NAME,
value: 'x',
},
typeCondition: {
kind: Kind.NAMED_TYPE,
name: {
kind: Kind.NAME,
value: 'Type',
},
},
variableDefinitions: [
{
kind: Kind.VARIABLE_DEFINITION,
type: {
kind: Kind.NAMED_TYPE,
name: {
kind: Kind.NAME,
value: 'Int',
},
},
variable: {
kind: Kind.VARIABLE,
name: {
kind: Kind.NAME,
value: 'var',
},
},
defaultValue: {
kind: Kind.INT,
value: '1',
},
directives: [],
},
],
selectionSet: {
kind: Kind.SELECTION_SET,
selections: [
{
alias: undefined,
kind: Kind.FIELD,
directives: [],
selectionSet: undefined,
arguments: [],
name: {
kind: Kind.NAME,
value: 'field',
},
},
],
},
});
});

it.only('parses fragment-spread arguments', () => {
expect(
parse('query x { ...x(var: 2) } fragment x on Type ($var: Int = 1) { field }').definitions[0]
).toHaveProperty('selectionSet.selections.0', {
kind: Kind.FRAGMENT_SPREAD,
directives: [],
name: {
kind: Kind.NAME,
value: 'x',
},
arguments: [
{
kind: 'Argument',
name: {
kind: 'Name',
value: 'var',
},
value: {
kind: 'IntValue',
value: '2',
},
},
],
});
});

it('parses fields', () => {
expect(() => parse('{ field: }')).toThrow();
expect(() => parse('{ alias: field() }')).toThrow();
Expand Down
89 changes: 89 additions & 0 deletions src/__tests__/printer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as graphql16 from 'graphql16';
import { parse } from '../parser';
import { print, printString, printBlockString } from '../printer';
import kitchenSinkAST from './fixtures/kitchen_sink.json';
import { Kind } from 'src/kind';

function dedentString(string: string) {
const trimmedStr = string
Expand Down Expand Up @@ -115,6 +116,94 @@ describe('print', () => {
).toBe('[Type!]');
});

it('prints fragment-definition with variables', () => {
expect(
print({
kind: Kind.FRAGMENT_DEFINITION,
directives: [],
name: {
kind: Kind.NAME,
value: 'x',
},
typeCondition: {
kind: Kind.NAMED_TYPE,
name: {
kind: Kind.NAME,
value: 'Type',
},
},
variableDefinitions: [
{
kind: Kind.VARIABLE_DEFINITION,
type: {
kind: Kind.NAMED_TYPE,
name: {
kind: Kind.NAME,
value: 'Int',
},
},
variable: {
kind: Kind.VARIABLE,
name: {
kind: Kind.NAME,
value: 'var',
},
},
defaultValue: {
kind: Kind.INT,
value: '1',
},
directives: [],
},
],
selectionSet: {
kind: Kind.SELECTION_SET,
selections: [
{
alias: undefined,
kind: Kind.FIELD,
directives: [],
selectionSet: undefined,
arguments: [],
name: {
kind: Kind.NAME,
value: 'field',
},
},
],
},
} as any)
).toBe(`fragment x on Type($var: Int = 1) {
field
}`);
});

it('prints fragment-spread with arguments', () => {
expect(
print({
kind: Kind.FRAGMENT_SPREAD,
directives: [],
name: {
kind: Kind.NAME,
value: 'x',
},
arguments: [
{
kind: 'Argument',
name: {
kind: 'Name',
value: 'var',
},
value: {
kind: 'IntValue',
value: '2',
},
},
],
} as any)
).toBe(`...x(var: 2)`);
});

// NOTE: The shim won't throw for invalid AST nodes
it('returns empty strings for invalid AST', () => {
const badAST = { random: 'Data' };
Expand Down
2 changes: 2 additions & 0 deletions src/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ export type FragmentSpreadNode = Or<
{
readonly kind: Kind.FRAGMENT_SPREAD;
readonly name: NameNode;
readonly arguments?: ReadonlyArray<ArgumentNode>;
readonly directives?: ReadonlyArray<DirectiveNode>;
readonly loc?: Location;
}
Expand All @@ -209,6 +210,7 @@ export type FragmentDefinitionNode = Or<
{
readonly kind: Kind.FRAGMENT_DEFINITION;
readonly name: NameNode;
readonly variableDefinitions?: ReadonlyArray<VariableDefinitionNode>;
readonly typeCondition: NamedTypeNode;
readonly directives?: ReadonlyArray<DirectiveNode>;
readonly selectionSet: SelectionSetNode;
Expand Down
5 changes: 5 additions & 0 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,10 +319,12 @@ function fragmentSpread(): ast.FragmentSpreadNode | ast.InlineFragmentNode | und
const _idx = idx;
let _name: ast.NameNode | undefined;
if ((_name = name()) && _name.value !== 'on') {
const _arguments = arguments_(false);
return {
kind: 'FragmentSpread' as Kind.FRAGMENT_SPREAD,
name: _name,
directives: directives(false),
arguments: _arguments,
};
} else {
idx = _idx;
Expand Down Expand Up @@ -404,14 +406,17 @@ function fragmentDefinition(): ast.FragmentDefinitionNode | undefined {
if (!_name) throw error('FragmentDefinition');
ignored();
const _typeCondition = typeCondition();

if (!_typeCondition) throw error('FragmentDefinition');
const _variableDefinitions = variableDefinitions();
const _directives = directives(false);
const _selectionSet = selectionSet();
if (!_selectionSet) throw error('FragmentDefinition');
return {
kind: 'FragmentDefinition' as Kind.FRAGMENT_DEFINITION,
name: _name,
typeCondition: _typeCondition,
variableDefinitions: _variableDefinitions.length ? _variableDefinitions : undefined,
directives: _directives,
selectionSet: _selectionSet,
};
Expand Down
4 changes: 4 additions & 0 deletions src/printer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ const nodes: {
},
FragmentSpread(node) {
let out = '...' + node.name.value;
if (hasItems(node.arguments)) out += '(' + node.arguments.map(nodes.Argument!).join(', ') + ')';
if (hasItems(node.directives)) out += ' ' + node.directives.map(nodes.Directive!).join(' ');
return out;
},
Expand All @@ -109,6 +110,9 @@ const nodes: {
FragmentDefinition(node) {
let out = 'fragment ' + node.name.value;
out += ' on ' + node.typeCondition.name.value;
if (hasItems(node.variableDefinitions)) {
out += '(' + node.variableDefinitions.map(nodes.VariableDefinition!).join(', ') + ')';
}
if (hasItems(node.directives)) out += ' ' + node.directives.map(nodes.Directive!).join(' ');
return out + ' ' + print(node.selectionSet);
},
Expand Down

0 comments on commit cb52a99

Please sign in to comment.