diff --git a/src/language/__tests__/parser-test.js b/src/language/__tests__/parser-test.js index e6811a0fe0..5aedf08ebf 100644 --- a/src/language/__tests__/parser-test.js +++ b/src/language/__tests__/parser-test.js @@ -108,7 +108,9 @@ describe('Parser', () => { it('parses variable definition directives', () => { expect(() => - parse('query Foo($x: Boolean = false @bar) { field }'), + parse('query Foo($x: Boolean = false @bar) { field }', { + experimentalVariableDefinitionDirectives: true, + }), ).to.not.throw(); }); diff --git a/src/language/__tests__/printer-test.js b/src/language/__tests__/printer-test.js index 1bcb35f55f..32b303e6a9 100644 --- a/src/language/__tests__/printer-test.js +++ b/src/language/__tests__/printer-test.js @@ -56,6 +56,7 @@ describe('Printer: Query document', () => { const queryAstWithArtifacts = parse( 'query ($foo: TestType) @testDirective { id, name }', + { experimentalVariableDefinitionDirectives: true }, ); expect(print(queryAstWithArtifacts)).to.equal(dedent` query ($foo: TestType) @testDirective { @@ -66,6 +67,7 @@ describe('Printer: Query document', () => { const queryAstWithVariableDirective = parse( 'query ($foo: TestType = {a: 123} @testDirective(if: true) @test) { id }', + { experimentalVariableDefinitionDirectives: true }, ); expect(print(queryAstWithVariableDirective)).to.equal(dedent` query ($foo: TestType = {a: 123} @testDirective(if: true) @test) { diff --git a/src/language/parser.js b/src/language/parser.js index 21241ec17e..f4d682f9af 100644 --- a/src/language/parser.js +++ b/src/language/parser.js @@ -115,6 +115,17 @@ export type ParseOptions = { * future. */ experimentalFragmentVariables?: boolean, + + /** + * EXPERIMENTAL: + * + * If enabled, the parser understands directives on variable definitions: + * + * query Foo($var: String = "abc" @variable_definition_directive) { + * ... + * } + */ + experimentalVariableDefinitionDirectives?: boolean, }; /** @@ -336,6 +347,19 @@ function parseVariableDefinitions( */ function parseVariableDefinition(lexer: Lexer<*>): VariableDefinitionNode { const start = lexer.token; + if (lexer.options.experimentalVariableDefinitionDirectives) { + return { + kind: Kind.VARIABLE_DEFINITION, + variable: parseVariable(lexer), + type: (expect(lexer, TokenKind.COLON), parseTypeReference(lexer)), + defaultValue: skip(lexer, TokenKind.EQUALS) + ? parseValueLiteral(lexer, true) + : undefined, + directives: parseDirectives(lexer, true), + loc: loc(lexer, start), + }; + } + return { kind: Kind.VARIABLE_DEFINITION, variable: parseVariable(lexer), @@ -343,7 +367,6 @@ function parseVariableDefinition(lexer: Lexer<*>): VariableDefinitionNode { defaultValue: skip(lexer, TokenKind.EQUALS) ? parseValueLiteral(lexer, true) : undefined, - directives: parseDirectives(lexer, true), loc: loc(lexer, start), }; } diff --git a/src/validation/__tests__/KnownDirectives-test.js b/src/validation/__tests__/KnownDirectives-test.js index a84382ad77..e210b3a695 100644 --- a/src/validation/__tests__/KnownDirectives-test.js +++ b/src/validation/__tests__/KnownDirectives-test.js @@ -5,12 +5,16 @@ * LICENSE file in the root directory of this source tree. */ +import { expect } from 'chai'; import { describe, it } from 'mocha'; +import { parse } from '../../language'; import { buildSchema } from '../../utilities'; +import { validate } from '../validate'; import { expectPassesRule, expectFailsRule, expectSDLErrorsFromRule, + testSchema, } from './harness'; import { @@ -127,7 +131,7 @@ describe('Validate: Known directives', () => { expectPassesRule( KnownDirectives, ` - query Foo($var: Boolean @onVariableDefinition) @onQuery { + query Foo($var: Boolean) @onQuery { name @include(if: $var) ...Frag @include(if: true) skippedField @skip(if: true) @@ -141,11 +145,26 @@ describe('Validate: Known directives', () => { ); }); + it('with well placed variable definition directive', () => { + // Need to parse with experimental flag + const queryString = ` + query Foo($var: Boolean @onVariableDefinition) { + name + } + `; + const errors = validate( + testSchema, + parse(queryString, { experimentalVariableDefinitionDirectives: true }), + [KnownDirectives], + ); + expect(errors).to.deep.equal([], 'Should validate'); + }); + it('with misplaced directives', () => { expectFailsRule( KnownDirectives, ` - query Foo($var: Boolean @onField) @include(if: true) { + query Foo($var: Boolean) @include(if: true) { name @onQuery @include(if: $var) ...Frag @onQuery } @@ -155,8 +174,7 @@ describe('Validate: Known directives', () => { } `, [ - misplacedDirective('onField', 'VARIABLE_DEFINITION', 2, 31), - misplacedDirective('include', 'QUERY', 2, 41), + misplacedDirective('include', 'QUERY', 2, 32), misplacedDirective('onQuery', 'FIELD', 3, 14), misplacedDirective('onQuery', 'FRAGMENT_SPREAD', 4, 17), misplacedDirective('onQuery', 'MUTATION', 7, 20), @@ -164,6 +182,25 @@ describe('Validate: Known directives', () => { ); }); + it('with misplaced variable definition directive', () => { + // Need to parse with experimental flag + const queryString = ` + query Foo($var: Boolean @onField) { + name + } + `; + const errors = validate( + testSchema, + parse(queryString, { experimentalVariableDefinitionDirectives: true }), + [KnownDirectives], + ); + const expectedErrors = [ + misplacedDirective('onField', 'VARIABLE_DEFINITION', 2, 31), + ]; + expect(errors).to.have.length.of.at.least(1, 'Should not validate'); + expect(errors).to.deep.equal(expectedErrors); + }); + describe('within SDL', () => { it('with directive defined inside SDL', () => { expectSDLErrors(`