Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ export {
parseValue,
parseConstValue,
parseType,
parseSchemaCoordinate,
// Print
print,
// Visit
Expand All @@ -255,6 +256,7 @@ export {
isTypeDefinitionNode,
isTypeSystemExtensionNode,
isTypeExtensionNode,
isSchemaCoordinateNode,
} from './language/index.js';

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

// Execute GraphQL queries.
Expand Down Expand Up @@ -496,6 +499,8 @@ export {
findBreakingChanges,
findDangerousChanges,
findSchemaChanges,
resolveSchemaCoordinate,
resolveASTSchemaCoordinate,
} from './utilities/index.js';

export type {
Expand Down Expand Up @@ -526,4 +531,5 @@ export type {
SafeChange,
DangerousChange,
TypedQueryDocumentNode,
ResolvedSchemaElement,
} from './utilities/index.js';
11 changes: 9 additions & 2 deletions src/language/__tests__/lexer-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,8 @@ describe('Lexer', () => {
});

it('reports unexpected characters', () => {
expectSyntaxError('.').to.deep.equal({
message: 'Syntax Error: Unexpected character: ".".',
expectSyntaxError('^').to.deep.equal({
message: 'Syntax Error: Unexpected character: "^".',
locations: [{ line: 1, column: 1 }],
});
});
Expand Down Expand Up @@ -965,6 +965,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
122 changes: 121 additions & 1 deletion src/language/__tests__/parser-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,13 @@ import { kitchenSinkQuery } from '../../__testUtils__/kitchenSinkQuery.js';
import { inspect } from '../../jsutils/inspect.js';

import { Kind } from '../kinds.js';
import { parse, parseConstValue, parseType, parseValue } from '../parser.js';
import {
parse,
parseConstValue,
parseSchemaCoordinate,
parseType,
parseValue,
} from '../parser.js';
import { Source } from '../source.js';
import { TokenKind } from '../tokenKind.js';

Expand Down Expand Up @@ -679,4 +685,118 @@ describe('Parser', () => {
});
});
});

describe('parseSchemaCoordinate', () => {
it('parses Name', () => {
const result = parseSchemaCoordinate('MyType');
expectJSON(result).toDeepEqual({
kind: Kind.TYPE_COORDINATE,
loc: { start: 0, end: 6 },
name: {
kind: Kind.NAME,
loc: { start: 0, end: 6 },
value: 'MyType',
},
});
});

it('parses Name . Name', () => {
const result = parseSchemaCoordinate('MyType.field');
expectJSON(result).toDeepEqual({
kind: Kind.MEMBER_COORDINATE,
loc: { start: 0, end: 12 },
name: {
kind: Kind.NAME,
loc: { start: 0, end: 6 },
value: 'MyType',
},
memberName: {
kind: Kind.NAME,
loc: { start: 7, end: 12 },
value: 'field',
},
});
});

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

it('parses Name . Name ( Name : )', () => {
const result = parseSchemaCoordinate('MyType.field(arg:)');
expectJSON(result).toDeepEqual({
kind: Kind.ARGUMENT_COORDINATE,
loc: { start: 0, end: 18 },
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.include({
message: 'Syntax Error: Expected ")", found Name "value".',
locations: [{ line: 1, column: 19 }],
});
});

it('parses @ Name', () => {
const result = parseSchemaCoordinate('@myDirective');
expectJSON(result).toDeepEqual({
kind: Kind.DIRECTIVE_COORDINATE,
loc: { start: 0, end: 12 },
name: {
kind: Kind.NAME,
loc: { start: 1, end: 12 },
value: 'myDirective',
},
});
});

it('parses @ Name ( Name : )', () => {
const result = parseSchemaCoordinate('@myDirective(arg:)');
expectJSON(result).toDeepEqual({
kind: Kind.DIRECTIVE_ARGUMENT_COORDINATE,
loc: { start: 0, end: 18 },
name: {
kind: Kind.NAME,
loc: { start: 1, end: 12 },
value: 'myDirective',
},
argumentName: {
kind: Kind.NAME,
loc: { start: 13, end: 16 },
value: 'arg',
},
});
});

it('rejects @ Name . Name', () => {
expect(() => parseSchemaCoordinate('@myDirective.field'))
.to.throw()
.to.deep.include({
message: 'Syntax Error: Expected <EOF>, found ".".',
locations: [{ line: 1, column: 13 }],
});
});
});
});
11 changes: 11 additions & 0 deletions src/language/__tests__/predicates-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
isConstValueNode,
isDefinitionNode,
isExecutableDefinitionNode,
isSchemaCoordinateNode,
isSelectionNode,
isTypeDefinitionNode,
isTypeExtensionNode,
Expand Down Expand Up @@ -141,4 +142,14 @@ describe('AST node predicates', () => {
'UnionTypeExtension',
]);
});

it('isSchemaCoordinateNode', () => {
expect(filterNodes(isSchemaCoordinateNode)).to.deep.equal([
'ArgumentCoordinate',
'DirectiveArgumentCoordinate',
'DirectiveCoordinate',
'MemberCoordinate',
'TypeCoordinate',
]);
});
});
16 changes: 15 additions & 1 deletion src/language/__tests__/printer-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { dedent, dedentString } from '../../__testUtils__/dedent.js';
import { kitchenSinkQuery } from '../../__testUtils__/kitchenSinkQuery.js';

import { Kind } from '../kinds.js';
import { parse } from '../parser.js';
import { parse, parseSchemaCoordinate } from '../parser.js';
import { print } from '../printer.js';

describe('Printer: Query document', () => {
Expand Down Expand Up @@ -299,4 +299,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:)',
);
});
});
57 changes: 56 additions & 1 deletion src/language/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,12 @@ export type ASTNode =
| InterfaceTypeExtensionNode
| UnionTypeExtensionNode
| EnumTypeExtensionNode
| InputObjectTypeExtensionNode;
| InputObjectTypeExtensionNode
| TypeCoordinateNode
| MemberCoordinateNode
| ArgumentCoordinateNode
| DirectiveCoordinateNode
| DirectiveArgumentCoordinateNode;

/**
* Utility type listing all nodes indexed by their kind.
Expand Down Expand Up @@ -287,6 +292,13 @@ export const QueryDocumentKeys: {
UnionTypeExtension: ['name', 'directives', 'types'],
EnumTypeExtension: ['name', 'directives', 'values'],
InputObjectTypeExtension: ['name', 'directives', 'fields'],

// Schema Coordinates
TypeCoordinate: ['name'],
MemberCoordinate: ['name', 'memberName'],
ArgumentCoordinate: ['name', 'fieldName', 'argumentName'],
DirectiveCoordinate: ['name'],
DirectiveArgumentCoordinate: ['name', 'argumentName'],
};

const kindValues = new Set<string>(Object.keys(QueryDocumentKeys));
Expand Down Expand Up @@ -762,3 +774,46 @@ export interface InputObjectTypeExtensionNode {
readonly directives?: ReadonlyArray<ConstDirectiveNode> | undefined;
readonly fields?: ReadonlyArray<InputValueDefinitionNode> | undefined;
}

/** Schema Coordinates */

export type SchemaCoordinateNode =
| TypeCoordinateNode
| MemberCoordinateNode
| ArgumentCoordinateNode
| DirectiveCoordinateNode
| DirectiveArgumentCoordinateNode;

export interface TypeCoordinateNode {
readonly kind: typeof Kind.TYPE_COORDINATE;
readonly loc?: Location;
readonly name: NameNode;
}

export interface MemberCoordinateNode {
readonly kind: typeof Kind.MEMBER_COORDINATE;
readonly loc?: Location;
readonly name: NameNode;
readonly memberName: NameNode;
}

export interface ArgumentCoordinateNode {
readonly kind: typeof Kind.ARGUMENT_COORDINATE;
readonly loc?: Location;
readonly name: NameNode;
readonly fieldName: NameNode;
readonly argumentName: NameNode;
}

export interface DirectiveCoordinateNode {
readonly kind: typeof Kind.DIRECTIVE_COORDINATE;
readonly loc?: Location;
readonly name: NameNode;
}

export interface DirectiveArgumentCoordinateNode {
readonly kind: typeof Kind.DIRECTIVE_ARGUMENT_COORDINATE;
readonly loc?: Location;
readonly name: NameNode;
readonly argumentName: NameNode;
}
10 changes: 9 additions & 1 deletion src/language/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ export { TokenKind } from './tokenKind.js';

export { Lexer } from './lexer.js';

export { parse, parseValue, parseConstValue, parseType } from './parser.js';
export {
parse,
parseValue,
parseConstValue,
parseType,
parseSchemaCoordinate,
} from './parser.js';
export type { ParseOptions } from './parser.js';

export { print } from './printer.js';
Expand Down Expand Up @@ -90,6 +96,7 @@ export type {
UnionTypeExtensionNode,
EnumTypeExtensionNode,
InputObjectTypeExtensionNode,
SchemaCoordinateNode,
} from './ast.js';

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

export { DirectiveLocation } from './directiveLocation.js';
17 changes: 17 additions & 0 deletions src/language/kinds_.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,20 @@ export const ENUM_TYPE_EXTENSION = 'EnumTypeExtension';
export type ENUM_TYPE_EXTENSION = typeof ENUM_TYPE_EXTENSION;
export const INPUT_OBJECT_TYPE_EXTENSION = 'InputObjectTypeExtension';
export type INPUT_OBJECT_TYPE_EXTENSION = typeof INPUT_OBJECT_TYPE_EXTENSION;

/** Schema Coordinates */
export const TYPE_COORDINATE = 'TypeCoordinate';
export type TYPE_COORDINATE = typeof TYPE_COORDINATE;

export const MEMBER_COORDINATE = 'MemberCoordinate';
export type MEMBER_COORDINATE = typeof MEMBER_COORDINATE;

export const ARGUMENT_COORDINATE = 'ArgumentCoordinate';
export type ARGUMENT_COORDINATE = typeof ARGUMENT_COORDINATE;

export const DIRECTIVE_COORDINATE = 'DirectiveCoordinate';
export type DIRECTIVE_COORDINATE = typeof DIRECTIVE_COORDINATE;

export const DIRECTIVE_ARGUMENT_COORDINATE = 'DirectiveArgumentCoordinate';
export type DIRECTIVE_ARGUMENT_COORDINATE =
typeof DIRECTIVE_ARGUMENT_COORDINATE;
Loading
Loading