Skip to content

Commit b6533ad

Browse files
authored
Implement schema coordinate spec as of 2025-06-06 (#4432)
This PR is applied to the `schema-coordinates` branch PR here: #3044 Implements schema coordinate spec changes per the June 5th 2025 WG discussion graphql/graphql-spec#794 - Add support for meta-fields (e.g. `__typename`) - Add support for introspection types - Revert back from FieldCoordinate+ValueCoordinate -> MemberCoordinate cc @benjie
1 parent b759e92 commit b6533ad

File tree

11 files changed

+107
-138
lines changed

11 files changed

+107
-138
lines changed

src/language/__tests__/parser-test.ts

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -703,14 +703,14 @@ describe('Parser', () => {
703703
it('parses Name . Name', () => {
704704
const result = parseSchemaCoordinate('MyType.field');
705705
expectJSON(result).toDeepEqual({
706-
kind: Kind.FIELD_COORDINATE,
706+
kind: Kind.MEMBER_COORDINATE,
707707
loc: { start: 0, end: 12 },
708708
name: {
709709
kind: Kind.NAME,
710710
loc: { start: 0, end: 6 },
711711
value: 'MyType',
712712
},
713-
fieldName: {
713+
memberName: {
714714
kind: Kind.NAME,
715715
loc: { start: 7, end: 12 },
716716
value: 'field',
@@ -727,24 +727,6 @@ describe('Parser', () => {
727727
});
728728
});
729729

730-
it('parses Name :: Name', () => {
731-
const result = parseSchemaCoordinate('MyEnum::value');
732-
expectJSON(result).toDeepEqual({
733-
kind: Kind.VALUE_COORDINATE,
734-
loc: { start: 0, end: 13 },
735-
name: {
736-
kind: Kind.NAME,
737-
loc: { start: 0, end: 6 },
738-
value: 'MyEnum',
739-
},
740-
valueName: {
741-
kind: Kind.NAME,
742-
loc: { start: 8, end: 13 },
743-
value: 'value',
744-
},
745-
});
746-
});
747-
748730
it('parses Name . Name ( Name : )', () => {
749731
const result = parseSchemaCoordinate('MyType.field(arg:)');
750732
expectJSON(result).toDeepEqual({

src/language/__tests__/predicates-test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,8 @@ describe('AST node predicates', () => {
148148
'ArgumentCoordinate',
149149
'DirectiveArgumentCoordinate',
150150
'DirectiveCoordinate',
151-
'FieldCoordinate',
151+
'MemberCoordinate',
152152
'TypeCoordinate',
153-
'ValueCoordinate',
154153
]);
155154
});
156155
});

src/language/ast.ts

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -183,9 +183,8 @@ export type ASTNode =
183183
| EnumTypeExtensionNode
184184
| InputObjectTypeExtensionNode
185185
| TypeCoordinateNode
186-
| FieldCoordinateNode
186+
| MemberCoordinateNode
187187
| ArgumentCoordinateNode
188-
| ValueCoordinateNode
189188
| DirectiveCoordinateNode
190189
| DirectiveArgumentCoordinateNode;
191190

@@ -296,9 +295,8 @@ export const QueryDocumentKeys: {
296295

297296
// Schema Coordinates
298297
TypeCoordinate: ['name'],
299-
FieldCoordinate: ['name', 'fieldName'],
298+
MemberCoordinate: ['name', 'memberName'],
300299
ArgumentCoordinate: ['name', 'fieldName', 'argumentName'],
301-
ValueCoordinate: ['name', 'valueName'],
302300
DirectiveCoordinate: ['name'],
303301
DirectiveArgumentCoordinate: ['name', 'argumentName'],
304302
};
@@ -781,9 +779,8 @@ export interface InputObjectTypeExtensionNode {
781779

782780
export type SchemaCoordinateNode =
783781
| TypeCoordinateNode
784-
| FieldCoordinateNode
782+
| MemberCoordinateNode
785783
| ArgumentCoordinateNode
786-
| ValueCoordinateNode
787784
| DirectiveCoordinateNode
788785
| DirectiveArgumentCoordinateNode;
789786

@@ -793,11 +790,11 @@ export interface TypeCoordinateNode {
793790
readonly name: NameNode;
794791
}
795792

796-
export interface FieldCoordinateNode {
797-
readonly kind: typeof Kind.FIELD_COORDINATE;
793+
export interface MemberCoordinateNode {
794+
readonly kind: typeof Kind.MEMBER_COORDINATE;
798795
readonly loc?: Location;
799796
readonly name: NameNode;
800-
readonly fieldName: NameNode;
797+
readonly memberName: NameNode;
801798
}
802799

803800
export interface ArgumentCoordinateNode {
@@ -808,13 +805,6 @@ export interface ArgumentCoordinateNode {
808805
readonly argumentName: NameNode;
809806
}
810807

811-
export interface ValueCoordinateNode {
812-
readonly kind: typeof Kind.VALUE_COORDINATE;
813-
readonly loc?: Location;
814-
readonly name: NameNode;
815-
readonly valueName: NameNode;
816-
}
817-
818808
export interface DirectiveCoordinateNode {
819809
readonly kind: typeof Kind.DIRECTIVE_COORDINATE;
820810
readonly loc?: Location;

src/language/kinds_.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,15 +113,12 @@ export type INPUT_OBJECT_TYPE_EXTENSION = typeof INPUT_OBJECT_TYPE_EXTENSION;
113113
export const TYPE_COORDINATE = 'TypeCoordinate';
114114
export type TYPE_COORDINATE = typeof TYPE_COORDINATE;
115115

116-
export const FIELD_COORDINATE = 'FieldCoordinate';
117-
export type FIELD_COORDINATE = typeof FIELD_COORDINATE;
116+
export const MEMBER_COORDINATE = 'MemberCoordinate';
117+
export type MEMBER_COORDINATE = typeof MEMBER_COORDINATE;
118118

119119
export const ARGUMENT_COORDINATE = 'ArgumentCoordinate';
120120
export type ARGUMENT_COORDINATE = typeof ARGUMENT_COORDINATE;
121121

122-
export const VALUE_COORDINATE = 'ValueCoordinate';
123-
export type VALUE_COORDINATE = typeof VALUE_COORDINATE;
124-
125122
export const DIRECTIVE_COORDINATE = 'DirectiveCoordinate';
126123
export type DIRECTIVE_COORDINATE = typeof DIRECTIVE_COORDINATE;
127124

src/language/lexer.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@ export function isPunctuatorTokenKind(kind: TokenKind): boolean {
9898
kind === TokenKind.DOT ||
9999
kind === TokenKind.SPREAD ||
100100
kind === TokenKind.COLON ||
101-
kind === TokenKind.TWO_COLON ||
102101
kind === TokenKind.EQUALS ||
103102
kind === TokenKind.AT ||
104103
kind === TokenKind.BRACKET_L ||
@@ -272,14 +271,6 @@ function readNextToken(lexer: Lexer, start: number): Token {
272271
return readDot(lexer, position);
273272
}
274273
case 0x003a: // :
275-
if (body.charCodeAt(position + 1) === 0x003a) {
276-
return createToken(
277-
lexer,
278-
TokenKind.TWO_COLON,
279-
position,
280-
position + 2,
281-
);
282-
}
283274
return createToken(lexer, TokenKind.COLON, position, position + 1);
284275
case 0x003d: // =
285276
return createToken(lexer, TokenKind.EQUALS, position, position + 1);

src/language/parser.ts

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import type {
2323
EnumTypeExtensionNode,
2424
EnumValueDefinitionNode,
2525
EnumValueNode,
26-
FieldCoordinateNode,
2726
FieldDefinitionNode,
2827
FieldNode,
2928
FloatValueNode,
@@ -39,6 +38,7 @@ import type {
3938
IntValueNode,
4039
ListTypeNode,
4140
ListValueNode,
41+
MemberCoordinateNode,
4242
NamedTypeNode,
4343
NameNode,
4444
NonNullTypeNode,
@@ -63,7 +63,6 @@ import type {
6363
TypeSystemExtensionNode,
6464
UnionTypeDefinitionNode,
6565
UnionTypeExtensionNode,
66-
ValueCoordinateNode,
6766
ValueNode,
6867
VariableDefinitionNode,
6968
VariableNode,
@@ -1467,24 +1466,13 @@ export class Parser {
14671466
* - Name
14681467
* - Name . Name
14691468
* - Name . Name ( Name : )
1470-
* - Name :: Name
14711469
* - @ Name
14721470
* - @ Name ( Name : )
14731471
*/
14741472
parseSchemaCoordinate(): SchemaCoordinateNode {
14751473
const start = this._lexer.token;
14761474
const ofDirective = this.expectOptionalToken(TokenKind.AT);
14771475
const name = this.parseName();
1478-
1479-
if (!ofDirective && this.expectOptionalToken(TokenKind.TWO_COLON)) {
1480-
const valueName = this.parseName();
1481-
return this.node<ValueCoordinateNode>(start, {
1482-
kind: Kind.VALUE_COORDINATE,
1483-
name,
1484-
valueName,
1485-
});
1486-
}
1487-
14881476
let memberName: NameNode | undefined;
14891477
if (!ofDirective && this.expectOptionalToken(TokenKind.DOT)) {
14901478
memberName = this.parseName();
@@ -1520,10 +1508,10 @@ export class Parser {
15201508
argumentName,
15211509
});
15221510
}
1523-
return this.node<FieldCoordinateNode>(start, {
1524-
kind: Kind.FIELD_COORDINATE,
1511+
return this.node<MemberCoordinateNode>(start, {
1512+
kind: Kind.MEMBER_COORDINATE,
15251513
name,
1526-
fieldName: memberName,
1514+
memberName,
15271515
});
15281516
}
15291517

src/language/predicates.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,8 @@ export function isSchemaCoordinateNode(
117117
): node is SchemaCoordinateNode {
118118
return (
119119
node.kind === Kind.TYPE_COORDINATE ||
120-
node.kind === Kind.FIELD_COORDINATE ||
120+
node.kind === Kind.MEMBER_COORDINATE ||
121121
node.kind === Kind.ARGUMENT_COORDINATE ||
122-
node.kind === Kind.VALUE_COORDINATE ||
123122
node.kind === Kind.DIRECTIVE_COORDINATE ||
124123
node.kind === Kind.DIRECTIVE_ARGUMENT_COORDINATE
125124
);

src/language/printer.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -325,19 +325,15 @@ const printDocASTReducer: ASTReducer<string> = {
325325

326326
TypeCoordinate: { leave: ({ name }) => name },
327327

328-
FieldCoordinate: {
329-
leave: ({ name, fieldName }) => join([name, wrap('.', fieldName)]),
328+
MemberCoordinate: {
329+
leave: ({ name, memberName }) => join([name, wrap('.', memberName)]),
330330
},
331331

332332
ArgumentCoordinate: {
333333
leave: ({ name, fieldName, argumentName }) =>
334334
join([name, wrap('.', fieldName), wrap('(', argumentName, ':)')]),
335335
},
336336

337-
ValueCoordinate: {
338-
leave: ({ name, valueName }) => join([name, wrap('::', valueName)]),
339-
},
340-
341337
DirectiveCoordinate: { leave: ({ name }) => join(['@', name]) },
342338

343339
DirectiveArgumentCoordinate: {

src/language/tokenKind.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ export const TokenKind = {
1313
DOT: '.' as const,
1414
SPREAD: '...' as const,
1515
COLON: ':' as const,
16-
TWO_COLON: '::' as const,
1716
EQUALS: '=' as const,
1817
AT: '@' as const,
1918
BRACKET_L: '[' as const,

src/utilities/__tests__/resolveSchemaCoordinate-test.ts

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { describe, it } from 'mocha';
33

44
import type {
55
GraphQLEnumType,
6+
GraphQLField,
67
GraphQLInputObjectType,
78
GraphQLObjectType,
89
} from '../../type/definition.js';
@@ -71,16 +72,10 @@ describe('resolveSchemaCoordinate', () => {
7172
);
7273

7374
expect(() => resolveSchemaCoordinate(schema, 'String.field')).to.throw(
74-
'Expected "String" to be an Input Object, Object or Interface type.',
75+
'Expected "String" to be an Enum, Input Object, Object or Interface type.',
7576
);
7677
});
7778

78-
it('does not resolve meta-fields', () => {
79-
expect(
80-
resolveSchemaCoordinate(schema, 'Business.__typename'),
81-
).to.deep.equal(undefined);
82-
});
83-
8479
it('resolves a Input Field', () => {
8580
const type = schema.getType('SearchCriteria') as GraphQLInputObjectType;
8681
const inputField = type.getFields().filter;
@@ -101,15 +96,15 @@ describe('resolveSchemaCoordinate', () => {
10196
const type = schema.getType('SearchFilter') as GraphQLEnumType;
10297
const enumValue = type.getValue('OPEN_NOW');
10398
expect(
104-
resolveSchemaCoordinate(schema, 'SearchFilter::OPEN_NOW'),
99+
resolveSchemaCoordinate(schema, 'SearchFilter.OPEN_NOW'),
105100
).to.deep.equal({
106101
kind: 'EnumValue',
107102
type,
108103
enumValue,
109104
});
110105

111106
expect(
112-
resolveSchemaCoordinate(schema, 'SearchFilter::UNKNOWN'),
107+
resolveSchemaCoordinate(schema, 'SearchFilter.UNKNOWN'),
113108
).to.deep.equal(undefined);
114109
});
115110

@@ -186,4 +181,59 @@ describe('resolveSchemaCoordinate', () => {
186181
'Expected "unknown" to be defined as a directive in the schema.',
187182
);
188183
});
184+
185+
it('resolves a meta-field', () => {
186+
const type = schema.getType('Business') as GraphQLObjectType;
187+
const field = schema.getField(type, '__typename');
188+
expect(
189+
resolveSchemaCoordinate(schema, 'Business.__typename'),
190+
).to.deep.equal({
191+
kind: 'Field',
192+
type,
193+
field,
194+
});
195+
});
196+
197+
it('resolves a meta-field argument', () => {
198+
const type = schema.getType('Query') as GraphQLObjectType;
199+
const field = schema.getField(type, '__type') as GraphQLField;
200+
const fieldArgument = field.args.find((arg) => arg.name === 'name');
201+
expect(
202+
resolveSchemaCoordinate(schema, 'Query.__type(name:)'),
203+
).to.deep.equal({
204+
kind: 'FieldArgument',
205+
type,
206+
field,
207+
fieldArgument,
208+
});
209+
});
210+
211+
it('resolves an Introspection Type', () => {
212+
expect(resolveSchemaCoordinate(schema, '__Type')).to.deep.equal({
213+
kind: 'NamedType',
214+
type: schema.getType('__Type'),
215+
});
216+
});
217+
218+
it('resolves an Introspection Type Field', () => {
219+
const type = schema.getType('__Directive') as GraphQLObjectType;
220+
const field = type.getFields().name;
221+
expect(resolveSchemaCoordinate(schema, '__Directive.name')).to.deep.equal({
222+
kind: 'Field',
223+
type,
224+
field,
225+
});
226+
});
227+
228+
it('resolves an Introspection Type Enum Value', () => {
229+
const type = schema.getType('__DirectiveLocation') as GraphQLEnumType;
230+
const enumValue = type.getValue('INLINE_FRAGMENT');
231+
expect(
232+
resolveSchemaCoordinate(schema, '__DirectiveLocation.INLINE_FRAGMENT'),
233+
).to.deep.equal({
234+
kind: 'EnumValue',
235+
type,
236+
enumValue,
237+
});
238+
});
189239
});

0 commit comments

Comments
 (0)