text: // @flow strict
  
  import { inspect as nodeInspect } from 'util';
  
  import { expect } from 'chai';
  import { describe, it } from 'mocha';
  
  import dedent from '../../__testUtils__/dedent';
  
  import inspect from '../../jsutils/inspect';
  
  import { Kind } from '../kinds';
  import { Source } from '../source';
  import { TokenKind } from '../tokenKind';
  import { parse, parseValue, parseType } from '../parser';
  
  import { kitchenSinkQuery } from '../../__fixtures__/index';
  
  import toJSONDeep from './toJSONDeep';
  
  function expectSyntaxError(text) {
    return expect(() => parse(text)).to.throw();
  }
  
  describe('Parser', () => {
    it('asserts that a source to parse was provided', () => {
      // $FlowExpectedError
      expect(() => parse()).to.throw('Must provide Source. Received: undefined.');
    });
  
    it('asserts that an invalid source to parse was provided', () => {
      // $FlowExpectedError
      expect(() => parse({})).to.throw('Must provide Source. Received: {}.');
    });
  
    it('parse provides useful errors', () => {
      let caughtError;
      try {
        parse('{');
      } catch (error) {
        caughtError = error;
      }
  
      expect(caughtError).to.deep.contain({
        message: 'Syntax Error: Expected Name, found <EOF>.',
        positions: [1],
        locations: [{ line: 1, column: 2 }],
      });
  
      expect(String(caughtError) + '\n').to.equal(dedent`
        Syntax Error: Expected Name, found <EOF>.
  
        GraphQL request:1:2
        1 | {
          |  ^
      `);
  
      expectSyntaxError(`
        { ...MissingOn }
        fragment MissingOn Type
      `).to.deep.include({
        message: 'Syntax Error: Expected "on", found Name "Type".',
        locations: [{ line: 3, column: 26 }],
      });
  
      expectSyntaxError('{ field: {} }').to.deep.include({
        message: 'Syntax Error: Expected Name, found "{".',
        locations: [{ line: 1, column: 10 }],
      });
  
      expectSyntaxError('notAnOperation Foo { field }').to.deep.include({
        message: 'Syntax Error: Unexpected Name "notAnOperation".',
        locations: [{ line: 1, column: 1 }],
      });
  
      expectSyntaxError('...').to.deep.include({
        message: 'Syntax Error: Unexpected "...".',
        locations: [{ line: 1, column: 1 }],
      });
  
      expectSyntaxError('{ ""').to.deep.include({
        message: 'Syntax Error: Expected Name, found String "".',
        locations: [{ line: 1, column: 3 }],
      });
    });
  
    it('parse provides useful error when using source', () => {
      let caughtError;
      try {
        parse(new Source('query', 'MyQuery.graphql'));
      } catch (error) {
        caughtError = error;
      }
      expect(String(caughtError) + '\n').to.equal(dedent`
        Syntax Error: Expected "{", found <EOF>.
  
        MyQuery.graphql:1:6
        1 | query
          |      ^
      `);
    });
  
    it('parses variable inline values', () => {
      expect(() =>
        parse('{ field(complex: { a: { b: [ $var ] } }) }'),
      ).to.not.throw();
    });
  
    it('parses constant default values', () => {
      expectSyntaxError(
        'query Foo($x: Complex = { a: { b: [ $var ] } }) { field }',
      ).to.deep.equal({
        message: 'Syntax Error: Unexpected "$".',
        locations: [{ line: 1, column: 37 }],
      });
    });
  
    it('parses variable definition directives', () => {
      expect(() =>
        parse('query Foo($x: Boolean = false @bar) { field }'),
      ).to.not.throw();
    });
  
    it('does not accept fragments named "on"', () => {
      expectSyntaxError('fragment on on on { on }').to.deep.equal({
        message: 'Syntax Error: Unexpected Name "on".',
        locations: [{ line: 1, column: 10 }],
      });
    });
  
    it('does not accept fragments spread of "on"', () => {
      expectSyntaxError('{ ...on }').to.deep.equal({
        message: 'Syntax Error: Expected Name, found "}".',
        locations: [{ line: 1, column: 9 }],
      });
    });
  
    it('parses multi-byte characters', () => {
      // Note: \u0A0A could be naively interpreted as two line-feed chars.
      const ast = parse(`
        # This comment has a \u0A0A multi-byte character.
        { field(arg: "Has a \u0A0A multi-byte character.") }
      `);
  
      expect(ast).to.have.nested.property(
        'definitions[0].selectionSet.selections[0].arguments[0].value.value',
        'Has a \u0A0A multi-byte character.',
      );
    });
  
    it('parses kitchen sink', () => {
      expect(() => parse(kitchenSinkQuery)).to.not.throw();
    });
  
    it('allows non-keywords anywhere a Name is allowed', () => {
      const nonKeywords = [
        'on',
        'fragment',
        'query',
        'mutation',
        'subscription',
        'true',
        'false',
      ];
      for (const keyword of nonKeywords) {
        // You can't define or reference a fragment named `on`.
        const fragmentName = keyword !== 'on' ? keyword : 'a';
        const document = `
          query ${keyword} {
            ... ${fragmentName}
            ... on ${keyword} { field }
          }
          fragment ${fragmentName} on Type {
            ${keyword}(${keyword}: $${keyword})
              @${keyword}(${keyword}: ${keyword})
          }
        `;
  
        expect(() => parse(document)).to.not.throw();
      }
    });
  
    it('parses anonymous mutation operations', () => {
      expect(() =>
        parse(`
        mutation {
          mutationField
        }
      `),
      ).to.not.throw();
    });
  
    it('parses anonymous subscription operations', () => {
      expect(() =>
        parse(`
        subscription {
          subscriptionField
        }
      `),
      ).to.not.throw();
    });
  
    it('parses named mutation operations', () => {
      expect(() =>
        parse(`
        mutation Foo {
          mutationField
        }
      `),
      ).to.not.throw();
    });
  
    it('parses named subscription operations', () => {
      expect(() =>
        parse(`
        subscription Foo {
          subscriptionField
        }
      `),
      ).to.not.throw();
    });
  
    it('creates ast', () => {
      const result = parse(dedent`
        {
          node(id: 4) {
            id,
            name
          }
        }
      `);
  
      expect(toJSONDeep(result)).to.deep.equal({
        kind: Kind.DOCUMENT,
        loc: { start: 0, end: 41 },
        definitions: [
          {
            kind: Kind.OPERATION_DEFINITION,
            loc: { start: 0, end: 40 },
            operation: 'query',
            name: undefined,
            variableDefinitions: [],
            directives: [],
            selectionSet: {
              kind: Kind.SELECTION_SET,
              loc: { start: 0, end: 40 },
              selections: [
                {
                  kind: Kind.FIELD,
                  loc: { start: 4, end: 38 },
                  alias: undefined,
                  name: {
                    kind: Kind.NAME,
                    loc: { start: 4, end: 8 },
                    value: 'node',
                  },
                  arguments: [
                    {
                      kind: Kind.ARGUMENT,
                      name: {
                        kind: Kind.NAME,
                        loc: { start: 9, end: 11 },
                        value: 'id',
                      },
                      value: {
                        kind: Kind.INT,
                        loc: { start: 13, end: 14 },
                        value: '4',
                      },
                      loc: { start: 9, end: 14 },
                    },
                  ],
                  directives: [],
                  selectionSet: {
                    kind: Kind.SELECTION_SET,
                    loc: { start: 16, end: 38 },
                    selections: [
                      {
                        kind: Kind.FIELD,
                        loc: { start: 22, end: 24 },
                        alias: undefined,
                        name: {
                          kind: Kind.NAME,
                          loc: { start: 22, end: 24 },
                          value: 'id',
                        },
                        arguments: [],
                        directives: [],
                        selectionSet: undefined,
                      },
                      {
                        kind: Kind.FIELD,
                        loc: { start: 30, end: 34 },
                        alias: undefined,
                        name: {
                          kind: Kind.NAME,
                          loc: { start: 30, end: 34 },
                          value: 'name',
                        },
                        arguments: [],
                        directives: [],
                        selectionSet: undefined,
                      },
                    ],
                  },
                },
              ],
            },
          },
        ],
      });
    });
  
    it('creates ast from nameless query without variables', () => {
      const result = parse(dedent`
        query {
          node {
            id
          }
        }
      `);
  
      expect(toJSONDeep(result)).to.deep.equal({
        kind: Kind.DOCUMENT,
        loc: { start: 0, end: 30 },
        definitions: [
          {
            kind: Kind.OPERATION_DEFINITION,
            loc: { start: 0, end: 29 },
            operation: 'query',
            name: undefined,
            variableDefinitions: [],
            directives: [],
            selectionSet: {
              kind: Kind.SELECTION_SET,
              loc: { start: 6, end: 29 },
              selections: [
                {
                  kind: Kind.FIELD,
                  loc: { start: 10, end: 27 },
                  alias: undefined,
                  name: {
                    kind: Kind.NAME,
                    loc: { start: 10, end: 14 },
                    value: 'node',
                  },
                  arguments: [],
                  directives: [],
                  selectionSet: {
                    kind: Kind.SELECTION_SET,
                    loc: { start: 15, end: 27 },
                    selections: [
                      {
                        kind: Kind.FIELD,
                        loc: { start: 21, end: 23 },
                        alias: undefined,
                        name: {
                          kind: Kind.NAME,
                          loc: { start: 21, end: 23 },
                          value: 'id',
                        },
                        arguments: [],
                        directives: [],
                        selectionSet: undefined,
                      },
                    ],
                  },
                },
              ],
            },
          },
        ],
      });
    });
  
    it('allows parsing without source location information', () => {
      const result = parse('{ id }', { noLocation: true });
      expect(result.loc).to.equal(undefined);
    });
  
    it('Experimental: allows parsing fragment defined variables', () => {
      const document = 'fragment a($v: Boolean = false) on t { f(v: $v) }';
  
      expect(() =>
        parse(document, { experimentalFragmentVariables: true }),
      ).to.not.throw();
      expect(() => parse(document)).to.throw('Syntax Error');
    });
  
    it('contains location information that only stringifies start/end', () => {
      const result = parse('{ id }');
  
      expect(JSON.stringify(result.loc)).to.equal('{"start":0,"end":6}');
      expect(nodeInspect(result.loc)).to.equal('{ start: 0, end: 6 }');
      expect(inspect(result.loc)).to.equal('{ start: 0, end: 6 }');
    });
  
    it('contains references to source', () => {
      const source = new Source('{ id }');
      const result = parse(source);
  
      expect(result).to.have.nested.property('loc.source', source);
    });
  
    it('contains references to start and end tokens', () => {
      const result = parse('{ id }');
  
      expect(result).to.have.nested.property(
        'loc.startToken.kind',
        TokenKind.SOF,
      );
      expect(result).to.have.nested.property('loc.endToken.kind', TokenKind.EOF);
    });
  
    describe('parseValue', () => {
      it('parses null value', () => {
        const result = parseValue('null');
        expect(toJSONDeep(result)).to.deep.equal({
          kind: Kind.NULL,
          loc: { start: 0, end: 4 },
        });
      });
  
      it('parses list values', () => {
        const result = parseValue('[123 "abc"]');
        expect(toJSONDeep(result)).to.deep.equal({
          kind: Kind.LIST,
          loc: { start: 0, end: 11 },
          values: [
            {
              kind: Kind.INT,
              loc: { start: 1, end: 4 },
              value: '123',
            },
            {
              kind: Kind.STRING,
              loc: { start: 5, end: 10 },
              value: 'abc',
              block: false,
            },
          ],
        });
      });
  
      it('parses block strings', () => {
        const result = parseValue('["""long""" "short"]');
        expect(toJSONDeep(result)).to.deep.equal({
          kind: Kind.LIST,
          loc: { start: 0, end: 20 },
          values: [
            {
              kind: Kind.STRING,
              loc: { start: 1, end: 11 },
              value: 'long',
              block: true,
            },
            {
              kind: Kind.STRING,
              loc: { start: 12, end: 19 },
              value: 'short',
              block: false,
            },
          ],
        });
      });
    });
  
    describe('parseType', () => {
      it('parses well known types', () => {
        const result = parseType('String');
        expect(toJSONDeep(result)).to.deep.equal({
          kind: Kind.NAMED_TYPE,
          loc: { start: 0, end: 6 },
          name: {
            kind: Kind.NAME,
            loc: { start: 0, end: 6 },
            value: 'String',
          },
        });
      });
  
      it('parses custom types', () => {
        const result = parseType('MyType');
        expect(toJSONDeep(result)).to.deep.equal({
          kind: Kind.NAMED_TYPE,
          loc: { start: 0, end: 6 },
          name: {
            kind: Kind.NAME,
            loc: { start: 0, end: 6 },
            value: 'MyType',
          },
        });
      });
  
      it('parses list types', () => {
        const result = parseType('[MyType]');
        expect(toJSONDeep(result)).to.deep.equal({
          kind: Kind.LIST_TYPE,
          loc: { start: 0, end: 8 },
          type: {
            kind: Kind.NAMED_TYPE,
            loc: { start: 1, end: 7 },
            name: {
              kind: Kind.NAME,
              loc: { start: 1, end: 7 },
              value: 'MyType',
            },
          },
        });
      });
  
      it('parses non-null types', () => {
        const result = parseType('MyType!');
        expect(toJSONDeep(result)).to.deep.equal({
          kind: Kind.NON_NULL_TYPE,
          loc: { start: 0, end: 7 },
          type: {
            kind: Kind.NAMED_TYPE,
            loc: { start: 0, end: 6 },
            name: {
              kind: Kind.NAME,
              loc: { start: 0, end: 6 },
              value: 'MyType',
            },
          },
        });
      });
  
      it('parses nested types', () => {
        const result = parseType('[MyType!]');
        expect(toJSONDeep(result)).to.deep.equal({
          kind: Kind.LIST_TYPE,
          loc: { start: 0, end: 9 },
          type: {
            kind: Kind.NON_NULL_TYPE,
            loc: { start: 1, end: 8 },
            type: {
              kind: Kind.NAMED_TYPE,
              loc: { start: 1, end: 7 },
              name: {
                kind: Kind.NAME,
                loc: { start: 1, end: 7 },
                value: 'MyType',
              },
            },
          },
        });
      });
    });
  });
  