From 002fb91b25c035403dbaf785cd04c3d1c6a2e004 Mon Sep 17 00:00:00 2001 From: Lee Byron Date: Wed, 5 May 2021 20:42:58 -0700 Subject: [PATCH] Improve parser location API (#3061) This replaces manual assignment of `loc` on each node in Parser with a `node()` function. This simplifies the parser and also ensures the `loc` field does not appear at all when `noLocation` is provided. --- src/language/parser.d.ts | 7 +- src/language/parser.js | 242 ++++++++++++++++----------------------- src/language/visitor.js | 13 +-- 3 files changed, 104 insertions(+), 158 deletions(-) diff --git a/src/language/parser.d.ts b/src/language/parser.d.ts index 443139c792..ed7b27c0b4 100644 --- a/src/language/parser.d.ts +++ b/src/language/parser.d.ts @@ -3,7 +3,6 @@ import { GraphQLError } from '../error/GraphQLError'; import { Token, - Location, NameNode, VariableNode, DocumentNode, @@ -501,9 +500,11 @@ export declare class Parser { parseDirectiveLocation(): NameNode; /** - * Returns a location object, used to identify the place in the source that created a given parsed object. + * Returns a node that, if configured to do so, sets a "loc" field as a + * location object, used to identify the place in the source that created a + * given parsed object. */ - loc(startToken: Token): Location | undefined; + node(startToken: Token, node: T): T; /** * Determines if the next token is of a given kind diff --git a/src/language/parser.js b/src/language/parser.js index f5f0a876b1..e7c3c51066 100644 --- a/src/language/parser.js +++ b/src/language/parser.js @@ -164,11 +164,10 @@ export class Parser { */ parseName(): NameNode { const token = this.expectToken(TokenKind.NAME); - return { + return this.node(token, { kind: Kind.NAME, value: ((token.value: any): string), - loc: this.loc(token), - }; + }); } // Implements the parsing rules in the Document section. @@ -177,16 +176,14 @@ export class Parser { * Document : Definition+ */ parseDocument(): DocumentNode { - const start = this._lexer.token; - return { + return this.node(this._lexer.token, { kind: Kind.DOCUMENT, definitions: this.many( TokenKind.SOF, this.parseDefinition, TokenKind.EOF, ), - loc: this.loc(start), - }; + }); } /** @@ -239,30 +236,28 @@ export class Parser { parseOperationDefinition(): OperationDefinitionNode { const start = this._lexer.token; if (this.peek(TokenKind.BRACE_L)) { - return { + return this.node(start, { kind: Kind.OPERATION_DEFINITION, operation: 'query', name: undefined, variableDefinitions: [], directives: [], selectionSet: this.parseSelectionSet(), - loc: this.loc(start), - }; + }); } const operation = this.parseOperationType(); let name; if (this.peek(TokenKind.NAME)) { name = this.parseName(); } - return { + return this.node(start, { kind: Kind.OPERATION_DEFINITION, operation, name, variableDefinitions: this.parseVariableDefinitions(), directives: this.parseDirectives(false), selectionSet: this.parseSelectionSet(), - loc: this.loc(start), - }; + }); } /** @@ -297,8 +292,7 @@ export class Parser { * VariableDefinition : Variable : Type DefaultValue? Directives[Const]? */ parseVariableDefinition(): VariableDefinitionNode { - const start = this._lexer.token; - return { + return this.node(this._lexer.token, { kind: Kind.VARIABLE_DEFINITION, variable: this.parseVariable(), type: (this.expectToken(TokenKind.COLON), this.parseTypeReference()), @@ -306,8 +300,7 @@ export class Parser { ? this.parseValueLiteral(true) : undefined, directives: this.parseDirectives(true), - loc: this.loc(start), - }; + }); } /** @@ -316,27 +309,24 @@ export class Parser { parseVariable(): VariableNode { const start = this._lexer.token; this.expectToken(TokenKind.DOLLAR); - return { + return this.node(start, { kind: Kind.VARIABLE, name: this.parseName(), - loc: this.loc(start), - }; + }); } /** * SelectionSet : { Selection+ } */ parseSelectionSet(): SelectionSetNode { - const start = this._lexer.token; - return { + return this.node(this._lexer.token, { kind: Kind.SELECTION_SET, selections: this.many( TokenKind.BRACE_L, this.parseSelection, TokenKind.BRACE_R, ), - loc: this.loc(start), - }; + }); } /** @@ -369,7 +359,7 @@ export class Parser { name = nameOrAlias; } - return { + return this.node(start, { kind: Kind.FIELD, alias, name, @@ -378,8 +368,7 @@ export class Parser { selectionSet: this.peek(TokenKind.BRACE_L) ? this.parseSelectionSet() : undefined, - loc: this.loc(start), - }; + }); } /** @@ -398,22 +387,19 @@ export class Parser { const name = this.parseName(); this.expectToken(TokenKind.COLON); - return { + return this.node(start, { kind: Kind.ARGUMENT, name, value: this.parseValueLiteral(false), - loc: this.loc(start), - }; + }); } parseConstArgument(): ArgumentNode { - const start = this._lexer.token; - return { + return this.node(this._lexer.token, { kind: Kind.ARGUMENT, name: this.parseName(), value: (this.expectToken(TokenKind.COLON), this.parseValueLiteral(true)), - loc: this.loc(start), - }; + }); } // Implements the parsing rules in the Fragments section. @@ -431,20 +417,18 @@ export class Parser { const hasTypeCondition = this.expectOptionalKeyword('on'); if (!hasTypeCondition && this.peek(TokenKind.NAME)) { - return { + return this.node(start, { kind: Kind.FRAGMENT_SPREAD, name: this.parseFragmentName(), directives: this.parseDirectives(false), - loc: this.loc(start), - }; + }); } - return { + return this.node(start, { kind: Kind.INLINE_FRAGMENT, typeCondition: hasTypeCondition ? this.parseNamedType() : undefined, directives: this.parseDirectives(false), selectionSet: this.parseSelectionSet(), - loc: this.loc(start), - }; + }); } /** @@ -460,24 +444,22 @@ export class Parser { // the grammar of FragmentDefinition: // - fragment FragmentName VariableDefinitions? on TypeCondition Directives? SelectionSet if (this._options?.allowLegacyFragmentVariables === true) { - return { + return this.node(start, { kind: Kind.FRAGMENT_DEFINITION, name: this.parseFragmentName(), variableDefinitions: this.parseVariableDefinitions(), typeCondition: (this.expectKeyword('on'), this.parseNamedType()), directives: this.parseDirectives(false), selectionSet: this.parseSelectionSet(), - loc: this.loc(start), - }; + }); } - return { + return this.node(start, { kind: Kind.FRAGMENT_DEFINITION, name: this.parseFragmentName(), typeCondition: (this.expectKeyword('on'), this.parseNamedType()), directives: this.parseDirectives(false), selectionSet: this.parseSelectionSet(), - loc: this.loc(start), - }; + }); } /** @@ -519,18 +501,16 @@ export class Parser { return this.parseObject(isConst); case TokenKind.INT: this._lexer.advance(); - return { + return this.node(token, { kind: Kind.INT, value: ((token.value: any): string), - loc: this.loc(token), - }; + }); case TokenKind.FLOAT: this._lexer.advance(); - return { + return this.node(token, { kind: Kind.FLOAT, value: ((token.value: any): string), - loc: this.loc(token), - }; + }); case TokenKind.STRING: case TokenKind.BLOCK_STRING: return this.parseStringLiteral(); @@ -538,17 +518,16 @@ export class Parser { this._lexer.advance(); switch (token.value) { case 'true': - return { kind: Kind.BOOLEAN, value: true, loc: this.loc(token) }; + return this.node(token, { kind: Kind.BOOLEAN, value: true }); case 'false': - return { kind: Kind.BOOLEAN, value: false, loc: this.loc(token) }; + return this.node(token, { kind: Kind.BOOLEAN, value: false }); case 'null': - return { kind: Kind.NULL, loc: this.loc(token) }; + return this.node(token, { kind: Kind.NULL }); default: - return { + return this.node(token, { kind: Kind.ENUM, value: ((token.value: any): string), - loc: this.loc(token), - }; + }); } case TokenKind.DOLLAR: if (!isConst) { @@ -562,12 +541,11 @@ export class Parser { parseStringLiteral(): StringValueNode { const token = this._lexer.token; this._lexer.advance(); - return { + return this.node(token, { kind: Kind.STRING, value: ((token.value: any): string), block: token.kind === TokenKind.BLOCK_STRING, - loc: this.loc(token), - }; + }); } /** @@ -576,13 +554,11 @@ export class Parser { * - [ Value[?Const]+ ] */ parseList(isConst: boolean): ListValueNode { - const start = this._lexer.token; const item = () => this.parseValueLiteral(isConst); - return { + return this.node(this._lexer.token, { kind: Kind.LIST, values: this.any(TokenKind.BRACKET_L, item, TokenKind.BRACKET_R), - loc: this.loc(start), - }; + }); } /** @@ -591,13 +567,11 @@ export class Parser { * - { ObjectField[?Const]+ } */ parseObject(isConst: boolean): ObjectValueNode { - const start = this._lexer.token; const item = () => this.parseObjectField(isConst); - return { + return this.node(this._lexer.token, { kind: Kind.OBJECT, fields: this.any(TokenKind.BRACE_L, item, TokenKind.BRACE_R), - loc: this.loc(start), - }; + }); } /** @@ -608,12 +582,11 @@ export class Parser { const name = this.parseName(); this.expectToken(TokenKind.COLON); - return { + return this.node(start, { kind: Kind.OBJECT_FIELD, name, value: this.parseValueLiteral(isConst), - loc: this.loc(start), - }; + }); } // Implements the parsing rules in the Directives section. @@ -635,12 +608,11 @@ export class Parser { parseDirective(isConst: boolean): DirectiveNode { const start = this._lexer.token; this.expectToken(TokenKind.AT); - return { + return this.node(start, { kind: Kind.DIRECTIVE, name: this.parseName(), arguments: this.parseArguments(isConst), - loc: this.loc(start), - }; + }); } // Implements the parsing rules in the Types section. @@ -657,21 +629,19 @@ export class Parser { if (this.expectOptionalToken(TokenKind.BRACKET_L)) { const innerType = this.parseTypeReference(); this.expectToken(TokenKind.BRACKET_R); - type = { + type = this.node(start, { kind: Kind.LIST_TYPE, type: innerType, - loc: this.loc(start), - }; + }); } else { type = this.parseNamedType(); } if (this.expectOptionalToken(TokenKind.BANG)) { - return { + return this.node(start, { kind: Kind.NON_NULL_TYPE, type, - loc: this.loc(start), - }; + }); } return type; } @@ -680,12 +650,10 @@ export class Parser { * NamedType : Name */ parseNamedType(): NamedTypeNode { - const start = this._lexer.token; - return { + return this.node(this._lexer.token, { kind: Kind.NAMED_TYPE, name: this.parseName(), - loc: this.loc(start), - }; + }); } // Implements the parsing rules in the Type Definition section. @@ -760,13 +728,12 @@ export class Parser { this.parseOperationTypeDefinition, TokenKind.BRACE_R, ); - return { + return this.node(start, { kind: Kind.SCHEMA_DEFINITION, description, directives, operationTypes, - loc: this.loc(start), - }; + }); } /** @@ -777,12 +744,11 @@ export class Parser { const operation = this.parseOperationType(); this.expectToken(TokenKind.COLON); const type = this.parseNamedType(); - return { + return this.node(start, { kind: Kind.OPERATION_TYPE_DEFINITION, operation, type, - loc: this.loc(start), - }; + }); } /** @@ -794,13 +760,12 @@ export class Parser { this.expectKeyword('scalar'); const name = this.parseName(); const directives = this.parseDirectives(true); - return { + return this.node(start, { kind: Kind.SCALAR_TYPE_DEFINITION, description, name, directives, - loc: this.loc(start), - }; + }); } /** @@ -816,15 +781,14 @@ export class Parser { const interfaces = this.parseImplementsInterfaces(); const directives = this.parseDirectives(true); const fields = this.parseFieldsDefinition(); - return { + return this.node(start, { kind: Kind.OBJECT_TYPE_DEFINITION, description, name, interfaces, directives, fields, - loc: this.loc(start), - }; + }); } /** @@ -861,15 +825,14 @@ export class Parser { this.expectToken(TokenKind.COLON); const type = this.parseTypeReference(); const directives = this.parseDirectives(true); - return { + return this.node(start, { kind: Kind.FIELD_DEFINITION, description, name, arguments: args, type, directives, - loc: this.loc(start), - }; + }); } /** @@ -898,15 +861,14 @@ export class Parser { defaultValue = this.parseValueLiteral(true); } const directives = this.parseDirectives(true); - return { + return this.node(start, { kind: Kind.INPUT_VALUE_DEFINITION, description, name, type, defaultValue, directives, - loc: this.loc(start), - }; + }); } /** @@ -921,15 +883,14 @@ export class Parser { const interfaces = this.parseImplementsInterfaces(); const directives = this.parseDirectives(true); const fields = this.parseFieldsDefinition(); - return { + return this.node(start, { kind: Kind.INTERFACE_TYPE_DEFINITION, description, name, interfaces, directives, fields, - loc: this.loc(start), - }; + }); } /** @@ -943,14 +904,13 @@ export class Parser { const name = this.parseName(); const directives = this.parseDirectives(true); const types = this.parseUnionMemberTypes(); - return { + return this.node(start, { kind: Kind.UNION_TYPE_DEFINITION, description, name, directives, types, - loc: this.loc(start), - }; + }); } /** @@ -975,14 +935,13 @@ export class Parser { const name = this.parseName(); const directives = this.parseDirectives(true); const values = this.parseEnumValuesDefinition(); - return { + return this.node(start, { kind: Kind.ENUM_TYPE_DEFINITION, description, name, directives, values, - loc: this.loc(start), - }; + }); } /** @@ -1006,13 +965,12 @@ export class Parser { const description = this.parseDescription(); const name = this.parseName(); const directives = this.parseDirectives(true); - return { + return this.node(start, { kind: Kind.ENUM_VALUE_DEFINITION, description, name, directives, - loc: this.loc(start), - }; + }); } /** @@ -1026,14 +984,13 @@ export class Parser { const name = this.parseName(); const directives = this.parseDirectives(true); const fields = this.parseInputFieldsDefinition(); - return { + return this.node(start, { kind: Kind.INPUT_OBJECT_TYPE_DEFINITION, description, name, directives, fields, - loc: this.loc(start), - }; + }); } /** @@ -1103,12 +1060,11 @@ export class Parser { if (directives.length === 0 && operationTypes.length === 0) { throw this.unexpected(); } - return { + return this.node(start, { kind: Kind.SCHEMA_EXTENSION, directives, operationTypes, - loc: this.loc(start), - }; + }); } /** @@ -1124,12 +1080,11 @@ export class Parser { if (directives.length === 0) { throw this.unexpected(); } - return { + return this.node(start, { kind: Kind.SCALAR_TYPE_EXTENSION, name, directives, - loc: this.loc(start), - }; + }); } /** @@ -1153,14 +1108,13 @@ export class Parser { ) { throw this.unexpected(); } - return { + return this.node(start, { kind: Kind.OBJECT_TYPE_EXTENSION, name, interfaces, directives, fields, - loc: this.loc(start), - }; + }); } /** @@ -1184,14 +1138,13 @@ export class Parser { ) { throw this.unexpected(); } - return { + return this.node(start, { kind: Kind.INTERFACE_TYPE_EXTENSION, name, interfaces, directives, fields, - loc: this.loc(start), - }; + }); } /** @@ -1209,13 +1162,12 @@ export class Parser { if (directives.length === 0 && types.length === 0) { throw this.unexpected(); } - return { + return this.node(start, { kind: Kind.UNION_TYPE_EXTENSION, name, directives, types, - loc: this.loc(start), - }; + }); } /** @@ -1233,13 +1185,12 @@ export class Parser { if (directives.length === 0 && values.length === 0) { throw this.unexpected(); } - return { + return this.node(start, { kind: Kind.ENUM_TYPE_EXTENSION, name, directives, values, - loc: this.loc(start), - }; + }); } /** @@ -1257,13 +1208,12 @@ export class Parser { if (directives.length === 0 && fields.length === 0) { throw this.unexpected(); } - return { + return this.node(start, { kind: Kind.INPUT_OBJECT_TYPE_EXTENSION, name, directives, fields, - loc: this.loc(start), - }; + }); } /** @@ -1280,15 +1230,14 @@ export class Parser { const repeatable = this.expectOptionalKeyword('repeatable'); this.expectKeyword('on'); const locations = this.parseDirectiveLocations(); - return { + return this.node(start, { kind: Kind.DIRECTIVE_DEFINITION, description, name, arguments: args, repeatable, locations, - loc: this.loc(start), - }; + }); } /** @@ -1339,16 +1288,19 @@ export class Parser { // Core parsing utility functions /** - * Returns a location object, used to identify the place in the source that created a given parsed object. + * Returns a node that, if configured to do so, sets a "loc" field as a + * location object, used to identify the place in the source that created a + * given parsed object. */ - loc(startToken: Token): Location | void { + node(startToken: Token, node: T): T { if (this._options?.noLocation !== true) { - return new Location( + node.loc = new Location( startToken, this._lexer.lastToken, this._lexer.source, ); } + return node; } /** diff --git a/src/language/visitor.js b/src/language/visitor.js index 582aaf4423..cafa5103a2 100644 --- a/src/language/visitor.js +++ b/src/language/visitor.js @@ -221,15 +221,9 @@ export function visit(root: ASTNode, visitor: ASTVisitor): any { node = parent; parent = ancestors.pop(); if (isEdited) { - if (inArray) { - node = node.slice(); - } else { - const clone = {}; - for (const k of Object.keys(node)) { - clone[k] = node[k]; - } - node = clone; - } + node = inArray + ? node.slice() + : Object.defineProperties({}, Object.getOwnPropertyDescriptors(node)); let editOffset = 0; for (let ii = 0; ii < edits.length; ii++) { let editKey: any = edits[ii][0]; @@ -268,7 +262,6 @@ export function visit(root: ASTNode, visitor: ASTVisitor): any { } const visitFn = getVisitFn(visitor, node.kind, isLeaving); if (visitFn) { - // $FlowFixMe[incompatible-call] result = visitFn.call(visitor, node, key, parent, path, ancestors); if (result === BREAK) {