From 75c01d80aa721cb23ca745843912d791c8f6a0aa Mon Sep 17 00:00:00 2001 From: Ivan Goncharov Date: Mon, 23 Aug 2021 18:24:48 +0300 Subject: [PATCH] parser: add specialized error for extension with descriptions Fixes #2568 Fixes #2385 --- src/language/__tests__/schema-parser-test.ts | 10 +- src/language/parser.ts | 110 +++++++++---------- 2 files changed, 57 insertions(+), 63 deletions(-) diff --git a/src/language/__tests__/schema-parser-test.ts b/src/language/__tests__/schema-parser-test.ts index e596bba35d..3ad3b9497c 100644 --- a/src/language/__tests__/schema-parser-test.ts +++ b/src/language/__tests__/schema-parser-test.ts @@ -340,8 +340,9 @@ describe('Schema Parser', () => { world: String } `).to.deep.equal({ - message: 'Syntax Error: Unexpected Name "extend".', - locations: [{ line: 3, column: 7 }], + message: + 'Syntax Error: Unexpected description, descriptions are supported only on type definitions.', + locations: [{ line: 2, column: 7 }], }); expectSyntaxError(` @@ -361,8 +362,9 @@ describe('Schema Parser', () => { world: String } `).to.deep.equal({ - message: 'Syntax Error: Unexpected Name "extend".', - locations: [{ line: 3, column: 7 }], + message: + 'Syntax Error: Unexpected description, descriptions are supported only on type definitions.', + locations: [{ line: 2, column: 7 }], }); expectSyntaxError(` diff --git a/src/language/parser.ts b/src/language/parser.ts index 9ecd045670..d4c74a09a1 100644 --- a/src/language/parser.ts +++ b/src/language/parser.ts @@ -41,7 +41,6 @@ import type { NamedTypeNode, ListTypeNode, NonNullTypeNode, - TypeSystemDefinitionNode, SchemaDefinitionNode, OperationTypeDefinitionNode, ScalarTypeDefinitionNode, @@ -226,35 +225,72 @@ export class Parser { * ExecutableDefinition : * - OperationDefinition * - FragmentDefinition + * + * TypeSystemDefinition : + * - SchemaDefinition + * - TypeDefinition + * - DirectiveDefinition + * + * TypeDefinition : + * - ScalarTypeDefinition + * - ObjectTypeDefinition + * - InterfaceTypeDefinition + * - UnionTypeDefinition + * - EnumTypeDefinition + * - InputObjectTypeDefinition */ parseDefinition(): DefinitionNode { - if (this.peek(TokenKind.NAME)) { - switch (this._lexer.token.value) { - case 'query': - case 'mutation': - case 'subscription': - return this.parseOperationDefinition(); - case 'fragment': - return this.parseFragmentDefinition(); + if (this.peek(TokenKind.BRACE_L)) { + return this.parseOperationDefinition(); + } + + // Many definitions begin with a description and require a lookahead. + const hasDescription = this.peekDescription(); + const keywordToken = hasDescription + ? this._lexer.lookahead() + : this._lexer.token; + + if (keywordToken.kind === TokenKind.NAME) { + switch (keywordToken.value) { case 'schema': + return this.parseSchemaDefinition(); case 'scalar': + return this.parseScalarTypeDefinition(); case 'type': + return this.parseObjectTypeDefinition(); case 'interface': + return this.parseInterfaceTypeDefinition(); case 'union': + return this.parseUnionTypeDefinition(); case 'enum': + return this.parseEnumTypeDefinition(); case 'input': + return this.parseInputObjectTypeDefinition(); case 'directive': - return this.parseTypeSystemDefinition(); + return this.parseDirectiveDefinition(); + } + + if (hasDescription) { + throw syntaxError( + this._lexer.source, + this._lexer.token.start, + 'Unexpected description, descriptions are supported only on type definitions.', + ); + } + + switch (keywordToken.value) { + case 'query': + case 'mutation': + case 'subscription': + return this.parseOperationDefinition(); + case 'fragment': + return this.parseFragmentDefinition(); case 'extend': return this.parseTypeSystemExtension(); } - } else if (this.peek(TokenKind.BRACE_L)) { - return this.parseOperationDefinition(); - } else if (this.peekDescription()) { - return this.parseTypeSystemDefinition(); } - throw this.unexpected(); + throw this.unexpected(keywordToken); } // Implements the parsing rules in the Operations section. @@ -731,50 +767,6 @@ export class Parser { // Implements the parsing rules in the Type Definition section. - /** - * TypeSystemDefinition : - * - SchemaDefinition - * - TypeDefinition - * - DirectiveDefinition - * - * TypeDefinition : - * - ScalarTypeDefinition - * - ObjectTypeDefinition - * - InterfaceTypeDefinition - * - UnionTypeDefinition - * - EnumTypeDefinition - * - InputObjectTypeDefinition - */ - parseTypeSystemDefinition(): TypeSystemDefinitionNode { - // Many definitions begin with a description and require a lookahead. - const keywordToken = this.peekDescription() - ? this._lexer.lookahead() - : this._lexer.token; - - if (keywordToken.kind === TokenKind.NAME) { - switch (keywordToken.value) { - case 'schema': - return this.parseSchemaDefinition(); - case 'scalar': - return this.parseScalarTypeDefinition(); - case 'type': - return this.parseObjectTypeDefinition(); - case 'interface': - return this.parseInterfaceTypeDefinition(); - case 'union': - return this.parseUnionTypeDefinition(); - case 'enum': - return this.parseEnumTypeDefinition(); - case 'input': - return this.parseInputObjectTypeDefinition(); - case 'directive': - return this.parseDirectiveDefinition(); - } - } - - throw this.unexpected(keywordToken); - } - peekDescription(): boolean { return this.peek(TokenKind.STRING) || this.peek(TokenKind.BLOCK_STRING); }