diff --git a/src/index.d.ts b/src/index.d.ts index 78afd0ed11..ac935945c5 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -200,6 +200,7 @@ export { // Parse parse, parseValue, + parseConstValue, parseType, // Print print, @@ -215,6 +216,7 @@ export { isExecutableDefinitionNode, isSelectionNode, isValueNode, + isConstValueNode, isTypeNode, isTypeSystemDefinitionNode, isTypeDefinitionNode, @@ -247,10 +249,12 @@ export { SelectionNode, FieldNode, ArgumentNode, + ConstArgumentNode, FragmentSpreadNode, InlineFragmentNode, FragmentDefinitionNode, ValueNode, + ConstValueNode, IntValueNode, FloatValueNode, StringValueNode, @@ -258,9 +262,13 @@ export { NullValueNode, EnumValueNode, ListValueNode, + ConstListValueNode, ObjectValueNode, + ConstObjectValueNode, ObjectFieldNode, + ConstObjectFieldNode, DirectiveNode, + ConstDirectiveNode, TypeNode, NamedTypeNode, ListTypeNode, diff --git a/src/index.js b/src/index.js index 30d99233d0..1e6b3f247a 100644 --- a/src/index.js +++ b/src/index.js @@ -187,6 +187,7 @@ export { // Parse parse, parseValue, + parseConstValue, parseType, // Print print, @@ -202,6 +203,7 @@ export { isExecutableDefinitionNode, isSelectionNode, isValueNode, + isConstValueNode, isTypeNode, isTypeSystemDefinitionNode, isTypeDefinitionNode, @@ -234,10 +236,12 @@ export type { SelectionNode, FieldNode, ArgumentNode, + ConstArgumentNode, FragmentSpreadNode, InlineFragmentNode, FragmentDefinitionNode, ValueNode, + ConstValueNode, IntValueNode, FloatValueNode, StringValueNode, @@ -245,9 +249,13 @@ export type { NullValueNode, EnumValueNode, ListValueNode, + ConstListValueNode, ObjectValueNode, + ConstObjectValueNode, ObjectFieldNode, + ConstObjectFieldNode, DirectiveNode, + ConstDirectiveNode, TypeNode, NamedTypeNode, ListTypeNode, diff --git a/src/language/__tests__/parser-test.js b/src/language/__tests__/parser-test.js index 661378b1d7..d042bec291 100644 --- a/src/language/__tests__/parser-test.js +++ b/src/language/__tests__/parser-test.js @@ -9,7 +9,7 @@ import { inspect } from '../../jsutils/inspect'; import { Kind } from '../kinds'; import { Source } from '../source'; import { TokenKind } from '../tokenKind'; -import { parse, parseValue, parseType } from '../parser'; +import { parse, parseValue, parseConstValue, parseType } from '../parser'; import { toJSONDeep } from './toJSONDeep'; @@ -95,7 +95,7 @@ describe('Parser', () => { expectSyntaxError( 'query Foo($x: Complex = { a: { b: [ $var ] } }) { field }', ).to.deep.equal({ - message: 'Syntax Error: Unexpected "$".', + message: 'Syntax Error: Unexpected variable "$var" in constant value.', locations: [{ line: 1, column: 37 }], }); }); @@ -447,6 +447,94 @@ describe('Parser', () => { ], }); }); + + it('allows variables', () => { + const result = parseValue('{ field: $var }'); + expect(toJSONDeep(result)).to.deep.equal({ + kind: Kind.OBJECT, + loc: { start: 0, end: 15 }, + fields: [ + { + kind: Kind.OBJECT_FIELD, + loc: { start: 2, end: 13 }, + name: { + kind: Kind.NAME, + loc: { start: 2, end: 7 }, + value: 'field', + }, + value: { + kind: Kind.VARIABLE, + loc: { start: 9, end: 13 }, + name: { + kind: Kind.NAME, + loc: { start: 10, end: 13 }, + value: 'var', + }, + }, + }, + ], + }); + }); + + it('correct message for incomplete variable', () => { + expect(() => parseValue('$')) + .to.throw() + .to.deep.include({ + message: 'Syntax Error: Expected Name, found .', + locations: [{ line: 1, column: 2 }], + }); + }); + + it('correct message for unexpected token', () => { + expect(() => parseValue(':')) + .to.throw() + .to.deep.include({ + message: 'Syntax Error: Unexpected ":".', + locations: [{ line: 1, column: 1 }], + }); + }); + }); + + describe('parseConstValue', () => { + it('parses values', () => { + const result = parseConstValue('[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('does not allow variables', () => { + expect(() => parseConstValue('{ field: $var }')) + .to.throw() + .to.deep.include({ + message: + 'Syntax Error: Unexpected variable "$var" in constant value.', + locations: [{ line: 1, column: 10 }], + }); + }); + + it('correct message for unexpected token', () => { + expect(() => parseConstValue('$')) + .to.throw() + .to.deep.include({ + message: 'Syntax Error: Unexpected "$".', + locations: [{ line: 1, column: 1 }], + }); + }); }); describe('parseType', () => { diff --git a/src/language/__tests__/predicates-test.js b/src/language/__tests__/predicates-test.js index eb620abd61..4a9f03571e 100644 --- a/src/language/__tests__/predicates-test.js +++ b/src/language/__tests__/predicates-test.js @@ -3,11 +3,13 @@ import { describe, it } from 'mocha'; import type { ASTNode } from '../ast'; import { Kind } from '../kinds'; +import { parseValue } from '../parser'; import { isDefinitionNode, isExecutableDefinitionNode, isSelectionNode, isValueNode, + isConstValueNode, isTypeNode, isTypeSystemDefinitionNode, isTypeDefinitionNode, @@ -75,6 +77,17 @@ describe('AST node predicates', () => { ]); }); + it('isConstValueNode', () => { + expect(isConstValueNode(parseValue('"value"'))).to.equal(true); + expect(isConstValueNode(parseValue('$var'))).to.equal(false); + + expect(isConstValueNode(parseValue('{ field: "value" }'))).to.equal(true); + expect(isConstValueNode(parseValue('{ field: $var }'))).to.equal(false); + + expect(isConstValueNode(parseValue('[ "value" ]'))).to.equal(true); + expect(isConstValueNode(parseValue('[ $var ]'))).to.equal(false); + }); + it('isTypeNode', () => { expect(filterNodes(isTypeNode)).to.deep.equal([ 'NamedType', diff --git a/src/language/ast.d.ts b/src/language/ast.d.ts index 20ec92f805..2e2850a423 100644 --- a/src/language/ast.d.ts +++ b/src/language/ast.d.ts @@ -241,8 +241,8 @@ export interface VariableDefinitionNode { readonly loc?: Location; readonly variable: VariableNode; readonly type: TypeNode; - readonly defaultValue?: ValueNode; - readonly directives?: ReadonlyArray; + readonly defaultValue?: ConstValueNode; + readonly directives?: ReadonlyArray; } export interface VariableNode { @@ -276,6 +276,13 @@ export interface ArgumentNode { readonly value: ValueNode; } +export interface ConstArgumentNode { + readonly kind: 'Argument'; + readonly loc?: Location; + readonly name: NameNode; + readonly value: ConstValueNode; +} + // Fragments export interface FragmentSpreadNode { @@ -317,6 +324,16 @@ export type ValueNode = | ListValueNode | ObjectValueNode; +export type ConstValueNode = + | IntValueNode + | FloatValueNode + | StringValueNode + | BooleanValueNode + | NullValueNode + | EnumValueNode + | ConstListValueNode + | ConstObjectValueNode; + export interface IntValueNode { readonly kind: 'IntValue'; readonly loc?: Location; @@ -359,12 +376,24 @@ export interface ListValueNode { readonly values: ReadonlyArray; } +export interface ConstListValueNode { + readonly kind: 'ListValue'; + readonly loc?: Location; + readonly values: ReadonlyArray; +} + export interface ObjectValueNode { readonly kind: 'ObjectValue'; readonly loc?: Location; readonly fields: ReadonlyArray; } +export interface ConstObjectValueNode { + readonly kind: 'ObjectValue'; + readonly loc?: Location; + readonly fields: ReadonlyArray; +} + export interface ObjectFieldNode { readonly kind: 'ObjectField'; readonly loc?: Location; @@ -372,6 +401,13 @@ export interface ObjectFieldNode { readonly value: ValueNode; } +export interface ConstObjectFieldNode { + readonly kind: 'ObjectField'; + readonly loc?: Location; + readonly name: NameNode; + readonly value: ConstValueNode; +} + // Directives export interface DirectiveNode { @@ -381,6 +417,13 @@ export interface DirectiveNode { readonly arguments?: ReadonlyArray; } +export interface ConstDirectiveNode { + readonly kind: 'Directive'; + readonly loc?: Location; + readonly name: NameNode; + readonly arguments?: ReadonlyArray; +} + // Type Reference export type TypeNode = NamedTypeNode | ListTypeNode | NonNullTypeNode; @@ -414,7 +457,7 @@ export interface SchemaDefinitionNode { readonly kind: 'SchemaDefinition'; readonly loc?: Location; readonly description?: StringValueNode; - readonly directives?: ReadonlyArray; + readonly directives?: ReadonlyArray; readonly operationTypes: ReadonlyArray; } @@ -440,7 +483,7 @@ export interface ScalarTypeDefinitionNode { readonly loc?: Location; readonly description?: StringValueNode; readonly name: NameNode; - readonly directives?: ReadonlyArray; + readonly directives?: ReadonlyArray; } export interface ObjectTypeDefinitionNode { @@ -449,7 +492,7 @@ export interface ObjectTypeDefinitionNode { readonly description?: StringValueNode; readonly name: NameNode; readonly interfaces?: ReadonlyArray; - readonly directives?: ReadonlyArray; + readonly directives?: ReadonlyArray; readonly fields?: ReadonlyArray; } @@ -460,7 +503,7 @@ export interface FieldDefinitionNode { readonly name: NameNode; readonly arguments?: ReadonlyArray; readonly type: TypeNode; - readonly directives?: ReadonlyArray; + readonly directives?: ReadonlyArray; } export interface InputValueDefinitionNode { @@ -469,8 +512,8 @@ export interface InputValueDefinitionNode { readonly description?: StringValueNode; readonly name: NameNode; readonly type: TypeNode; - readonly defaultValue?: ValueNode; - readonly directives?: ReadonlyArray; + readonly defaultValue?: ConstValueNode; + readonly directives?: ReadonlyArray; } export interface InterfaceTypeDefinitionNode { @@ -479,7 +522,7 @@ export interface InterfaceTypeDefinitionNode { readonly description?: StringValueNode; readonly name: NameNode; readonly interfaces?: ReadonlyArray; - readonly directives?: ReadonlyArray; + readonly directives?: ReadonlyArray; readonly fields?: ReadonlyArray; } @@ -488,7 +531,7 @@ export interface UnionTypeDefinitionNode { readonly loc?: Location; readonly description?: StringValueNode; readonly name: NameNode; - readonly directives?: ReadonlyArray; + readonly directives?: ReadonlyArray; readonly types?: ReadonlyArray; } @@ -497,7 +540,7 @@ export interface EnumTypeDefinitionNode { readonly loc?: Location; readonly description?: StringValueNode; readonly name: NameNode; - readonly directives?: ReadonlyArray; + readonly directives?: ReadonlyArray; readonly values?: ReadonlyArray; } @@ -506,7 +549,7 @@ export interface EnumValueDefinitionNode { readonly loc?: Location; readonly description?: StringValueNode; readonly name: NameNode; - readonly directives?: ReadonlyArray; + readonly directives?: ReadonlyArray; } export interface InputObjectTypeDefinitionNode { @@ -514,7 +557,7 @@ export interface InputObjectTypeDefinitionNode { readonly loc?: Location; readonly description?: StringValueNode; readonly name: NameNode; - readonly directives?: ReadonlyArray; + readonly directives?: ReadonlyArray; readonly fields?: ReadonlyArray; } @@ -537,7 +580,7 @@ export type TypeSystemExtensionNode = SchemaExtensionNode | TypeExtensionNode; export interface SchemaExtensionNode { readonly kind: 'SchemaExtension'; readonly loc?: Location; - readonly directives?: ReadonlyArray; + readonly directives?: ReadonlyArray; readonly operationTypes?: ReadonlyArray; } @@ -555,7 +598,7 @@ export interface ScalarTypeExtensionNode { readonly kind: 'ScalarTypeExtension'; readonly loc?: Location; readonly name: NameNode; - readonly directives?: ReadonlyArray; + readonly directives?: ReadonlyArray; } export interface ObjectTypeExtensionNode { @@ -563,7 +606,7 @@ export interface ObjectTypeExtensionNode { readonly loc?: Location; readonly name: NameNode; readonly interfaces?: ReadonlyArray; - readonly directives?: ReadonlyArray; + readonly directives?: ReadonlyArray; readonly fields?: ReadonlyArray; } @@ -572,7 +615,7 @@ export interface InterfaceTypeExtensionNode { readonly loc?: Location; readonly name: NameNode; readonly interfaces?: ReadonlyArray; - readonly directives?: ReadonlyArray; + readonly directives?: ReadonlyArray; readonly fields?: ReadonlyArray; } @@ -580,7 +623,7 @@ export interface UnionTypeExtensionNode { readonly kind: 'UnionTypeExtension'; readonly loc?: Location; readonly name: NameNode; - readonly directives?: ReadonlyArray; + readonly directives?: ReadonlyArray; readonly types?: ReadonlyArray; } @@ -588,7 +631,7 @@ export interface EnumTypeExtensionNode { readonly kind: 'EnumTypeExtension'; readonly loc?: Location; readonly name: NameNode; - readonly directives?: ReadonlyArray; + readonly directives?: ReadonlyArray; readonly values?: ReadonlyArray; } @@ -596,6 +639,6 @@ export interface InputObjectTypeExtensionNode { readonly kind: 'InputObjectTypeExtension'; readonly loc?: Location; readonly name: NameNode; - readonly directives?: ReadonlyArray; + readonly directives?: ReadonlyArray; readonly fields?: ReadonlyArray; } diff --git a/src/language/ast.js b/src/language/ast.js index c0a9a615e7..f4de32054d 100644 --- a/src/language/ast.js +++ b/src/language/ast.js @@ -267,8 +267,8 @@ export type VariableDefinitionNode = {| +loc?: Location, +variable: VariableNode, +type: TypeNode, - +defaultValue?: ValueNode, - +directives?: $ReadOnlyArray, + +defaultValue?: ConstValueNode, + +directives?: $ReadOnlyArray, |}; export type VariableNode = {| @@ -302,6 +302,13 @@ export type ArgumentNode = {| +value: ValueNode, |}; +export type ConstArgumentNode = {| + +kind: 'Argument', + +loc?: Location, + +name: NameNode, + +value: ConstValueNode, +|}; + // Fragments export type FragmentSpreadNode = {| @@ -343,6 +350,16 @@ export type ValueNode = | ListValueNode | ObjectValueNode; +export type ConstValueNode = + | IntValueNode + | FloatValueNode + | StringValueNode + | BooleanValueNode + | NullValueNode + | EnumValueNode + | ConstListValueNode + | ConstObjectValueNode; + export type IntValueNode = {| +kind: 'IntValue', +loc?: Location, @@ -385,12 +402,24 @@ export type ListValueNode = {| +values: $ReadOnlyArray, |}; +export type ConstListValueNode = {| + +kind: 'ListValue', + +loc?: Location, + +values: $ReadOnlyArray, +|}; + export type ObjectValueNode = {| +kind: 'ObjectValue', +loc?: Location, +fields: $ReadOnlyArray, |}; +export type ConstObjectValueNode = {| + +kind: 'ObjectValue', + +loc?: Location, + +fields: $ReadOnlyArray, +|}; + export type ObjectFieldNode = {| +kind: 'ObjectField', +loc?: Location, @@ -398,6 +427,13 @@ export type ObjectFieldNode = {| +value: ValueNode, |}; +export type ConstObjectFieldNode = {| + +kind: 'ObjectField', + +loc?: Location, + +name: NameNode, + +value: ConstValueNode, +|}; + // Directives export type DirectiveNode = {| @@ -407,6 +443,13 @@ export type DirectiveNode = {| +arguments?: $ReadOnlyArray, |}; +export type ConstDirectiveNode = {| + +kind: 'Directive', + +loc?: Location, + +name: NameNode, + +arguments?: $ReadOnlyArray, +|}; + // Type Reference export type TypeNode = NamedTypeNode | ListTypeNode | NonNullTypeNode; @@ -440,7 +483,7 @@ export type SchemaDefinitionNode = {| +kind: 'SchemaDefinition', +loc?: Location, +description?: StringValueNode, - +directives?: $ReadOnlyArray, + +directives?: $ReadOnlyArray, +operationTypes: $ReadOnlyArray, |}; @@ -466,7 +509,7 @@ export type ScalarTypeDefinitionNode = {| +loc?: Location, +description?: StringValueNode, +name: NameNode, - +directives?: $ReadOnlyArray, + +directives?: $ReadOnlyArray, |}; export type ObjectTypeDefinitionNode = {| @@ -475,7 +518,7 @@ export type ObjectTypeDefinitionNode = {| +description?: StringValueNode, +name: NameNode, +interfaces?: $ReadOnlyArray, - +directives?: $ReadOnlyArray, + +directives?: $ReadOnlyArray, +fields?: $ReadOnlyArray, |}; @@ -486,7 +529,7 @@ export type FieldDefinitionNode = {| +name: NameNode, +arguments?: $ReadOnlyArray, +type: TypeNode, - +directives?: $ReadOnlyArray, + +directives?: $ReadOnlyArray, |}; export type InputValueDefinitionNode = {| @@ -495,8 +538,8 @@ export type InputValueDefinitionNode = {| +description?: StringValueNode, +name: NameNode, +type: TypeNode, - +defaultValue?: ValueNode, - +directives?: $ReadOnlyArray, + +defaultValue?: ConstValueNode, + +directives?: $ReadOnlyArray, |}; export type InterfaceTypeDefinitionNode = {| @@ -505,7 +548,7 @@ export type InterfaceTypeDefinitionNode = {| +description?: StringValueNode, +name: NameNode, +interfaces?: $ReadOnlyArray, - +directives?: $ReadOnlyArray, + +directives?: $ReadOnlyArray, +fields?: $ReadOnlyArray, |}; @@ -514,7 +557,7 @@ export type UnionTypeDefinitionNode = {| +loc?: Location, +description?: StringValueNode, +name: NameNode, - +directives?: $ReadOnlyArray, + +directives?: $ReadOnlyArray, +types?: $ReadOnlyArray, |}; @@ -523,7 +566,7 @@ export type EnumTypeDefinitionNode = {| +loc?: Location, +description?: StringValueNode, +name: NameNode, - +directives?: $ReadOnlyArray, + +directives?: $ReadOnlyArray, +values?: $ReadOnlyArray, |}; @@ -532,7 +575,7 @@ export type EnumValueDefinitionNode = {| +loc?: Location, +description?: StringValueNode, +name: NameNode, - +directives?: $ReadOnlyArray, + +directives?: $ReadOnlyArray, |}; export type InputObjectTypeDefinitionNode = {| @@ -540,7 +583,7 @@ export type InputObjectTypeDefinitionNode = {| +loc?: Location, +description?: StringValueNode, +name: NameNode, - +directives?: $ReadOnlyArray, + +directives?: $ReadOnlyArray, +fields?: $ReadOnlyArray, |}; @@ -563,7 +606,7 @@ export type TypeSystemExtensionNode = SchemaExtensionNode | TypeExtensionNode; export type SchemaExtensionNode = {| +kind: 'SchemaExtension', +loc?: Location, - +directives?: $ReadOnlyArray, + +directives?: $ReadOnlyArray, +operationTypes?: $ReadOnlyArray, |}; @@ -581,7 +624,7 @@ export type ScalarTypeExtensionNode = {| +kind: 'ScalarTypeExtension', +loc?: Location, +name: NameNode, - +directives?: $ReadOnlyArray, + +directives?: $ReadOnlyArray, |}; export type ObjectTypeExtensionNode = {| @@ -589,7 +632,7 @@ export type ObjectTypeExtensionNode = {| +loc?: Location, +name: NameNode, +interfaces?: $ReadOnlyArray, - +directives?: $ReadOnlyArray, + +directives?: $ReadOnlyArray, +fields?: $ReadOnlyArray, |}; @@ -598,7 +641,7 @@ export type InterfaceTypeExtensionNode = {| +loc?: Location, +name: NameNode, +interfaces?: $ReadOnlyArray, - +directives?: $ReadOnlyArray, + +directives?: $ReadOnlyArray, +fields?: $ReadOnlyArray, |}; @@ -606,7 +649,7 @@ export type UnionTypeExtensionNode = {| +kind: 'UnionTypeExtension', +loc?: Location, +name: NameNode, - +directives?: $ReadOnlyArray, + +directives?: $ReadOnlyArray, +types?: $ReadOnlyArray, |}; @@ -614,7 +657,7 @@ export type EnumTypeExtensionNode = {| +kind: 'EnumTypeExtension', +loc?: Location, +name: NameNode, - +directives?: $ReadOnlyArray, + +directives?: $ReadOnlyArray, +values?: $ReadOnlyArray, |}; @@ -622,6 +665,6 @@ export type InputObjectTypeExtensionNode = {| +kind: 'InputObjectTypeExtension', +loc?: Location, +name: NameNode, - +directives?: $ReadOnlyArray, + +directives?: $ReadOnlyArray, +fields?: $ReadOnlyArray, |}; diff --git a/src/language/index.d.ts b/src/language/index.d.ts index a5b1157b24..47e4ecfef7 100644 --- a/src/language/index.d.ts +++ b/src/language/index.d.ts @@ -6,7 +6,13 @@ export { printLocation, printSourceLocation } from './printLocation'; export { Kind, KindEnum } from './kinds'; export { TokenKind, TokenKindEnum } from './tokenKind'; export { Lexer } from './lexer'; -export { parse, parseValue, parseType, ParseOptions } from './parser'; +export { + parse, + parseValue, + parseConstValue, + parseType, + ParseOptions, +} from './parser'; export { print } from './printer'; export { visit, @@ -35,10 +41,12 @@ export { SelectionNode, FieldNode, ArgumentNode, + ConstArgumentNode, FragmentSpreadNode, InlineFragmentNode, FragmentDefinitionNode, ValueNode, + ConstValueNode, IntValueNode, FloatValueNode, StringValueNode, @@ -46,9 +54,13 @@ export { NullValueNode, EnumValueNode, ListValueNode, + ConstListValueNode, ObjectValueNode, + ConstObjectValueNode, ObjectFieldNode, + ConstObjectFieldNode, DirectiveNode, + ConstDirectiveNode, TypeNode, NamedTypeNode, ListTypeNode, @@ -83,6 +95,7 @@ export { isExecutableDefinitionNode, isSelectionNode, isValueNode, + isConstValueNode, isTypeNode, isTypeSystemDefinitionNode, isTypeDefinitionNode, diff --git a/src/language/index.js b/src/language/index.js index 71cb5f5c22..447c2d0e24 100644 --- a/src/language/index.js +++ b/src/language/index.js @@ -13,7 +13,7 @@ export type { TokenKindEnum } from './tokenKind'; export { Lexer } from './lexer'; -export { parse, parseValue, parseType } from './parser'; +export { parse, parseValue, parseConstValue, parseType } from './parser'; export type { ParseOptions } from './parser'; export { print } from './printer'; @@ -38,10 +38,12 @@ export type { SelectionNode, FieldNode, ArgumentNode, + ConstArgumentNode, FragmentSpreadNode, InlineFragmentNode, FragmentDefinitionNode, ValueNode, + ConstValueNode, IntValueNode, FloatValueNode, StringValueNode, @@ -49,9 +51,13 @@ export type { NullValueNode, EnumValueNode, ListValueNode, + ConstListValueNode, ObjectValueNode, + ConstObjectValueNode, ObjectFieldNode, + ConstObjectFieldNode, DirectiveNode, + ConstDirectiveNode, TypeNode, NamedTypeNode, ListTypeNode, @@ -86,6 +92,7 @@ export { isExecutableDefinitionNode, isSelectionNode, isValueNode, + isConstValueNode, isTypeNode, isTypeSystemDefinitionNode, isTypeDefinitionNode, diff --git a/src/language/parser.d.ts b/src/language/parser.d.ts index ed7b27c0b4..5790cc3802 100644 --- a/src/language/parser.d.ts +++ b/src/language/parser.d.ts @@ -14,15 +14,21 @@ import { SelectionNode, FieldNode, ArgumentNode, + ConstArgumentNode, FragmentSpreadNode, InlineFragmentNode, FragmentDefinitionNode, ValueNode, + ConstValueNode, StringValueNode, ListValueNode, + ConstListValueNode, ObjectValueNode, + ConstObjectValueNode, ObjectFieldNode, + ConstObjectFieldNode, DirectiveNode, + ConstDirectiveNode, TypeNode, NamedTypeNode, TypeSystemDefinitionNode, @@ -99,6 +105,15 @@ export function parseValue( options?: ParseOptions, ): ValueNode; +/** + * Similar to parseValue(), but raises a parse error if it encounters a + * variable. The return type will be a constant value. + */ +export function parseConstValue( + source: string | Source, + options?: ParseOptions, +): ConstValueNode; + /** * Given a string containing a GraphQL Type (ex. `[Int!]`), parse the AST for * that type. @@ -203,14 +218,16 @@ export declare class Parser { /** * Arguments[Const] : ( Argument[?Const]+ ) */ + parseArguments(isConst: true): Array; parseArguments(isConst: boolean): Array; /** * Argument[Const] : Name : Value[?Const] */ - parseArgument(): ArgumentNode; + parseArgument(isConst: true): ConstArgumentNode; + parseArgument(isConst: boolean): ArgumentNode; - parseConstArgument(): ArgumentNode; + parseConstArgument(): ConstArgumentNode; /** * Corresponds to both FragmentSpread and InlineFragment in the spec. @@ -252,6 +269,7 @@ export declare class Parser { * * EnumValue : Name but not `true`, `false` or `null` */ + parseValueLiteral(isConst: true): ConstValueNode; parseValueLiteral(isConst: boolean): ValueNode; parseStringLiteral(): StringValueNode; @@ -261,6 +279,7 @@ export declare class Parser { * - [ ] * - [ Value[?Const]+ ] */ + parseList(isConst: true): ConstListValueNode; parseList(isConst: boolean): ListValueNode; /** @@ -268,21 +287,25 @@ export declare class Parser { * - { } * - { ObjectField[?Const]+ } */ + parseObject(isConst: true): ConstObjectValueNode; parseObject(isConst: boolean): ObjectValueNode; /** * ObjectField[Const] : Name : Value[?Const] */ + parseObjectField(isConst: true): ConstObjectFieldNode; parseObjectField(isConst: boolean): ObjectFieldNode; /** * Directives[Const] : Directive[?Const]+ */ + parseDirectives(isConst: true): Array; parseDirectives(isConst: boolean): Array; /** * Directive[Const] : @ Name Arguments[?Const]? */ + parseDirective(isConst: true): ConstDirectiveNode; parseDirective(isConst: boolean): DirectiveNode; /** diff --git a/src/language/parser.js b/src/language/parser.js index e7c3c51066..a366ac6895 100644 --- a/src/language/parser.js +++ b/src/language/parser.js @@ -15,15 +15,18 @@ import type { SelectionNode, FieldNode, ArgumentNode, + ConstArgumentNode, FragmentSpreadNode, InlineFragmentNode, FragmentDefinitionNode, ValueNode, + ConstValueNode, StringValueNode, ListValueNode, ObjectValueNode, ObjectFieldNode, DirectiveNode, + ConstDirectiveNode, TypeNode, NamedTypeNode, TypeSystemDefinitionNode, @@ -116,6 +119,21 @@ export function parseValue( return value; } +/** + * Similar to parseValue(), but raises a parse error if it encounters a + * variable. The return type will be a constant value. + */ +export function parseConstValue( + source: string | Source, + options?: ParseOptions, +): ConstValueNode { + const parser = new Parser(source, options); + parser.expectToken(TokenKind.SOF); + const value = parser.parseConstValueLiteral(); + parser.expectToken(TokenKind.EOF); + return value; +} + /** * Given a string containing a GraphQL Type (ex. `[Int!]`), parse the AST for * that type. @@ -297,9 +315,9 @@ export class Parser { variable: this.parseVariable(), type: (this.expectToken(TokenKind.COLON), this.parseTypeReference()), defaultValue: this.expectOptionalToken(TokenKind.EQUALS) - ? this.parseValueLiteral(true) + ? this.parseConstValueLiteral() : undefined, - directives: this.parseDirectives(true), + directives: this.parseConstDirectives(), }); } @@ -382,7 +400,7 @@ export class Parser { /** * Argument[Const] : Name : Value[?Const] */ - parseArgument(): ArgumentNode { + parseArgument(isConst: boolean = false): ArgumentNode { const start = this._lexer.token; const name = this.parseName(); @@ -390,16 +408,12 @@ export class Parser { return this.node(start, { kind: Kind.ARGUMENT, name, - value: this.parseValueLiteral(false), + value: this.parseValueLiteral(isConst), }); } - parseConstArgument(): ArgumentNode { - return this.node(this._lexer.token, { - kind: Kind.ARGUMENT, - name: this.parseName(), - value: (this.expectToken(TokenKind.COLON), this.parseValueLiteral(true)), - }); + parseConstArgument(): ConstArgumentNode { + return (this.parseArgument(true): any); } // Implements the parsing rules in the Fragments section. @@ -530,14 +544,28 @@ export class Parser { }); } case TokenKind.DOLLAR: - if (!isConst) { - return this.parseVariable(); + if (isConst) { + this.expectToken(TokenKind.DOLLAR); + const varName = this.expectOptionalToken(TokenKind.NAME)?.value; + if (varName != null) { + throw syntaxError( + this._lexer.source, + token.start, + `Unexpected variable "$${varName}" in constant value.`, + ); + } else { + throw this.unexpected(token); + } } - break; + return this.parseVariable(); } throw this.unexpected(); } + parseConstValueLiteral(): ConstValueNode { + return (this.parseValueLiteral(true): any); + } + parseStringLiteral(): StringValueNode { const token = this._lexer.token; this._lexer.advance(); @@ -602,6 +630,10 @@ export class Parser { return directives; } + parseConstDirectives(): Array { + return (this.parseDirectives(true): any); + } + /** * Directive[Const] : @ Name Arguments[?Const]? */ @@ -722,7 +754,7 @@ export class Parser { const start = this._lexer.token; const description = this.parseDescription(); this.expectKeyword('schema'); - const directives = this.parseDirectives(true); + const directives = this.parseConstDirectives(); const operationTypes = this.many( TokenKind.BRACE_L, this.parseOperationTypeDefinition, @@ -759,7 +791,7 @@ export class Parser { const description = this.parseDescription(); this.expectKeyword('scalar'); const name = this.parseName(); - const directives = this.parseDirectives(true); + const directives = this.parseConstDirectives(); return this.node(start, { kind: Kind.SCALAR_TYPE_DEFINITION, description, @@ -779,7 +811,7 @@ export class Parser { this.expectKeyword('type'); const name = this.parseName(); const interfaces = this.parseImplementsInterfaces(); - const directives = this.parseDirectives(true); + const directives = this.parseConstDirectives(); const fields = this.parseFieldsDefinition(); return this.node(start, { kind: Kind.OBJECT_TYPE_DEFINITION, @@ -824,7 +856,7 @@ export class Parser { const args = this.parseArgumentDefs(); this.expectToken(TokenKind.COLON); const type = this.parseTypeReference(); - const directives = this.parseDirectives(true); + const directives = this.parseConstDirectives(); return this.node(start, { kind: Kind.FIELD_DEFINITION, description, @@ -858,9 +890,9 @@ export class Parser { const type = this.parseTypeReference(); let defaultValue; if (this.expectOptionalToken(TokenKind.EQUALS)) { - defaultValue = this.parseValueLiteral(true); + defaultValue = this.parseConstValueLiteral(); } - const directives = this.parseDirectives(true); + const directives = this.parseConstDirectives(); return this.node(start, { kind: Kind.INPUT_VALUE_DEFINITION, description, @@ -881,7 +913,7 @@ export class Parser { this.expectKeyword('interface'); const name = this.parseName(); const interfaces = this.parseImplementsInterfaces(); - const directives = this.parseDirectives(true); + const directives = this.parseConstDirectives(); const fields = this.parseFieldsDefinition(); return this.node(start, { kind: Kind.INTERFACE_TYPE_DEFINITION, @@ -902,7 +934,7 @@ export class Parser { const description = this.parseDescription(); this.expectKeyword('union'); const name = this.parseName(); - const directives = this.parseDirectives(true); + const directives = this.parseConstDirectives(); const types = this.parseUnionMemberTypes(); return this.node(start, { kind: Kind.UNION_TYPE_DEFINITION, @@ -933,7 +965,7 @@ export class Parser { const description = this.parseDescription(); this.expectKeyword('enum'); const name = this.parseName(); - const directives = this.parseDirectives(true); + const directives = this.parseConstDirectives(); const values = this.parseEnumValuesDefinition(); return this.node(start, { kind: Kind.ENUM_TYPE_DEFINITION, @@ -964,7 +996,7 @@ export class Parser { const start = this._lexer.token; const description = this.parseDescription(); const name = this.parseName(); - const directives = this.parseDirectives(true); + const directives = this.parseConstDirectives(); return this.node(start, { kind: Kind.ENUM_VALUE_DEFINITION, description, @@ -982,7 +1014,7 @@ export class Parser { const description = this.parseDescription(); this.expectKeyword('input'); const name = this.parseName(); - const directives = this.parseDirectives(true); + const directives = this.parseConstDirectives(); const fields = this.parseInputFieldsDefinition(); return this.node(start, { kind: Kind.INPUT_OBJECT_TYPE_DEFINITION, @@ -1051,7 +1083,7 @@ export class Parser { const start = this._lexer.token; this.expectKeyword('extend'); this.expectKeyword('schema'); - const directives = this.parseDirectives(true); + const directives = this.parseConstDirectives(); const operationTypes = this.optionalMany( TokenKind.BRACE_L, this.parseOperationTypeDefinition, @@ -1076,7 +1108,7 @@ export class Parser { this.expectKeyword('extend'); this.expectKeyword('scalar'); const name = this.parseName(); - const directives = this.parseDirectives(true); + const directives = this.parseConstDirectives(); if (directives.length === 0) { throw this.unexpected(); } @@ -1099,7 +1131,7 @@ export class Parser { this.expectKeyword('type'); const name = this.parseName(); const interfaces = this.parseImplementsInterfaces(); - const directives = this.parseDirectives(true); + const directives = this.parseConstDirectives(); const fields = this.parseFieldsDefinition(); if ( interfaces.length === 0 && @@ -1129,7 +1161,7 @@ export class Parser { this.expectKeyword('interface'); const name = this.parseName(); const interfaces = this.parseImplementsInterfaces(); - const directives = this.parseDirectives(true); + const directives = this.parseConstDirectives(); const fields = this.parseFieldsDefinition(); if ( interfaces.length === 0 && @@ -1157,7 +1189,7 @@ export class Parser { this.expectKeyword('extend'); this.expectKeyword('union'); const name = this.parseName(); - const directives = this.parseDirectives(true); + const directives = this.parseConstDirectives(); const types = this.parseUnionMemberTypes(); if (directives.length === 0 && types.length === 0) { throw this.unexpected(); @@ -1180,7 +1212,7 @@ export class Parser { this.expectKeyword('extend'); this.expectKeyword('enum'); const name = this.parseName(); - const directives = this.parseDirectives(true); + const directives = this.parseConstDirectives(); const values = this.parseEnumValuesDefinition(); if (directives.length === 0 && values.length === 0) { throw this.unexpected(); @@ -1203,7 +1235,7 @@ export class Parser { this.expectKeyword('extend'); this.expectKeyword('input'); const name = this.parseName(); - const directives = this.parseDirectives(true); + const directives = this.parseConstDirectives(); const fields = this.parseInputFieldsDefinition(); if (directives.length === 0 && fields.length === 0) { throw this.unexpected(); diff --git a/src/language/predicates.d.ts b/src/language/predicates.d.ts index cdbe1f9fd6..3111607635 100644 --- a/src/language/predicates.d.ts +++ b/src/language/predicates.d.ts @@ -4,6 +4,7 @@ import { ExecutableDefinitionNode, SelectionNode, ValueNode, + ConstValueNode, TypeNode, TypeSystemDefinitionNode, TypeDefinitionNode, @@ -21,6 +22,8 @@ export function isSelectionNode(node: ASTNode): node is SelectionNode; export function isValueNode(node: ASTNode): node is ValueNode; +export function isConstValueNode(node: ASTNode): node is ConstValueNode; + export function isTypeNode(node: ASTNode): node is TypeNode; export function isTypeSystemDefinitionNode( diff --git a/src/language/predicates.js b/src/language/predicates.js index b9108f87ad..8be57aaa7d 100644 --- a/src/language/predicates.js +++ b/src/language/predicates.js @@ -38,6 +38,17 @@ export function isValueNode(node: ASTNode): boolean %checks { ); } +export function isConstValueNode(node: ASTNode): boolean %checks { + return ( + isValueNode(node) && + (node.kind === Kind.LIST + ? node.values.some(isConstValueNode) + : node.kind === Kind.OBJECT + ? node.fields.some((field) => isConstValueNode(field.value)) + : node.kind !== Kind.VARIABLE) + ); +} + export function isTypeNode(node: ASTNode): boolean %checks { return ( node.kind === Kind.NAMED_TYPE ||