Skip to content

Commit 0a5630a

Browse files
committed
Schema Coordinates
Implements graphql/graphql-spec#794 Adds: * DOT punctuator in lexer * Improvements to lexer errors around misuse of `.` * Minor improvement to parser core which simplified this addition * `SchemaCoordinate` node and `isSchemaCoodinate()` predicate * Support in `print()` and `visit()` * Added function `parseSchemaCoordinate()` since it is a parser entry point. * Added function `resolveSchemaCoordinate()` and `resolveASTSchemeCoordinate()` which implement the semantics (name mirrored from `buildASTSchema`) as well as the return type `GraphQLSchemaElement`
1 parent b9fd53b commit 0a5630a

30 files changed

+687
-17
lines changed

src/index.d.ts

+6
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ export {
181181
GraphQLScalarSerializer,
182182
GraphQLScalarValueParser,
183183
GraphQLScalarLiteralParser,
184+
GraphQLSchemaElement,
184185
} from './type/index';
185186

186187
// Parse and operate on GraphQL language source files.
@@ -199,6 +200,7 @@ export {
199200
parse,
200201
parseValue,
201202
parseType,
203+
parseSchemaCoordinate,
202204
// Print
203205
print,
204206
// Visit
@@ -218,6 +220,7 @@ export {
218220
isTypeDefinitionNode,
219221
isTypeSystemExtensionNode,
220222
isTypeExtensionNode,
223+
isSchemaCoordinateNode,
221224
} from './language/index';
222225

223226
export {
@@ -286,6 +289,7 @@ export {
286289
UnionTypeExtensionNode,
287290
EnumTypeExtensionNode,
288291
InputObjectTypeExtensionNode,
292+
SchemaCoordinateNode,
289293
} from './language/index';
290294

291295
// Execute GraphQL queries.
@@ -427,6 +431,8 @@ export {
427431
DangerousChangeType,
428432
findBreakingChanges,
429433
findDangerousChanges,
434+
resolveSchemaCoordinate,
435+
resolveASTSchemaCoordinate,
430436
} from './utilities/index';
431437

432438
export {

src/index.js

+6
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ export type {
168168
GraphQLScalarSerializer,
169169
GraphQLScalarValueParser,
170170
GraphQLScalarLiteralParser,
171+
GraphQLSchemaElement,
171172
} from './type/index';
172173

173174
// Parse and operate on GraphQL language source files.
@@ -186,6 +187,7 @@ export {
186187
parse,
187188
parseValue,
188189
parseType,
190+
parseSchemaCoordinate,
189191
// Print
190192
print,
191193
// Visit
@@ -205,6 +207,7 @@ export {
205207
isTypeDefinitionNode,
206208
isTypeSystemExtensionNode,
207209
isTypeExtensionNode,
210+
isSchemaCoordinateNode,
208211
} from './language/index';
209212

210213
export type {
@@ -273,6 +276,7 @@ export type {
273276
UnionTypeExtensionNode,
274277
EnumTypeExtensionNode,
275278
InputObjectTypeExtensionNode,
279+
SchemaCoordinateNode,
276280
} from './language/index';
277281

278282
// Execute GraphQL queries.
@@ -416,6 +420,8 @@ export {
416420
DangerousChangeType,
417421
findBreakingChanges,
418422
findDangerousChanges,
423+
resolveSchemaCoordinate,
424+
resolveASTSchemaCoordinate,
419425
} from './utilities/index';
420426

421427
export type {

src/language/__tests__/lexer-test.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -649,7 +649,7 @@ describe('Lexer', () => {
649649
});
650650

651651
expectSyntaxError('.123').to.deep.equal({
652-
message: 'Syntax Error: Cannot parse the unexpected character ".".',
652+
message: 'Syntax Error: Invalid number, expected digit before ".".',
653653
locations: [{ line: 1, column: 1 }],
654654
});
655655

@@ -753,6 +753,13 @@ describe('Lexer', () => {
753753
value: undefined,
754754
});
755755

756+
expect(lexOne('.')).to.contain({
757+
kind: TokenKind.DOT,
758+
start: 0,
759+
end: 1,
760+
value: undefined,
761+
});
762+
756763
expect(lexOne('...')).to.contain({
757764
kind: TokenKind.SPREAD,
758765
start: 0,
@@ -819,7 +826,7 @@ describe('Lexer', () => {
819826

820827
it('lex reports useful unknown character error', () => {
821828
expectSyntaxError('..').to.deep.equal({
822-
message: 'Syntax Error: Cannot parse the unexpected character ".".',
829+
message: 'Syntax Error: Cannot parse the unexpected character "..".',
823830
locations: [{ line: 1, column: 1 }],
824831
});
825832

src/language/__tests__/parser-test.js

+126-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { inspect } from '../../jsutils/inspect';
99
import { Kind } from '../kinds';
1010
import { Source } from '../source';
1111
import { TokenKind } from '../tokenKind';
12-
import { parse, parseValue, parseType } from '../parser';
12+
import { parse, parseValue, parseType, parseSchemaCoordinate } from '../parser';
1313

1414
import { toJSONDeep } from './toJSONDeep';
1515

@@ -531,4 +531,129 @@ describe('Parser', () => {
531531
});
532532
});
533533
});
534+
535+
describe('parseSchemaCoordinate', () => {
536+
it('parses Name', () => {
537+
const result = parseSchemaCoordinate('MyType');
538+
expect(toJSONDeep(result)).to.deep.equal({
539+
kind: Kind.SCHEMA_COORDINATE,
540+
loc: { start: 0, end: 6 },
541+
isDirective: false,
542+
name: {
543+
kind: Kind.NAME,
544+
loc: { start: 0, end: 6 },
545+
value: 'MyType',
546+
},
547+
fieldName: undefined,
548+
argumentName: undefined,
549+
});
550+
});
551+
552+
it('parses Name . Name', () => {
553+
const result = parseSchemaCoordinate('MyType.field');
554+
expect(toJSONDeep(result)).to.deep.equal({
555+
kind: Kind.SCHEMA_COORDINATE,
556+
loc: { start: 0, end: 12 },
557+
isDirective: false,
558+
name: {
559+
kind: Kind.NAME,
560+
loc: { start: 0, end: 6 },
561+
value: 'MyType',
562+
},
563+
fieldName: {
564+
kind: Kind.NAME,
565+
loc: { start: 7, end: 12 },
566+
value: 'field',
567+
},
568+
argumentName: undefined,
569+
});
570+
});
571+
572+
it('rejects Name . Name . Name', () => {
573+
expect(() => parseSchemaCoordinate('MyType.field.deep'))
574+
.to.throw()
575+
.to.deep.equal({
576+
message: 'Syntax Error: Expected <EOF>, found ".".',
577+
locations: [{ line: 1, column: 13 }],
578+
});
579+
});
580+
581+
it('parses Name . Name ( Name : )', () => {
582+
const result = parseSchemaCoordinate('MyType.field(arg:)');
583+
expect(toJSONDeep(result)).to.deep.equal({
584+
kind: Kind.SCHEMA_COORDINATE,
585+
loc: { start: 0, end: 18 },
586+
isDirective: false,
587+
name: {
588+
kind: Kind.NAME,
589+
loc: { start: 0, end: 6 },
590+
value: 'MyType',
591+
},
592+
fieldName: {
593+
kind: Kind.NAME,
594+
loc: { start: 7, end: 12 },
595+
value: 'field',
596+
},
597+
argumentName: {
598+
kind: Kind.NAME,
599+
loc: { start: 13, end: 16 },
600+
value: 'arg',
601+
},
602+
});
603+
});
604+
605+
it('rejects Name . Name ( Name : Name )', () => {
606+
expect(() => parseSchemaCoordinate('MyType.field(arg: value)'))
607+
.to.throw()
608+
.to.deep.equal({
609+
message: 'Syntax Error: Expected ")", found Name "value".',
610+
locations: [{ line: 1, column: 19 }],
611+
});
612+
});
613+
614+
it('parses @ Name', () => {
615+
const result = parseSchemaCoordinate('@myDirective');
616+
expect(toJSONDeep(result)).to.deep.equal({
617+
kind: Kind.SCHEMA_COORDINATE,
618+
loc: { start: 0, end: 12 },
619+
isDirective: true,
620+
name: {
621+
kind: Kind.NAME,
622+
loc: { start: 1, end: 12 },
623+
value: 'myDirective',
624+
},
625+
fieldName: undefined,
626+
argumentName: undefined,
627+
});
628+
});
629+
630+
it('parses @ Name ( Name : )', () => {
631+
const result = parseSchemaCoordinate('@myDirective(arg:)');
632+
expect(toJSONDeep(result)).to.deep.equal({
633+
kind: Kind.SCHEMA_COORDINATE,
634+
loc: { start: 0, end: 18 },
635+
isDirective: true,
636+
name: {
637+
kind: Kind.NAME,
638+
loc: { start: 1, end: 12 },
639+
value: 'myDirective',
640+
},
641+
fieldName: undefined,
642+
argumentName: {
643+
kind: Kind.NAME,
644+
loc: { start: 13, end: 16 },
645+
value: 'arg',
646+
},
647+
});
648+
});
649+
650+
it('rejects @ Name . Name', () => {
651+
expect(() => parseSchemaCoordinate('@myDirective.field'))
652+
.to.throw()
653+
.to.deep.equal({
654+
message: 'Syntax Error: Expected <EOF>, found ".".',
655+
locations: [{ line: 1, column: 13 }],
656+
});
657+
});
658+
});
534659
});

src/language/__tests__/predicates-test.js

+7
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
isTypeDefinitionNode,
1414
isTypeSystemExtensionNode,
1515
isTypeExtensionNode,
16+
isSchemaCoordinateNode,
1617
} from '../predicates';
1718

1819
const allASTNodes: Array<ASTNode> = Object.values(Kind).map(
@@ -129,4 +130,10 @@ describe('AST node predicates', () => {
129130
'InputObjectTypeExtension',
130131
]);
131132
});
133+
134+
it('isSchemaCoordinateNode', () => {
135+
expect(filterNodes(isSchemaCoordinateNode)).to.deep.equal([
136+
'SchemaCoordinate',
137+
]);
138+
});
132139
});

src/language/__tests__/printer-test.js

+15-1
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { describe, it } from 'mocha';
33

44
import { dedent, dedentString } from '../../__testUtils__/dedent';
55
import { kitchenSinkQuery } from '../../__testUtils__/kitchenSinkQuery';
6+
import { parseSchemaCoordinate, parse } from '../parser';
67

7-
import { parse } from '../parser';
88
import { print } from '../printer';
99

1010
describe('Printer: Query document', () => {
@@ -214,4 +214,18 @@ describe('Printer: Query document', () => {
214214
`),
215215
);
216216
});
217+
218+
it('prints schema coordinates', () => {
219+
expect(print(parseSchemaCoordinate(' Name '))).to.equal('Name');
220+
expect(print(parseSchemaCoordinate(' Name . field '))).to.equal(
221+
'Name.field',
222+
);
223+
expect(print(parseSchemaCoordinate(' Name . field ( arg: )'))).to.equal(
224+
'Name.field(arg:)',
225+
);
226+
expect(print(parseSchemaCoordinate(' @ name '))).to.equal('@name');
227+
expect(print(parseSchemaCoordinate(' @ name (arg:) '))).to.equal(
228+
'@name(arg:)',
229+
);
230+
});
217231
});

src/language/ast.d.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ export type ASTNode =
148148
| InterfaceTypeExtensionNode
149149
| UnionTypeExtensionNode
150150
| EnumTypeExtensionNode
151-
| InputObjectTypeExtensionNode;
151+
| InputObjectTypeExtensionNode
152+
| SchemaCoordinateNode;
152153

153154
/**
154155
* Utility type listing all nodes indexed by their kind.
@@ -197,6 +198,7 @@ export interface ASTKindToNode {
197198
UnionTypeExtension: UnionTypeExtensionNode;
198199
EnumTypeExtension: EnumTypeExtensionNode;
199200
InputObjectTypeExtension: InputObjectTypeExtensionNode;
201+
SchemaCoordinate: SchemaCoordinateNode;
200202
}
201203

202204
// Name
@@ -599,3 +601,14 @@ export interface InputObjectTypeExtensionNode {
599601
readonly directives?: ReadonlyArray<DirectiveNode>;
600602
readonly fields?: ReadonlyArray<InputValueDefinitionNode>;
601603
}
604+
605+
// Schema Coordinates
606+
607+
export interface SchemaCoordinateNode {
608+
readonly kind: 'SchemaCoordinate';
609+
readonly loc?: Location;
610+
readonly isDirective: boolean;
611+
readonly name: NameNode;
612+
readonly fieldName?: NameNode;
613+
readonly argumentName?: NameNode;
614+
}

src/language/ast.js

+14-1
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,8 @@ export type ASTNode =
174174
| InterfaceTypeExtensionNode
175175
| UnionTypeExtensionNode
176176
| EnumTypeExtensionNode
177-
| InputObjectTypeExtensionNode;
177+
| InputObjectTypeExtensionNode
178+
| SchemaCoordinateNode;
178179

179180
/**
180181
* Utility type listing all nodes indexed by their kind.
@@ -223,6 +224,7 @@ export type ASTKindToNode = {|
223224
UnionTypeExtension: UnionTypeExtensionNode,
224225
EnumTypeExtension: EnumTypeExtensionNode,
225226
InputObjectTypeExtension: InputObjectTypeExtensionNode,
227+
SchemaCoordinate: SchemaCoordinateNode,
226228
|};
227229

228230
// Name
@@ -625,3 +627,14 @@ export type InputObjectTypeExtensionNode = {|
625627
+directives?: $ReadOnlyArray<DirectiveNode>,
626628
+fields?: $ReadOnlyArray<InputValueDefinitionNode>,
627629
|};
630+
631+
// Schema Coordinates
632+
633+
export type SchemaCoordinateNode = {|
634+
+kind: 'SchemaCoordinate',
635+
+loc?: Location,
636+
+isDirective: boolean,
637+
+name: NameNode,
638+
+fieldName?: NameNode,
639+
+argumentName?: NameNode,
640+
|};

src/language/index.d.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@ export { printLocation, printSourceLocation } from './printLocation';
66
export { Kind, KindEnum } from './kinds';
77
export { TokenKind, TokenKindEnum } from './tokenKind';
88
export { Lexer } from './lexer';
9-
export { parse, parseValue, parseType, ParseOptions } from './parser';
9+
export {
10+
parse,
11+
parseValue,
12+
parseType,
13+
parseSchemaCoordinate,
14+
ParseOptions,
15+
} from './parser';
1016
export { print } from './printer';
1117
export {
1218
visit,
@@ -76,6 +82,7 @@ export {
7682
UnionTypeExtensionNode,
7783
EnumTypeExtensionNode,
7884
InputObjectTypeExtensionNode,
85+
SchemaCoordinateNode,
7986
} from './ast';
8087

8188
export {
@@ -88,6 +95,7 @@ export {
8895
isTypeDefinitionNode,
8996
isTypeSystemExtensionNode,
9097
isTypeExtensionNode,
98+
isSchemaCoordinateNode,
9199
} from './predicates';
92100

93101
export { DirectiveLocation, DirectiveLocationEnum } from './directiveLocation';

0 commit comments

Comments
 (0)