Skip to content

Commit ab41972

Browse files
committed
Backport schema coordinates
1 parent 364f17f commit ab41972

18 files changed

+1177
-14
lines changed

cspell.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ ignoreRegExpList:
4646
- u\{[0-9a-f]{1,8}\}
4747

4848
words:
49+
- Coodinate
50+
- metafield
4951
- graphiql
5052
- sublinks
5153
- instanceof

src/index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ export {
215215
parseValue,
216216
parseConstValue,
217217
parseType,
218+
parseSchemaCoordinate,
218219
// Print
219220
print,
220221
// Visit
@@ -236,6 +237,7 @@ export {
236237
isTypeDefinitionNode,
237238
isTypeSystemExtensionNode,
238239
isTypeExtensionNode,
240+
isSchemaCoordinateNode,
239241
} from './language/index';
240242

241243
export type {
@@ -310,6 +312,13 @@ export type {
310312
UnionTypeExtensionNode,
311313
EnumTypeExtensionNode,
312314
InputObjectTypeExtensionNode,
315+
// Schema Coordinates
316+
SchemaCoordinateNode,
317+
TypeCoordinateNode,
318+
MemberCoordinateNode,
319+
ArgumentCoordinateNode,
320+
DirectiveCoordinateNode,
321+
DirectiveArgumentCoordinateNode,
313322
} from './language/index';
314323

315324
// Execute GraphQL queries.
@@ -460,6 +469,9 @@ export {
460469
DangerousChangeType,
461470
findBreakingChanges,
462471
findDangerousChanges,
472+
// Schema Coordinates
473+
resolveSchemaCoordinate,
474+
resolveASTSchemaCoordinate,
463475
} from './utilities/index';
464476

465477
export type {
@@ -489,4 +501,6 @@ export type {
489501
BreakingChange,
490502
DangerousChange,
491503
TypedQueryDocumentNode,
504+
// Schema Coordinates
505+
ResolvedSchemaElement,
492506
} from './utilities/index';

src/language/__tests__/parser-test.ts

Lines changed: 181 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@ import { kitchenSinkQuery } from '../../__testUtils__/kitchenSinkQuery';
88
import { inspect } from '../../jsutils/inspect';
99

1010
import { Kind } from '../kinds';
11-
import { parse, parseConstValue, parseType, parseValue } from '../parser';
11+
import {
12+
parse,
13+
parseConstValue,
14+
parseSchemaCoordinate,
15+
parseType,
16+
parseValue,
17+
} from '../parser';
1218
import { Source } from '../source';
1319
import { TokenKind } from '../tokenKind';
1420

@@ -729,6 +735,180 @@ describe('Parser', () => {
729735
});
730736
});
731737

738+
describe('parseSchemaCoordinate', () => {
739+
it('parses Name', () => {
740+
const result = parseSchemaCoordinate('MyType');
741+
expectJSON(result).toDeepEqual({
742+
kind: Kind.TYPE_COORDINATE,
743+
loc: { start: 0, end: 6 },
744+
name: {
745+
kind: Kind.NAME,
746+
loc: { start: 0, end: 6 },
747+
value: 'MyType',
748+
},
749+
});
750+
});
751+
752+
it('parses Name . Name', () => {
753+
const result = parseSchemaCoordinate('MyType.field');
754+
expectJSON(result).toDeepEqual({
755+
kind: Kind.MEMBER_COORDINATE,
756+
loc: { start: 0, end: 12 },
757+
name: {
758+
kind: Kind.NAME,
759+
loc: { start: 0, end: 6 },
760+
value: 'MyType',
761+
},
762+
memberName: {
763+
kind: Kind.NAME,
764+
loc: { start: 7, end: 12 },
765+
value: 'field',
766+
},
767+
});
768+
});
769+
770+
it('rejects Name . Name . Name', () => {
771+
expect(() => parseSchemaCoordinate('MyType.field.deep'))
772+
.to.throw()
773+
.to.deep.include({
774+
message: 'Syntax Error: Expected <EOF>, found ".".',
775+
locations: [{ line: 1, column: 13 }],
776+
});
777+
});
778+
779+
it('parses Name . Name ( Name : )', () => {
780+
const result = parseSchemaCoordinate('MyType.field(arg:)');
781+
expectJSON(result).toDeepEqual({
782+
kind: Kind.ARGUMENT_COORDINATE,
783+
loc: { start: 0, end: 18 },
784+
name: {
785+
kind: Kind.NAME,
786+
loc: { start: 0, end: 6 },
787+
value: 'MyType',
788+
},
789+
fieldName: {
790+
kind: Kind.NAME,
791+
loc: { start: 7, end: 12 },
792+
value: 'field',
793+
},
794+
argumentName: {
795+
kind: Kind.NAME,
796+
loc: { start: 13, end: 16 },
797+
value: 'arg',
798+
},
799+
});
800+
});
801+
802+
it('rejects Name . Name ( Name : Name )', () => {
803+
expect(() => parseSchemaCoordinate('MyType.field(arg: value)'))
804+
.to.throw()
805+
.to.deep.include({
806+
message: 'Syntax Error: Invalid character: " ".',
807+
locations: [{ line: 1, column: 18 }],
808+
});
809+
});
810+
811+
it('parses @ Name', () => {
812+
const result = parseSchemaCoordinate('@myDirective');
813+
expectJSON(result).toDeepEqual({
814+
kind: Kind.DIRECTIVE_COORDINATE,
815+
loc: { start: 0, end: 12 },
816+
name: {
817+
kind: Kind.NAME,
818+
loc: { start: 1, end: 12 },
819+
value: 'myDirective',
820+
},
821+
});
822+
});
823+
824+
it('parses @ Name ( Name : )', () => {
825+
const result = parseSchemaCoordinate('@myDirective(arg:)');
826+
expectJSON(result).toDeepEqual({
827+
kind: Kind.DIRECTIVE_ARGUMENT_COORDINATE,
828+
loc: { start: 0, end: 18 },
829+
name: {
830+
kind: Kind.NAME,
831+
loc: { start: 1, end: 12 },
832+
value: 'myDirective',
833+
},
834+
argumentName: {
835+
kind: Kind.NAME,
836+
loc: { start: 13, end: 16 },
837+
value: 'arg',
838+
},
839+
});
840+
});
841+
842+
it('parses __Type', () => {
843+
const result = parseSchemaCoordinate('__Type');
844+
expectJSON(result).toDeepEqual({
845+
kind: Kind.TYPE_COORDINATE,
846+
loc: { start: 0, end: 6 },
847+
name: {
848+
kind: Kind.NAME,
849+
loc: { start: 0, end: 6 },
850+
value: '__Type',
851+
},
852+
});
853+
});
854+
855+
it('parses Type.__metafield', () => {
856+
const result = parseSchemaCoordinate('Type.__metafield');
857+
expectJSON(result).toDeepEqual({
858+
kind: Kind.MEMBER_COORDINATE,
859+
loc: { start: 0, end: 16 },
860+
name: {
861+
kind: Kind.NAME,
862+
loc: { start: 0, end: 4 },
863+
value: 'Type',
864+
},
865+
memberName: {
866+
kind: Kind.NAME,
867+
loc: { start: 5, end: 16 },
868+
value: '__metafield',
869+
},
870+
});
871+
});
872+
873+
it('parses Type.__metafield(arg:)', () => {
874+
const result = parseSchemaCoordinate('Type.__metafield(arg:)');
875+
expectJSON(result).toDeepEqual({
876+
kind: Kind.ARGUMENT_COORDINATE,
877+
loc: { start: 0, end: 22 },
878+
name: {
879+
kind: Kind.NAME,
880+
loc: { start: 0, end: 4 },
881+
value: 'Type',
882+
},
883+
fieldName: {
884+
kind: Kind.NAME,
885+
loc: { start: 5, end: 16 },
886+
value: '__metafield',
887+
},
888+
argumentName: {
889+
kind: Kind.NAME,
890+
loc: { start: 17, end: 20 },
891+
value: 'arg',
892+
},
893+
});
894+
});
895+
896+
it('rejects @ Name . Name', () => {
897+
expect(() => parseSchemaCoordinate('@myDirective.field'))
898+
.to.throw()
899+
.to.deep.include({
900+
message: 'Syntax Error: Expected <EOF>, found ".".',
901+
locations: [{ line: 1, column: 13 }],
902+
});
903+
});
904+
905+
it('accepts a Source object', () => {
906+
expect(parseSchemaCoordinate('MyType')).to.deep.equal(
907+
parseSchemaCoordinate(new Source('MyType')),
908+
);
909+
});
910+
});
911+
732912
describe('operation and variable definition descriptions', () => {
733913
it('parses operation with description and variable descriptions', () => {
734914
const result = parse(dedent`

src/language/__tests__/predicates-test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
isConstValueNode,
99
isDefinitionNode,
1010
isExecutableDefinitionNode,
11+
isSchemaCoordinateNode,
1112
isSelectionNode,
1213
isTypeDefinitionNode,
1314
isTypeExtensionNode,
@@ -141,4 +142,16 @@ describe('AST node predicates', () => {
141142
'InputObjectTypeExtension',
142143
]);
143144
});
145+
146+
it('isSchemaCoordinateNode', () => {
147+
expect(
148+
[
149+
Kind.TYPE_COORDINATE,
150+
Kind.MEMBER_COORDINATE,
151+
Kind.ARGUMENT_COORDINATE,
152+
Kind.DIRECTIVE_COORDINATE,
153+
Kind.DIRECTIVE_ARGUMENT_COORDINATE,
154+
].every((kind) => isSchemaCoordinateNode({ kind } as ASTNode)),
155+
).to.equal(true);
156+
});
144157
});

src/language/__tests__/printer-test.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { dedent, dedentString } from '../../__testUtils__/dedent';
55
import { kitchenSinkQuery } from '../../__testUtils__/kitchenSinkQuery';
66

77
import { Kind } from '../kinds';
8-
import { parse } from '../parser';
8+
import { parse, parseSchemaCoordinate } from '../parser';
99
import { print } from '../printer';
1010

1111
describe('Printer: Query document', () => {
@@ -232,4 +232,33 @@ describe('Printer: Query document', () => {
232232
`),
233233
);
234234
});
235+
236+
it('prints schema coordinates', () => {
237+
expect(print(parseSchemaCoordinate('Name'))).to.equal('Name');
238+
expect(print(parseSchemaCoordinate('Name.field'))).to.equal('Name.field');
239+
expect(print(parseSchemaCoordinate('Name.field(arg:)'))).to.equal(
240+
'Name.field(arg:)',
241+
);
242+
expect(print(parseSchemaCoordinate('@name'))).to.equal('@name');
243+
expect(print(parseSchemaCoordinate('@name(arg:)'))).to.equal('@name(arg:)');
244+
expect(print(parseSchemaCoordinate('__Type'))).to.equal('__Type');
245+
expect(print(parseSchemaCoordinate('Type.__metafield'))).to.equal(
246+
'Type.__metafield',
247+
);
248+
expect(print(parseSchemaCoordinate('Type.__metafield(arg:)'))).to.equal(
249+
'Type.__metafield(arg:)',
250+
);
251+
});
252+
253+
it('throws syntax error for ignored tokens in schema coordinates', () => {
254+
expect(() => print(parseSchemaCoordinate('# foo\nName'))).to.throw(
255+
'Syntax Error: Invalid character: "#"',
256+
);
257+
expect(() => print(parseSchemaCoordinate('\nName'))).to.throw(
258+
'Syntax Error: Invalid character: U+000A.',
259+
);
260+
expect(() => print(parseSchemaCoordinate('Name .field'))).to.throw(
261+
'Syntax Error: Invalid character: " "',
262+
);
263+
});
235264
});
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { expect } from 'chai';
2+
import { describe, it } from 'mocha';
3+
4+
import { expectToThrowJSON } from '../../__testUtils__/expectJSON';
5+
6+
import { SchemaCoordinateLexer } from '../schemaCoordinateLexer';
7+
import { Source } from '../source';
8+
import { TokenKind } from '../tokenKind';
9+
10+
function lexSecond(str: string) {
11+
const lexer = new SchemaCoordinateLexer(new Source(str));
12+
lexer.advance();
13+
return lexer.advance();
14+
}
15+
16+
function expectSyntaxError(text: string) {
17+
return expectToThrowJSON(() => lexSecond(text));
18+
}
19+
20+
describe('SchemaCoordinateLexer', () => {
21+
it('can be stringified', () => {
22+
const lexer = new SchemaCoordinateLexer(new Source('Name.field'));
23+
expect(Object.prototype.toString.call(lexer)).to.equal(
24+
'[object SchemaCoordinateLexer]',
25+
);
26+
});
27+
28+
it('tracks a schema coordinate', () => {
29+
const lexer = new SchemaCoordinateLexer(new Source('Name.field'));
30+
expect(lexer.advance()).to.contain({
31+
kind: TokenKind.NAME,
32+
start: 0,
33+
end: 4,
34+
value: 'Name',
35+
});
36+
});
37+
38+
it('forbids ignored tokens', () => {
39+
const lexer = new SchemaCoordinateLexer(new Source('\nName.field'));
40+
expectToThrowJSON(() => lexer.advance()).to.deep.equal({
41+
message: 'Syntax Error: Invalid character: U+000A.',
42+
locations: [{ line: 1, column: 1 }],
43+
});
44+
});
45+
46+
it('lex reports a useful syntax errors', () => {
47+
expectSyntaxError('Foo .bar').to.deep.equal({
48+
message: 'Syntax Error: Invalid character: " ".',
49+
locations: [{ line: 1, column: 4 }],
50+
});
51+
});
52+
});

0 commit comments

Comments
 (0)