From f02630ece5494f0a5a618db73a4707099c3b7a8c Mon Sep 17 00:00:00 2001
From: Matt Mahoney <mmahoney@fb.com>
Date: Tue, 7 Aug 2018 13:50:49 -0400
Subject: [PATCH] VariableDefinition Directives: hide behind experimental flag

---
 src/language/__tests__/parser-test.js         |  4 +-
 src/language/__tests__/printer-test.js        |  2 +
 src/language/parser.js                        | 25 ++++++++++-
 .../__tests__/KnownDirectives-test.js         | 45 +++++++++++++++++--
 4 files changed, 70 insertions(+), 6 deletions(-)

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(`