Skip to content

Commit

Permalink
parser: add specialized error for extension with descriptions (#3242)
Browse files Browse the repository at this point in the history
Fixes #2568
Fixes #2385
  • Loading branch information
IvanGoncharov committed Aug 25, 2021
1 parent 91bc70b commit 4bb273d
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 63 deletions.
10 changes: 6 additions & 4 deletions src/language/__tests__/schema-parser-test.ts
Expand Up @@ -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(`
Expand All @@ -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(`
Expand Down
110 changes: 51 additions & 59 deletions src/language/parser.ts
Expand Up @@ -41,7 +41,6 @@ import type {
NamedTypeNode,
ListTypeNode,
NonNullTypeNode,
TypeSystemDefinitionNode,
SchemaDefinitionNode,
OperationTypeDefinitionNode,
ScalarTypeDefinitionNode,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
}
Expand Down

0 comments on commit 4bb273d

Please sign in to comment.