Skip to content

Commit

Permalink
Add flag for parser
Browse files Browse the repository at this point in the history
  • Loading branch information
mjmahone committed Feb 9, 2023
1 parent 10a3021 commit 02db413
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 18 deletions.
2 changes: 1 addition & 1 deletion src/__testUtils__/kitchenSinkQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ query queryName($foo: ComplexType, $site: Site = MOBILE) @onQuery {
...frag @onFragmentSpread
}
}
field3!
field4?
requiredField5: field5!
Expand Down
2 changes: 1 addition & 1 deletion src/execution/__tests__/variables-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ function executeQuery(
query: string,
variableValues?: { [variable: string]: unknown },
) {
const document = parse(query);
const document = parse(query, { experimentalFragmentArguments: true });
return executeSync({ schema, document, variableValues });
}

Expand Down
22 changes: 19 additions & 3 deletions src/language/__tests__/parser-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -607,16 +607,32 @@ describe('Parser', () => {
expect('loc' in result).to.equal(false);
});

it('allows parsing fragment defined arguments', () => {
it('allows parsing fragment defined variables', () => {
const document = 'fragment a($v: Boolean = false) on t { f(v: $v) }';

expect(() => parse(document)).to.not.throw();
expect(() =>
parse(document, { experimentalFragmentArguments: true }),
).to.not.throw();
});

it('disallows parsing fragment defined variables without experimental flag', () => {
const document = 'fragment a($v: Boolean = false) on t { f(v: $v) }';

expect(() => parse(document)).to.throw();
});

it('allows parsing fragment spread arguments', () => {
const document = 'fragment a on t { ...b(v: $v) }';

expect(() => parse(document)).to.not.throw();
expect(() =>
parse(document, { experimentalFragmentArguments: true }),
).to.not.throw();
});

it('disallows parsing fragment spread arguments without experimental flag', () => {
const document = 'fragment a on t { ...b(v: $v) }';

expect(() => parse(document)).to.throw();
});

it('contains location that can be Object.toStringified, JSON.stringified, or jsutils.inspected', () => {
Expand Down
4 changes: 4 additions & 0 deletions src/language/__tests__/printer-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ describe('Printer: Query document', () => {
it('prints fragment with argument definition directives', () => {
const fragmentWithArgumentDefinitionDirective = parse(
'fragment Foo($foo: TestType @test) on TestType @testDirective { id }',
{ experimentalFragmentArguments: true },
);
expect(print(fragmentWithArgumentDefinitionDirective)).to.equal(dedent`
fragment Foo($foo: TestType @test) on TestType @testDirective {
Expand All @@ -128,6 +129,7 @@ describe('Printer: Query document', () => {
id
}
`,
{ experimentalFragmentArguments: true },
);
expect(print(fragmentWithArgumentDefinition)).to.equal(dedent`
fragment Foo($a: ComplexType, $b: Boolean = false) on TestType {
Expand All @@ -139,6 +141,7 @@ describe('Printer: Query document', () => {
it('prints fragment spread with arguments', () => {
const fragmentSpreadWithArguments = parse(
'fragment Foo on TestType { ...Bar(a: {x: $x}, b: true) }',
{ experimentalFragmentArguments: true },
);
expect(print(fragmentSpreadWithArguments)).to.equal(dedent`
fragment Foo on TestType {
Expand All @@ -150,6 +153,7 @@ describe('Printer: Query document', () => {
it('prints fragment spread with multi-line arguments', () => {
const fragmentSpreadWithArguments = parse(
'fragment Foo on TestType { ...Bar(a: {x: $x, y: $y, z: $z, xy: $xy}, b: true, c: "a long string extending arguments over max length") }',
{ experimentalFragmentArguments: true },
);
expect(print(fragmentSpreadWithArguments)).to.equal(dedent`
fragment Foo on TestType {
Expand Down
2 changes: 2 additions & 0 deletions src/language/__tests__/visitor-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,7 @@ describe('Visitor', () => {
it('visits arguments defined on fragments', () => {
const ast = parse('fragment a($v: Boolean = false) on t { f }', {
noLocation: true,
experimentalFragmentArguments: true,
});
const visited: Array<any> = [];

Expand Down Expand Up @@ -507,6 +508,7 @@ describe('Visitor', () => {
it('visits arguments on fragment spreads', () => {
const ast = parse('fragment a on t { ...s(v: false) }', {
noLocation: true,
experimentalFragmentArguments: true,
});
const visited: Array<any> = [];

Expand Down
30 changes: 28 additions & 2 deletions src/language/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,27 @@ export interface ParseOptions {
*/
maxTokens?: number | undefined;

/**
* EXPERIMENTAL:
*
* If enabled, the parser will understand and parse fragment variable definitions
* and arguments on fragment spreads. Fragment variable definitions will be represented
* in the `variableDefinitions` field of the FragmentDefinitionNode.
* Fragment spread arguments will be represented in the `arguments` field of FragmentSpreadNode.
*
* For example:
*
* ```graphql
* {
* t { ...A(var: true) }
* }
* fragment A($var: Boolean = false) on T {
* ...B(x: $var)
* }
* ```
*/
experimentalFragmentArguments?: boolean | undefined;

/**
* EXPERIMENTAL:
*
Expand Down Expand Up @@ -544,7 +565,10 @@ export class Parser {
const hasTypeCondition = this.expectOptionalKeyword('on');
if (!hasTypeCondition && this.peek(TokenKind.NAME)) {
const name = this.parseFragmentName();
if (this.peek(TokenKind.PAREN_L)) {
if (
this.peek(TokenKind.PAREN_L) &&
this._options.experimentalFragmentArguments
) {
return this.node<FragmentSpreadNode>(start, {
kind: Kind.FRAGMENT_SPREAD,
name,
Expand Down Expand Up @@ -578,7 +602,9 @@ export class Parser {
return this.node<FragmentDefinitionNode>(start, {
kind: Kind.FRAGMENT_DEFINITION,
name: this.parseFragmentName(),
variableDefinitions: this.parseVariableDefinitions(),
variableDefinitions: this._options.experimentalFragmentArguments
? this.parseVariableDefinitions()
: undefined,
typeCondition: (this.expectKeyword('on'), this.parseNamedType()),
directives: this.parseDirectives(false),
selectionSet: this.parseSelectionSet(),
Expand Down
23 changes: 13 additions & 10 deletions src/utilities/__tests__/TypeInfo-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -519,17 +519,20 @@ describe('visitWithTypeInfo', () => {
it('supports traversals of fragment arguments', () => {
const typeInfo = new TypeInfo(testSchema);

const ast = parse(`
query {
...Foo(x: 4)
}
const ast = parse(
`
query {
...Foo(x: 4)
}
fragment Foo(
$x: ID!
) on QueryRoot {
human(id: $x) { name }
}
`);
fragment Foo(
$x: ID!
) on QueryRoot {
human(id: $x) { name }
}
`,
{ experimentalFragmentArguments: true },
);

const visited: Array<any> = [];
visit(
Expand Down
2 changes: 1 addition & 1 deletion src/validation/__tests__/harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export function expectValidationErrorsWithSchema(
rule: ValidationRule,
queryStr: string,
): any {
const doc = parse(queryStr);
const doc = parse(queryStr, { experimentalFragmentArguments: true });
const errors = validate(schema, doc, [rule]);
return expectJSON(errors);
}
Expand Down

0 comments on commit 02db413

Please sign in to comment.