Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/good-spiders-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@0no-co/graphql.web': minor
---

Add support for variable definitions on fragments and arguments on fragment spreads (Fragment Arguments Spec Addition)
2 changes: 2 additions & 0 deletions 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": undefined,
"directives": [
{
"arguments": undefined,
Expand Down Expand Up @@ -669,6 +670,7 @@ exports[`parse > parses the kitchen sink document like graphql.js does 1`] = `
"value": "Friend",
},
},
"variableDefinitions": undefined,
},
{
"directives": undefined,
Expand Down
99 changes: 99 additions & 0 deletions src/__tests__/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,105 @@ describe('parse', () => {
expect(() => parse('fragment Name on Type { field }')).not.toThrow();
});

it('parses fragment variable definitions', () => {
expect(parse('fragment x($var: Int = 1) on Type { field }').definitions[0]).toEqual({
kind: Kind.FRAGMENT_DEFINITION,
directives: undefined,
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: undefined,
},
],
selectionSet: {
kind: Kind.SELECTION_SET,
selections: [
{
alias: undefined,
kind: Kind.FIELD,
directives: undefined,
selectionSet: undefined,
arguments: undefined,
name: {
kind: Kind.NAME,
value: 'field',
},
},
],
},
});
});

it('parses fragment spread arguments', () => {
expect(parse('query x { ...x(varA: 2, varB: $var) }').definitions[0]).toHaveProperty(
'selectionSet.selections.0',
{
kind: Kind.FRAGMENT_SPREAD,
directives: undefined,
name: {
kind: Kind.NAME,
value: 'x',
},
arguments: [
{
kind: 'FragmentArgument',
name: {
kind: 'Name',
value: 'varA',
},
value: {
kind: 'IntValue',
value: '2',
},
},
{
kind: 'FragmentArgument',
name: {
kind: 'Name',
value: 'varB',
},
value: {
kind: 'Variable',
name: {
kind: 'Name',
value: 'var',
},
},
},
],
}
);
});

it('parses fields', () => {
expect(() => parse('{ field: }')).toThrow();
expect(() => parse('{ alias: field() }')).toThrow();
Expand Down
88 changes: 88 additions & 0 deletions src/__tests__/printer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,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($var: Int = 1) on Type {
field
}`);
});

it('prints fragment-spread with arguments', () => {
expect(
print({
kind: Kind.FRAGMENT_SPREAD,
directives: [],
name: {
kind: Kind.NAME,
value: 'x',
},
arguments: [
{
kind: 'FragmentArgument',
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
114 changes: 62 additions & 52 deletions src/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,52 +27,54 @@ import type {
InputObjectTypeExtensionNode,
} from './schemaAst';

export type ASTNode = Or<
GraphQL.ASTNode,
| NameNode
| DocumentNode
| OperationDefinitionNode
| VariableDefinitionNode
| VariableNode
| SelectionSetNode
| FieldNode
| ArgumentNode
| FragmentSpreadNode
| InlineFragmentNode
| FragmentDefinitionNode
| IntValueNode
| FloatValueNode
| StringValueNode
| BooleanValueNode
| NullValueNode
| EnumValueNode
| ListValueNode
| ObjectValueNode
| ObjectFieldNode
| DirectiveNode
| NamedTypeNode
| ListTypeNode
| NonNullTypeNode
| SchemaDefinitionNode
| OperationTypeDefinitionNode
| ScalarTypeDefinitionNode
| ObjectTypeDefinitionNode
| FieldDefinitionNode
| InputValueDefinitionNode
| InterfaceTypeDefinitionNode
| UnionTypeDefinitionNode
| EnumTypeDefinitionNode
| EnumValueDefinitionNode
| InputObjectTypeDefinitionNode
| DirectiveDefinitionNode
| SchemaExtensionNode
| ScalarTypeExtensionNode
| ObjectTypeExtensionNode
| InterfaceTypeExtensionNode
| UnionTypeExtensionNode
| EnumTypeExtensionNode
| InputObjectTypeExtensionNode
>;
export type ASTNode =
| Or<
GraphQL.ASTNode,
| NameNode
| DocumentNode
| OperationDefinitionNode
| VariableDefinitionNode
| VariableNode
| SelectionSetNode
| FieldNode
| ArgumentNode
| FragmentSpreadNode
| InlineFragmentNode
| FragmentDefinitionNode
| IntValueNode
| FloatValueNode
| StringValueNode
| BooleanValueNode
| NullValueNode
| EnumValueNode
| ListValueNode
| ObjectValueNode
| ObjectFieldNode
| DirectiveNode
| NamedTypeNode
| ListTypeNode
| NonNullTypeNode
| SchemaDefinitionNode
| OperationTypeDefinitionNode
| ScalarTypeDefinitionNode
| ObjectTypeDefinitionNode
| FieldDefinitionNode
| InputValueDefinitionNode
| InterfaceTypeDefinitionNode
| UnionTypeDefinitionNode
| EnumTypeDefinitionNode
| EnumValueDefinitionNode
| InputObjectTypeDefinitionNode
| DirectiveDefinitionNode
| SchemaExtensionNode
| ScalarTypeExtensionNode
| ObjectTypeExtensionNode
| InterfaceTypeExtensionNode
| UnionTypeExtensionNode
| EnumTypeExtensionNode
| InputObjectTypeExtensionNode
>
| FragmentArgumentNode;

export type NameNode = Or<
GraphQL.NameNode,
Expand Down Expand Up @@ -147,10 +149,7 @@ export type SelectionSetNode = Or<
}
>;

export declare type SelectionNode = Or<
GraphQL.SelectionNode,
FieldNode | FragmentSpreadNode | InlineFragmentNode
>;
export declare type SelectionNode = FieldNode | FragmentSpreadNode | InlineFragmentNode;

export type FieldNode = Or<
GraphQL.FieldNode,
Expand Down Expand Up @@ -185,6 +184,13 @@ export type ConstArgumentNode = Or<
}
>;

export type FragmentArgumentNode = {
readonly kind: 'FragmentArgument';
readonly name: NameNode;
readonly value: ValueNode;
readonly loc?: Location;
};

export type FragmentSpreadNode = Or<
GraphQL.FragmentSpreadNode,
{
Expand All @@ -193,7 +199,9 @@ export type FragmentSpreadNode = Or<
readonly directives?: ReadonlyArray<DirectiveNode>;
readonly loc?: Location;
}
>;
> & {
readonly arguments?: ReadonlyArray<FragmentArgumentNode>;
};

export type InlineFragmentNode = Or<
GraphQL.InlineFragmentNode,
Expand All @@ -217,7 +225,9 @@ export type FragmentDefinitionNode = Or<
readonly selectionSet: SelectionSetNode;
readonly loc?: Location;
}
>;
> & {
readonly variableDefinitions?: ReadonlyArray<VariableDefinitionNode>;
};

export type ValueNode = Or<
GraphQL.ValueNode,
Expand Down
Loading
Loading