Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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: '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
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