Skip to content

Commit c68ca9b

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 `resolveASTSchemaCoordinate()` which implement the semantics (name mirrored from `buildASTSchema`) as well as the return type `ResolvedSchemaElement`
1 parent 2f893d6 commit c68ca9b

17 files changed

+693
-8
lines changed

src/index.ts

+6
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ export {
201201
parseValue,
202202
parseConstValue,
203203
parseType,
204+
parseSchemaCoordinate,
204205
/** Print */
205206
print,
206207
/** Visit */
@@ -221,6 +222,7 @@ export {
221222
isTypeDefinitionNode,
222223
isTypeSystemExtensionNode,
223224
isTypeExtensionNode,
225+
isSchemaCoordinateNode,
224226
} from './language/index';
225227

226228
export type {
@@ -295,6 +297,7 @@ export type {
295297
UnionTypeExtensionNode,
296298
EnumTypeExtensionNode,
297299
InputObjectTypeExtensionNode,
300+
SchemaCoordinateNode,
298301
} from './language/index';
299302

300303
/** Execute GraphQL queries. */
@@ -436,6 +439,8 @@ export {
436439
DangerousChangeType,
437440
findBreakingChanges,
438441
findDangerousChanges,
442+
resolveSchemaCoordinate,
443+
resolveASTSchemaCoordinate,
439444
} from './utilities/index';
440445

441446
export type {
@@ -465,4 +470,5 @@ export type {
465470
BreakingChange,
466471
DangerousChange,
467472
TypedQueryDocumentNode,
473+
ResolvedSchemaElement,
468474
} from './utilities/index';

src/language/__tests__/lexer-test.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -657,7 +657,8 @@ describe('Lexer', () => {
657657
});
658658

659659
expectSyntaxError('.123').to.deep.equal({
660-
message: 'Syntax Error: Unexpected character: ".".',
660+
message:
661+
'Syntax Error: Invalid number, expected digit before ".", did you mean "0.123"?',
661662
locations: [{ line: 1, column: 1 }],
662663
});
663664

@@ -762,6 +763,13 @@ describe('Lexer', () => {
762763
value: undefined,
763764
});
764765

766+
expect(lexOne('.')).to.contain({
767+
kind: TokenKind.DOT,
768+
start: 0,
769+
end: 1,
770+
value: undefined,
771+
});
772+
765773
expect(lexOne('...')).to.contain({
766774
kind: TokenKind.SPREAD,
767775
start: 0,
@@ -828,7 +836,7 @@ describe('Lexer', () => {
828836

829837
it('lex reports useful unknown character error', () => {
830838
expectSyntaxError('..').to.deep.equal({
831-
message: 'Syntax Error: Unexpected character: ".".',
839+
message: 'Syntax Error: Unexpected "..", did you mean "..."?',
832840
locations: [{ line: 1, column: 1 }],
833841
});
834842

src/language/__tests__/parser-test.ts

+132-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@ import { inspect } from '../../jsutils/inspect';
99
import { Kind } from '../kinds';
1010
import { Source } from '../source';
1111
import { TokenKind } from '../tokenKind';
12-
import { parse, parseValue, parseConstValue, parseType } from '../parser';
12+
import {
13+
parse,
14+
parseValue,
15+
parseConstValue,
16+
parseType,
17+
parseSchemaCoordinate,
18+
} from '../parser';
1319

1420
import { toJSONDeep } from './toJSONDeep';
1521

@@ -619,4 +625,129 @@ describe('Parser', () => {
619625
});
620626
});
621627
});
628+
629+
describe('parseSchemaCoordinate', () => {
630+
it('parses Name', () => {
631+
const result = parseSchemaCoordinate('MyType');
632+
expect(toJSONDeep(result)).to.deep.equal({
633+
kind: Kind.SCHEMA_COORDINATE,
634+
loc: { start: 0, end: 6 },
635+
ofDirective: false,
636+
name: {
637+
kind: Kind.NAME,
638+
loc: { start: 0, end: 6 },
639+
value: 'MyType',
640+
},
641+
memberName: undefined,
642+
argumentName: undefined,
643+
});
644+
});
645+
646+
it('parses Name . Name', () => {
647+
const result = parseSchemaCoordinate('MyType.field');
648+
expect(toJSONDeep(result)).to.deep.equal({
649+
kind: Kind.SCHEMA_COORDINATE,
650+
loc: { start: 0, end: 12 },
651+
ofDirective: false,
652+
name: {
653+
kind: Kind.NAME,
654+
loc: { start: 0, end: 6 },
655+
value: 'MyType',
656+
},
657+
memberName: {
658+
kind: Kind.NAME,
659+
loc: { start: 7, end: 12 },
660+
value: 'field',
661+
},
662+
argumentName: undefined,
663+
});
664+
});
665+
666+
it('rejects Name . Name . Name', () => {
667+
expect(() => parseSchemaCoordinate('MyType.field.deep'))
668+
.to.throw()
669+
.to.deep.equal({
670+
message: 'Syntax Error: Expected <EOF>, found ".".',
671+
locations: [{ line: 1, column: 13 }],
672+
});
673+
});
674+
675+
it('parses Name . Name ( Name : )', () => {
676+
const result = parseSchemaCoordinate('MyType.field(arg:)');
677+
expect(toJSONDeep(result)).to.deep.equal({
678+
kind: Kind.SCHEMA_COORDINATE,
679+
loc: { start: 0, end: 18 },
680+
ofDirective: false,
681+
name: {
682+
kind: Kind.NAME,
683+
loc: { start: 0, end: 6 },
684+
value: 'MyType',
685+
},
686+
memberName: {
687+
kind: Kind.NAME,
688+
loc: { start: 7, end: 12 },
689+
value: 'field',
690+
},
691+
argumentName: {
692+
kind: Kind.NAME,
693+
loc: { start: 13, end: 16 },
694+
value: 'arg',
695+
},
696+
});
697+
});
698+
699+
it('rejects Name . Name ( Name : Name )', () => {
700+
expect(() => parseSchemaCoordinate('MyType.field(arg: value)'))
701+
.to.throw()
702+
.to.deep.equal({
703+
message: 'Syntax Error: Expected ")", found Name "value".',
704+
locations: [{ line: 1, column: 19 }],
705+
});
706+
});
707+
708+
it('parses @ Name', () => {
709+
const result = parseSchemaCoordinate('@myDirective');
710+
expect(toJSONDeep(result)).to.deep.equal({
711+
kind: Kind.SCHEMA_COORDINATE,
712+
loc: { start: 0, end: 12 },
713+
ofDirective: true,
714+
name: {
715+
kind: Kind.NAME,
716+
loc: { start: 1, end: 12 },
717+
value: 'myDirective',
718+
},
719+
memberName: undefined,
720+
argumentName: undefined,
721+
});
722+
});
723+
724+
it('parses @ Name ( Name : )', () => {
725+
const result = parseSchemaCoordinate('@myDirective(arg:)');
726+
expect(toJSONDeep(result)).to.deep.equal({
727+
kind: Kind.SCHEMA_COORDINATE,
728+
loc: { start: 0, end: 18 },
729+
ofDirective: true,
730+
name: {
731+
kind: Kind.NAME,
732+
loc: { start: 1, end: 12 },
733+
value: 'myDirective',
734+
},
735+
memberName: undefined,
736+
argumentName: {
737+
kind: Kind.NAME,
738+
loc: { start: 13, end: 16 },
739+
value: 'arg',
740+
},
741+
});
742+
});
743+
744+
it('rejects @ Name . Name', () => {
745+
expect(() => parseSchemaCoordinate('@myDirective.field'))
746+
.to.throw()
747+
.to.deep.equal({
748+
message: 'Syntax Error: Expected <EOF>, found ".".',
749+
locations: [{ line: 1, column: 13 }],
750+
});
751+
});
752+
});
622753
});

src/language/__tests__/predicates-test.ts

+7
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
isTypeDefinitionNode,
1616
isTypeSystemExtensionNode,
1717
isTypeExtensionNode,
18+
isSchemaCoordinateNode,
1819
} from '../predicates';
1920

2021
function filterNodes(predicate: (node: ASTNode) => boolean): Array<string> {
@@ -141,4 +142,10 @@ describe('AST node predicates', () => {
141142
'InputObjectTypeExtension',
142143
]);
143144
});
145+
146+
it('isSchemaCoordinateNode', () => {
147+
expect(filterNodes(isSchemaCoordinateNode)).to.deep.equal([
148+
'SchemaCoordinate',
149+
]);
150+
});
144151
});

src/language/__tests__/printer-test.ts

+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', () => {
@@ -216,4 +216,18 @@ describe('Printer: Query document', () => {
216216
`),
217217
);
218218
});
219+
220+
it('prints schema coordinates', () => {
221+
expect(print(parseSchemaCoordinate(' Name '))).to.equal('Name');
222+
expect(print(parseSchemaCoordinate(' Name . field '))).to.equal(
223+
'Name.field',
224+
);
225+
expect(print(parseSchemaCoordinate(' Name . field ( arg: )'))).to.equal(
226+
'Name.field(arg:)',
227+
);
228+
expect(print(parseSchemaCoordinate(' @ name '))).to.equal('@name');
229+
expect(print(parseSchemaCoordinate(' @ name (arg:) '))).to.equal(
230+
'@name(arg:)',
231+
);
232+
});
219233
});

src/language/ast.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,8 @@ export type ASTNode =
176176
| InterfaceTypeExtensionNode
177177
| UnionTypeExtensionNode
178178
| EnumTypeExtensionNode
179-
| InputObjectTypeExtensionNode;
179+
| InputObjectTypeExtensionNode
180+
| SchemaCoordinateNode;
180181

181182
/**
182183
* Utility type listing all nodes indexed by their kind.
@@ -225,6 +226,7 @@ export interface ASTKindToNode {
225226
UnionTypeExtension: UnionTypeExtensionNode;
226227
EnumTypeExtension: EnumTypeExtensionNode;
227228
InputObjectTypeExtension: InputObjectTypeExtensionNode;
229+
SchemaCoordinate: SchemaCoordinateNode;
228230
}
229231

230232
/** Name */
@@ -670,3 +672,14 @@ export interface InputObjectTypeExtensionNode {
670672
readonly directives?: ReadonlyArray<ConstDirectiveNode>;
671673
readonly fields?: ReadonlyArray<InputValueDefinitionNode>;
672674
}
675+
676+
// Schema Coordinates
677+
678+
export interface SchemaCoordinateNode {
679+
readonly kind: 'SchemaCoordinate';
680+
readonly loc?: Location;
681+
readonly ofDirective: boolean;
682+
readonly name: NameNode;
683+
readonly memberName?: NameNode;
684+
readonly argumentName?: NameNode;
685+
}

src/language/index.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@ export type { TokenKindEnum } from './tokenKind';
1313

1414
export { Lexer } from './lexer';
1515

16-
export { parse, parseValue, parseConstValue, parseType } from './parser';
16+
export {
17+
parse,
18+
parseValue,
19+
parseConstValue,
20+
parseType,
21+
parseSchemaCoordinate,
22+
} from './parser';
1723
export type { ParseOptions } from './parser';
1824

1925
export { print } from './printer';
@@ -85,6 +91,7 @@ export type {
8591
UnionTypeExtensionNode,
8692
EnumTypeExtensionNode,
8793
InputObjectTypeExtensionNode,
94+
SchemaCoordinateNode,
8895
} from './ast';
8996

9097
export {
@@ -98,6 +105,7 @@ export {
98105
isTypeDefinitionNode,
99106
isTypeSystemExtensionNode,
100107
isTypeExtensionNode,
108+
isSchemaCoordinateNode,
101109
} from './predicates';
102110

103111
export { DirectiveLocation } from './directiveLocation';

src/language/kinds.ts

+3
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ export const Kind = Object.freeze({
6666
UNION_TYPE_EXTENSION: 'UnionTypeExtension',
6767
ENUM_TYPE_EXTENSION: 'EnumTypeExtension',
6868
INPUT_OBJECT_TYPE_EXTENSION: 'InputObjectTypeExtension',
69+
70+
/** Schema Coordinates */
71+
SCHEMA_COORDINATE: 'SchemaCoordinate',
6972
} as const);
7073

7174
/**

0 commit comments

Comments
 (0)