Skip to content

Commit

Permalink
Schema Coordinates
Browse files Browse the repository at this point in the history
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`
  • Loading branch information
leebyron committed Apr 16, 2021
1 parent b9fd53b commit c7b87e2
Show file tree
Hide file tree
Showing 30 changed files with 679 additions and 17 deletions.
6 changes: 6 additions & 0 deletions src/index.d.ts
Expand Up @@ -181,6 +181,7 @@ export {
GraphQLScalarSerializer,
GraphQLScalarValueParser,
GraphQLScalarLiteralParser,
GraphQLSchemaElement,
} from './type/index';

// Parse and operate on GraphQL language source files.
Expand All @@ -199,6 +200,7 @@ export {
parse,
parseValue,
parseType,
parseSchemaCoordinate,
// Print
print,
// Visit
Expand All @@ -218,6 +220,7 @@ export {
isTypeDefinitionNode,
isTypeSystemExtensionNode,
isTypeExtensionNode,
isSchemaCoordinateNode,
} from './language/index';

export {
Expand Down Expand Up @@ -286,6 +289,7 @@ export {
UnionTypeExtensionNode,
EnumTypeExtensionNode,
InputObjectTypeExtensionNode,
SchemaCoordinateNode,
} from './language/index';

// Execute GraphQL queries.
Expand Down Expand Up @@ -427,6 +431,8 @@ export {
DangerousChangeType,
findBreakingChanges,
findDangerousChanges,
resolveSchemaCoordinate,
resolveASTSchemaCoordinate,
} from './utilities/index';

export {
Expand Down
6 changes: 6 additions & 0 deletions src/index.js
Expand Up @@ -168,6 +168,7 @@ export type {
GraphQLScalarSerializer,
GraphQLScalarValueParser,
GraphQLScalarLiteralParser,
GraphQLSchemaElement,
} from './type/index';

// Parse and operate on GraphQL language source files.
Expand All @@ -186,6 +187,7 @@ export {
parse,
parseValue,
parseType,
parseSchemaCoordinate,
// Print
print,
// Visit
Expand All @@ -205,6 +207,7 @@ export {
isTypeDefinitionNode,
isTypeSystemExtensionNode,
isTypeExtensionNode,
isSchemaCoordinateNode,
} from './language/index';

export type {
Expand Down Expand Up @@ -273,6 +276,7 @@ export type {
UnionTypeExtensionNode,
EnumTypeExtensionNode,
InputObjectTypeExtensionNode,
SchemaCoordinateNode,
} from './language/index';

// Execute GraphQL queries.
Expand Down Expand Up @@ -416,6 +420,8 @@ export {
DangerousChangeType,
findBreakingChanges,
findDangerousChanges,
resolveSchemaCoordinate,
resolveASTSchemaCoordinate,
} from './utilities/index';

export type {
Expand Down
11 changes: 9 additions & 2 deletions src/language/__tests__/lexer-test.js
Expand Up @@ -649,7 +649,7 @@ describe('Lexer', () => {
});

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

Expand Down Expand Up @@ -753,6 +753,13 @@ describe('Lexer', () => {
value: undefined,
});

expect(lexOne('.')).to.contain({
kind: TokenKind.DOT,
start: 0,
end: 1,
value: undefined,
});

expect(lexOne('...')).to.contain({
kind: TokenKind.SPREAD,
start: 0,
Expand Down Expand Up @@ -819,7 +826,7 @@ describe('Lexer', () => {

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

Expand Down
127 changes: 126 additions & 1 deletion src/language/__tests__/parser-test.js
Expand Up @@ -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, parseType, parseSchemaCoordinate } from '../parser';

import { toJSONDeep } from './toJSONDeep';

Expand Down Expand Up @@ -531,4 +531,129 @@ describe('Parser', () => {
});
});
});

describe('parseSchemaCoordinate', () => {
it('parses Name', () => {
const result = parseSchemaCoordinate('MyType');
expect(toJSONDeep(result)).to.deep.equal({
kind: Kind.SCHEMA_COORDINATE,
loc: { start: 0, end: 6 },
isDirective: false,
name: {
kind: Kind.NAME,
loc: { start: 0, end: 6 },
value: 'MyType',
},
fieldName: undefined,
argumentName: undefined,
});
});

it('parses Name . Name', () => {
const result = parseSchemaCoordinate('MyType.field');
expect(toJSONDeep(result)).to.deep.equal({
kind: Kind.SCHEMA_COORDINATE,
loc: { start: 0, end: 12 },
isDirective: false,
name: {
kind: Kind.NAME,
loc: { start: 0, end: 6 },
value: 'MyType',
},
fieldName: {
kind: Kind.NAME,
loc: { start: 7, end: 12 },
value: 'field',
},
argumentName: undefined,
});
});

it('rejects Name . Name . Name', () => {
expect(() => parseSchemaCoordinate('MyType.field.deep'))
.to.throw()
.to.deep.equal({
message: 'Syntax Error: Expected <EOF>, found ".".',
locations: [{ line: 1, column: 13 }],
});
});

it('parses Name . Name ( Name : )', () => {
const result = parseSchemaCoordinate('MyType.field(arg:)');
expect(toJSONDeep(result)).to.deep.equal({
kind: Kind.SCHEMA_COORDINATE,
loc: { start: 0, end: 18 },
isDirective: false,
name: {
kind: Kind.NAME,
loc: { start: 0, end: 6 },
value: 'MyType',
},
fieldName: {
kind: Kind.NAME,
loc: { start: 7, end: 12 },
value: 'field',
},
argumentName: {
kind: Kind.NAME,
loc: { start: 13, end: 16 },
value: 'arg',
},
});
});

it('rejects Name . Name ( Name : Name )', () => {
expect(() => parseSchemaCoordinate('MyType.field(arg: value)'))
.to.throw()
.to.deep.equal({
message: 'Syntax Error: Expected ")", found Name "value".',
locations: [{ line: 1, column: 19 }],
});
});

it('parses @ Name', () => {
const result = parseSchemaCoordinate('@myDirective');
expect(toJSONDeep(result)).to.deep.equal({
kind: Kind.SCHEMA_COORDINATE,
loc: { start: 0, end: 12 },
isDirective: true,
name: {
kind: Kind.NAME,
loc: { start: 1, end: 12 },
value: 'myDirective',
},
fieldName: undefined,
argumentName: undefined,
});
});

it('parses @ Name ( Name : )', () => {
const result = parseSchemaCoordinate('@myDirective(arg:)');
expect(toJSONDeep(result)).to.deep.equal({
kind: Kind.SCHEMA_COORDINATE,
loc: { start: 0, end: 18 },
isDirective: true,
name: {
kind: Kind.NAME,
loc: { start: 1, end: 12 },
value: 'myDirective',
},
fieldName: undefined,
argumentName: {
kind: Kind.NAME,
loc: { start: 13, end: 16 },
value: 'arg',
},
});
});

it('rejects @ Name . Name', () => {
expect(() => parseSchemaCoordinate('@myDirective.field'))
.to.throw()
.to.deep.equal({
message: 'Syntax Error: Expected <EOF>, found ".".',
locations: [{ line: 1, column: 13 }],
});
});
});
});
7 changes: 7 additions & 0 deletions src/language/__tests__/predicates-test.js
Expand Up @@ -13,6 +13,7 @@ import {
isTypeDefinitionNode,
isTypeSystemExtensionNode,
isTypeExtensionNode,
isSchemaCoordinateNode,
} from '../predicates';

const allASTNodes: Array<ASTNode> = Object.values(Kind).map(
Expand Down Expand Up @@ -129,4 +130,10 @@ describe('AST node predicates', () => {
'InputObjectTypeExtension',
]);
});

it('isSchemaCoordinateNode', () => {
expect(filterNodes(isSchemaCoordinateNode)).to.deep.equal([
'SchemaCoordinate',
]);
});
});
16 changes: 15 additions & 1 deletion src/language/__tests__/printer-test.js
Expand Up @@ -3,8 +3,8 @@ import { describe, it } from 'mocha';

import { dedent, dedentString } from '../../__testUtils__/dedent';
import { kitchenSinkQuery } from '../../__testUtils__/kitchenSinkQuery';
import { parseSchemaCoordinate, parse } from '../parser';

import { parse } from '../parser';
import { print } from '../printer';

describe('Printer: Query document', () => {
Expand Down Expand Up @@ -214,4 +214,18 @@ describe('Printer: Query document', () => {
`),
);
});

it('prints schema coordinates', () => {
expect(print(parseSchemaCoordinate(' Name '))).to.equal('Name');
expect(print(parseSchemaCoordinate(' Name . field '))).to.equal(
'Name.field',
);
expect(print(parseSchemaCoordinate(' Name . field ( arg: )'))).to.equal(
'Name.field(arg:)',
);
expect(print(parseSchemaCoordinate(' @ name '))).to.equal('@name');
expect(print(parseSchemaCoordinate(' @ name (arg:) '))).to.equal(
'@name(arg:)',
);
});
});
15 changes: 14 additions & 1 deletion src/language/ast.d.ts
Expand Up @@ -148,7 +148,8 @@ export type ASTNode =
| InterfaceTypeExtensionNode
| UnionTypeExtensionNode
| EnumTypeExtensionNode
| InputObjectTypeExtensionNode;
| InputObjectTypeExtensionNode
| SchemaCoordinateNode;

/**
* Utility type listing all nodes indexed by their kind.
Expand Down Expand Up @@ -197,6 +198,7 @@ export interface ASTKindToNode {
UnionTypeExtension: UnionTypeExtensionNode;
EnumTypeExtension: EnumTypeExtensionNode;
InputObjectTypeExtension: InputObjectTypeExtensionNode;
SchemaCoordinate: SchemaCoordinateNode;
}

// Name
Expand Down Expand Up @@ -599,3 +601,14 @@ export interface InputObjectTypeExtensionNode {
readonly directives?: ReadonlyArray<DirectiveNode>;
readonly fields?: ReadonlyArray<InputValueDefinitionNode>;
}

// Schema Coordinates

export interface SchemaCoordinateNode {
readonly kind: 'SchemaCoordinate';
readonly loc?: Location;
readonly isDirective: boolean;
readonly name: NameNode;
readonly fieldName?: NameNode;
readonly argumentName?: NameNode;
}
15 changes: 14 additions & 1 deletion src/language/ast.js
Expand Up @@ -174,7 +174,8 @@ export type ASTNode =
| InterfaceTypeExtensionNode
| UnionTypeExtensionNode
| EnumTypeExtensionNode
| InputObjectTypeExtensionNode;
| InputObjectTypeExtensionNode
| SchemaCoordinateNode;

/**
* Utility type listing all nodes indexed by their kind.
Expand Down Expand Up @@ -223,6 +224,7 @@ export type ASTKindToNode = {|
UnionTypeExtension: UnionTypeExtensionNode,
EnumTypeExtension: EnumTypeExtensionNode,
InputObjectTypeExtension: InputObjectTypeExtensionNode,
SchemaCoordinate: SchemaCoordinateNode,
|};

// Name
Expand Down Expand Up @@ -625,3 +627,14 @@ export type InputObjectTypeExtensionNode = {|
+directives?: $ReadOnlyArray<DirectiveNode>,
+fields?: $ReadOnlyArray<InputValueDefinitionNode>,
|};

// Schema Coordinates

export type SchemaCoordinateNode = {|
+kind: 'SchemaCoordinate',
+loc?: Location,
+isDirective: boolean,
+name: NameNode,
+fieldName?: NameNode,
+argumentName?: NameNode,
|};
10 changes: 9 additions & 1 deletion src/language/index.d.ts
Expand Up @@ -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,
parseType,
parseSchemaCoordinate,
ParseOptions,
} from './parser';
export { print } from './printer';
export {
visit,
Expand Down Expand Up @@ -76,6 +82,7 @@ export {
UnionTypeExtensionNode,
EnumTypeExtensionNode,
InputObjectTypeExtensionNode,
SchemaCoordinateNode,
} from './ast';

export {
Expand All @@ -88,6 +95,7 @@ export {
isTypeDefinitionNode,
isTypeSystemExtensionNode,
isTypeExtensionNode,
isSchemaCoordinateNode,
} from './predicates';

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

0 comments on commit c7b87e2

Please sign in to comment.