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
10 changes: 9 additions & 1 deletion src/__testUtils__/kitchenSinkQuery.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
export const kitchenSinkQuery: string = String.raw`
query queryName($foo: ComplexType, $site: Site = MOBILE) @onQuery {
"Query description"
query queryName(
"Very complex variable"
$foo: ComplexType,
$site: Site = MOBILE
) @onQuery {
whoever123is: node(id: [123, 456]) {
id
... on User @onInlineFragment {
Expand Down Expand Up @@ -44,6 +49,9 @@ subscription StoryLikeSubscription(
}
}

"""
Fragment description
"""
fragment frag on Friend @onFragmentDefinition {
foo(
size: $size
Expand Down
347 changes: 347 additions & 0 deletions src/language/__tests__/parser-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ describe('Parser', () => {
definitions: [
{
kind: Kind.OPERATION_DEFINITION,
description: undefined,
loc: { start: 0, end: 40 },
operation: 'query',
name: undefined,
Expand Down Expand Up @@ -349,6 +350,7 @@ describe('Parser', () => {
{
kind: Kind.OPERATION_DEFINITION,
loc: { start: 0, end: 29 },
description: undefined,
operation: 'query',
name: undefined,
variableDefinitions: [],
Expand Down Expand Up @@ -395,6 +397,75 @@ describe('Parser', () => {
});
});

it('creates ast from nameless query with description', () => {
const result = parse(dedent`
"Description"
query {
node {
id
}
}
`);

expectJSON(result).toDeepEqual({
kind: Kind.DOCUMENT,
loc: { start: 0, end: 43 },
definitions: [
{
kind: Kind.OPERATION_DEFINITION,
loc: { start: 0, end: 43 },
description: {
kind: Kind.STRING,
loc: { start: 0, end: 13 },
value: 'Description',
block: false,
},
operation: 'query',
name: undefined,
variableDefinitions: [],
directives: [],
selectionSet: {
kind: Kind.SELECTION_SET,
loc: { start: 20, end: 43 },
selections: [
{
kind: Kind.FIELD,
loc: { start: 24, end: 41 },
alias: undefined,
name: {
kind: Kind.NAME,
loc: { start: 24, end: 28 },
value: 'node',
},
arguments: [],
directives: [],
selectionSet: {
kind: Kind.SELECTION_SET,
loc: { start: 29, end: 41 },
selections: [
{
kind: Kind.FIELD,
loc: { start: 35, end: 37 },
alias: undefined,
name: {
kind: Kind.NAME,
loc: { start: 35, end: 37 },
value: 'id',
},
arguments: [],
directives: [],
selectionSet: undefined,
},
],
},
},
],
},
},
],
});
});

it('allows parsing without source location information', () => {
const result = parse('{ id }', { noLocation: true });
expect('loc' in result).to.equal(false);
Expand Down Expand Up @@ -657,4 +728,280 @@ describe('Parser', () => {
});
});
});

describe('operation and variable definition descriptions', () => {
it('parses operation with description and variable descriptions', () => {
const result = parse(dedent`
"Operation description"
query myQuery(
"Variable a description"
$a: Int,
"""Variable b\nmultiline description"""
$b: String
) {
field(a: $a, b: $b)
}
`);

// Find the operation definition
const opDef = result.definitions.find(
(d) => d.kind === Kind.OPERATION_DEFINITION,
);
if (!opDef || opDef.kind !== Kind.OPERATION_DEFINITION) {
throw new Error('No operation definition found');
}

expectJSON(opDef).toDeepEqual({
kind: Kind.OPERATION_DEFINITION,
operation: 'query',
description: {
kind: Kind.STRING,
value: 'Operation description',
block: false,
loc: { start: 0, end: 23 },
},
name: {
kind: Kind.NAME,
value: 'myQuery',
loc: { start: 30, end: 37 },
},
variableDefinitions: [
{
kind: Kind.VARIABLE_DEFINITION,
description: {
kind: Kind.STRING,
value: 'Variable a description',
block: false,
loc: { start: 41, end: 65 },
},
variable: {
kind: Kind.VARIABLE,
name: {
kind: Kind.NAME,
value: 'a',
loc: { start: 69, end: 70 },
},
loc: { start: 68, end: 70 },
},
type: {
kind: Kind.NAMED_TYPE,
name: {
kind: Kind.NAME,
value: 'Int',
loc: { start: 72, end: 75 },
},
loc: { start: 72, end: 75 },
},
defaultValue: undefined,
directives: [],
loc: { start: 41, end: 75 },
},
{
kind: Kind.VARIABLE_DEFINITION,
description: {
kind: Kind.STRING,
value: 'Variable b\nmultiline description',
block: true,
loc: { start: 79, end: 117 },
},
variable: {
kind: Kind.VARIABLE,
name: {
kind: Kind.NAME,
value: 'b',
loc: { start: 121, end: 122 },
},
loc: { start: 120, end: 122 },
},
type: {
kind: Kind.NAMED_TYPE,
name: {
kind: Kind.NAME,
value: 'String',
loc: { start: 124, end: 130 },
},
loc: { start: 124, end: 130 },
},
defaultValue: undefined,
directives: [],
loc: { start: 79, end: 130 },
},
],
directives: [],
selectionSet: {
kind: Kind.SELECTION_SET,
selections: [
{
kind: Kind.FIELD,
alias: undefined,
name: {
kind: Kind.NAME,
value: 'field',
loc: { start: 137, end: 142 },
},
arguments: [
{
kind: Kind.ARGUMENT,
name: {
kind: Kind.NAME,
value: 'a',
loc: { start: 143, end: 144 },
},
value: {
kind: Kind.VARIABLE,
name: {
kind: Kind.NAME,
value: 'a',
loc: { start: 147, end: 148 },
},
loc: { start: 146, end: 148 },
},
loc: { start: 143, end: 148 },
},
{
kind: Kind.ARGUMENT,
name: {
kind: Kind.NAME,
value: 'b',
loc: { start: 150, end: 151 },
},
value: {
kind: Kind.VARIABLE,
name: {
kind: Kind.NAME,
value: 'b',
loc: { start: 154, end: 155 },
},
loc: { start: 153, end: 155 },
},
loc: { start: 150, end: 155 },
},
],
directives: [],
selectionSet: undefined,
loc: { start: 137, end: 156 },
},
],
loc: { start: 133, end: 158 },
},
loc: { start: 0, end: 158 },
});
});

it('descriptions on a short-hand query produce a sensible error', () => {
const input = `"""Invalid"""
{ __typename }`;
expect(() => parse(input)).to.throw(
'Syntax Error: Unexpected description, descriptions are not supported on shorthand queries.',
);
});

it('parses variable definition with description, default value, and directives', () => {
const result = parse(dedent`
query (
"desc"
$foo: Int = 42 @dir
) {
field(foo: $foo)
}
`);
const opDef = result.definitions.find(
(d) => d.kind === Kind.OPERATION_DEFINITION,
);
if (!opDef || opDef.kind !== Kind.OPERATION_DEFINITION) {
throw new Error('No operation definition found');
}
const varDef = opDef.variableDefinitions?.[0];
expectJSON(varDef).toDeepEqual({
kind: Kind.VARIABLE_DEFINITION,
defaultValue: {
kind: Kind.INT,
value: '42',
loc: { start: 31, end: 33 },
},
directives: [
{
arguments: [],
kind: Kind.DIRECTIVE,
name: {
kind: Kind.NAME,
value: 'dir',
loc: { start: 35, end: 38 },
},
loc: { start: 34, end: 38 },
},
],
description: {
kind: Kind.STRING,
value: 'desc',
block: false,
loc: { start: 10, end: 16 },
},
variable: {
kind: Kind.VARIABLE,
name: {
kind: Kind.NAME,
value: 'foo',
loc: { start: 20, end: 23 },
},
loc: { start: 19, end: 23 },
},
type: {
kind: Kind.NAMED_TYPE,
name: {
kind: Kind.NAME,
value: 'Int',
loc: { start: 25, end: 28 },
},
loc: { start: 25, end: 28 },
},
loc: { start: 10, end: 38 },
});
});

it('parses fragment with variable description (legacy)', () => {
const result = parse('fragment Foo("desc" $foo: Int) on Bar { baz }', {
allowLegacyFragmentVariables: true,
});

const fragDef = result.definitions.find(
(d) => d.kind === Kind.FRAGMENT_DEFINITION,
);
if (!fragDef || fragDef.kind !== Kind.FRAGMENT_DEFINITION) {
throw new Error('No fragment definition found');
}
const varDef = fragDef.variableDefinitions?.[0];

expectJSON(varDef).toDeepEqual({
kind: Kind.VARIABLE_DEFINITION,
description: {
kind: Kind.STRING,
value: 'desc',
block: false,
loc: { start: 13, end: 19 },
},
variable: {
kind: Kind.VARIABLE,
name: {
kind: Kind.NAME,
value: 'foo',
loc: { start: 21, end: 24 },
},
loc: { start: 20, end: 24 },
},
type: {
kind: Kind.NAMED_TYPE,
name: {
kind: Kind.NAME,
value: 'Int',
loc: { start: 26, end: 29 },
},
loc: { start: 26, end: 29 },
},
defaultValue: undefined,
directives: [],
loc: { start: 13, end: 29 },
});
});
});
});
Loading
Loading