Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ export type {
UnionTypeExtensionNode,
EnumTypeExtensionNode,
InputObjectTypeExtensionNode,
DirectiveExtensionNode,
// Schema Coordinates
SchemaCoordinateNode,
TypeCoordinateNode,
Expand Down
2 changes: 2 additions & 0 deletions src/language/__tests__/predicates-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ describe('AST node predicates', () => {
'InputObjectTypeDefinition',
'DirectiveDefinition',
'SchemaExtension',
'DirectiveExtension',
'ScalarTypeExtension',
'ObjectTypeExtension',
'InterfaceTypeExtension',
Expand Down Expand Up @@ -123,6 +124,7 @@ describe('AST node predicates', () => {
it('isTypeSystemExtensionNode', () => {
expect(filterNodes(isTypeSystemExtensionNode)).to.deep.equal([
'SchemaExtension',
'DirectiveExtension',
'ScalarTypeExtension',
'ObjectTypeExtension',
'InterfaceTypeExtension',
Expand Down
15 changes: 15 additions & 0 deletions src/language/__tests__/printer-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,21 @@ describe('Printer: Query document', () => {
`);
});

it('Experimental: prints directives on directives', () => {
const queryASTWithVariableDirective = parse(
`
directive @foo @bar on FIELD_DEFINITION
extend directive @foo @baz
`,
{ experimentalDirectivesOnDirectiveDefinitions: true },
);
expect(print(queryASTWithVariableDirective)).to.equal(dedent`
directive @foo @bar on FIELD_DEFINITION

extend directive @foo @baz
`);
});

it('Legacy: correctly prints fragment defined variables', () => {
const fragmentWithVariable = parse(
`
Expand Down
2 changes: 2 additions & 0 deletions src/language/__tests__/schema-parser-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1029,6 +1029,7 @@ input Hello {
{
kind: 'DirectiveDefinition',
description: undefined,
directives: [],
name: {
kind: 'Name',
value: 'foo',
Expand Down Expand Up @@ -1065,6 +1066,7 @@ input Hello {
{
kind: 'DirectiveDefinition',
description: undefined,
directives: [],
name: {
kind: 'Name',
value: 'foo',
Expand Down
24 changes: 22 additions & 2 deletions src/language/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ export type ASTNode =
| UnionTypeExtensionNode
| EnumTypeExtensionNode
| InputObjectTypeExtensionNode
| DirectiveExtensionNode
| TypeCoordinateNode
| MemberCoordinateNode
| ArgumentCoordinateNode
Expand Down Expand Up @@ -280,10 +281,18 @@ export const QueryDocumentKeys: {
EnumValueDefinition: ['description', 'name', 'directives'],
InputObjectTypeDefinition: ['description', 'name', 'directives', 'fields'],

DirectiveDefinition: ['description', 'name', 'arguments', 'locations'],
DirectiveDefinition: [
'description',
'name',
'arguments',
'directives',
'locations',
],

SchemaExtension: ['directives', 'operationTypes'],

DirectiveExtension: ['name', 'directives'],

ScalarTypeExtension: ['name', 'directives'],
ObjectTypeExtension: ['name', 'interfaces', 'directives', 'fields'],
InterfaceTypeExtension: ['name', 'interfaces', 'directives', 'fields'],
Expand Down Expand Up @@ -686,13 +695,17 @@ export interface DirectiveDefinitionNode {
readonly description?: StringValueNode;
readonly name: NameNode;
readonly arguments?: ReadonlyArray<InputValueDefinitionNode>;
readonly directives?: ReadonlyArray<ConstDirectiveNode>;
readonly repeatable: boolean;
readonly locations: ReadonlyArray<NameNode>;
}

/** Type System Extensions */

export type TypeSystemExtensionNode = SchemaExtensionNode | TypeExtensionNode;
export type TypeSystemExtensionNode =
| SchemaExtensionNode
| TypeExtensionNode
| DirectiveExtensionNode;

export interface SchemaExtensionNode {
readonly kind: Kind.SCHEMA_EXTENSION;
Expand Down Expand Up @@ -760,6 +773,13 @@ export interface InputObjectTypeExtensionNode {
readonly fields?: ReadonlyArray<InputValueDefinitionNode>;
}

export interface DirectiveExtensionNode {
readonly kind: Kind.DIRECTIVE_EXTENSION;
readonly loc?: Location;
readonly name: NameNode;
readonly directives?: ReadonlyArray<ConstDirectiveNode>;
}

/** Schema Coordinates */

export type SchemaCoordinateNode =
Expand Down
1 change: 1 addition & 0 deletions src/language/directiveLocation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ enum DirectiveLocation {
ENUM_VALUE = 'ENUM_VALUE',
INPUT_OBJECT = 'INPUT_OBJECT',
INPUT_FIELD_DEFINITION = 'INPUT_FIELD_DEFINITION',
DIRECTIVE_DEFINITION = 'DIRECTIVE_DEFINITION',
}
export { DirectiveLocation };

Expand Down
1 change: 1 addition & 0 deletions src/language/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export type {
UnionTypeExtensionNode,
EnumTypeExtensionNode,
InputObjectTypeExtensionNode,
DirectiveExtensionNode,
// Schema Coordinates
SchemaCoordinateNode,
TypeCoordinateNode,
Expand Down
1 change: 1 addition & 0 deletions src/language/kinds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ enum Kind {

/** Type System Extensions */
SCHEMA_EXTENSION = 'SchemaExtension',
DIRECTIVE_EXTENSION = 'DirectiveExtension',

/** Type Extensions */
SCALAR_TYPE_EXTENSION = 'ScalarTypeExtension',
Expand Down
44 changes: 44 additions & 0 deletions src/language/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {
DirectiveArgumentCoordinateNode,
DirectiveCoordinateNode,
DirectiveDefinitionNode,
DirectiveExtensionNode,
DirectiveNode,
DocumentNode,
EnumTypeDefinitionNode,
Expand Down Expand Up @@ -112,6 +113,18 @@ export interface ParseOptions {
*/
allowLegacyFragmentVariables?: boolean;

/**
* EXPERIMENTAL:
*
* If enabled, the parser will parse directives on directive definitions.
* This syntax is not part of the GraphQL specification and may change.
*
* ```graphql
* directive @foo @bar on FIELD
* ```
*/
experimentalDirectivesOnDirectiveDefinitions?: boolean;

/**
* You may override the Lexer class used to lex the source; this is used by
* schema coordinates to introduce a lexer with a restricted syntax.
Expand Down Expand Up @@ -1184,6 +1197,7 @@ export class Parser {
* - UnionTypeExtension
* - EnumTypeExtension
* - InputObjectTypeDefinition
* - DirectiveDefinitionExtension
*/
parseTypeSystemExtension(): TypeSystemExtensionNode {
const keywordToken = this._lexer.lookahead();
Expand All @@ -1204,6 +1218,13 @@ export class Parser {
return this.parseEnumTypeExtension();
case 'input':
return this.parseInputObjectTypeExtension();
case 'directive':
if (
this._options.experimentalDirectivesOnDirectiveDefinitions === true
) {
return this.parseDirectiveDefinitionExtension();
}
break;
}
}

Expand Down Expand Up @@ -1386,6 +1407,23 @@ export class Parser {
});
}

parseDirectiveDefinitionExtension(): DirectiveExtensionNode {
const start = this._lexer.token;
this.expectKeyword('extend');
this.expectKeyword('directive');
this.expectToken(TokenKind.AT);
const name = this.parseName();
const directives = this.parseConstDirectives();
if (directives.length === 0) {
throw this.unexpected();
}
return this.node<DirectiveExtensionNode>(start, {
kind: Kind.DIRECTIVE_EXTENSION,
name,
directives,
});
}

/**
* ```
* DirectiveDefinition :
Expand All @@ -1399,6 +1437,10 @@ export class Parser {
this.expectToken(TokenKind.AT);
const name = this.parseName();
const args = this.parseArgumentDefs();
const directives =
this._options.experimentalDirectivesOnDirectiveDefinitions === true
? this.parseConstDirectives()
: [];
const repeatable = this.expectOptionalKeyword('repeatable');
this.expectKeyword('on');
const locations = this.parseDirectiveLocations();
Expand All @@ -1407,6 +1449,7 @@ export class Parser {
description,
name,
arguments: args,
directives,
repeatable,
locations,
});
Expand Down Expand Up @@ -1447,6 +1490,7 @@ export class Parser {
* `ENUM_VALUE`
* `INPUT_OBJECT`
* `INPUT_FIELD_DEFINITION`
* `DIRECTIVE_DEFINITION`
*/
parseDirectiveLocation(): NameNode {
const start = this._lexer.token;
Expand Down
6 changes: 5 additions & 1 deletion src/language/predicates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,11 @@ export function isTypeDefinitionNode(
export function isTypeSystemExtensionNode(
node: ASTNode,
): node is TypeSystemExtensionNode {
return node.kind === Kind.SCHEMA_EXTENSION || isTypeExtensionNode(node);
return (
node.kind === Kind.SCHEMA_EXTENSION ||
node.kind === Kind.DIRECTIVE_EXTENSION ||
isTypeExtensionNode(node)
);
}

export function isTypeExtensionNode(node: ASTNode): node is TypeExtensionNode {
Expand Down
15 changes: 14 additions & 1 deletion src/language/printer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,13 +235,21 @@ const printDocASTReducer: ASTReducer<string> = {
},

DirectiveDefinition: {
leave: ({ description, name, arguments: args, repeatable, locations }) =>
leave: ({
description,
name,
arguments: args,
directives,
repeatable,
locations,
}) =>
wrap('', description, '\n') +
'directive @' +
name +
(hasMultilineItems(args)
? wrap('(\n', indent(join(args, '\n')), '\n)')
: wrap('(', join(args, ', '), ')')) +
wrap(' ', join(directives, ' ')) +
(repeatable ? ' repeatable' : '') +
' on ' +
join(locations, ' | '),
Expand Down Expand Up @@ -311,6 +319,11 @@ const printDocASTReducer: ASTReducer<string> = {
join(['extend input', name, join(directives, ' '), block(fields)], ' '),
},

DirectiveExtension: {
leave: ({ name, directives }) =>
join(['extend directive @' + name, join(directives, ' ')], ' '),
},

// Schema Coordinates

TypeCoordinate: { leave: ({ name }) => name },
Expand Down
16 changes: 16 additions & 0 deletions src/type/__tests__/directive-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,22 @@ describe('Type System: Directive', () => {
});
});

it('defines a deprecated directive', () => {
const directive = new GraphQLDirective({
name: 'Foo',
locations: [DirectiveLocation.QUERY],
deprecationReason: 'Some reason',
});

expect(directive).to.deep.include({
name: 'Foo',
args: [],
isRepeatable: false,
locations: ['QUERY'],
deprecationReason: 'Some reason',
});
});

it('can be stringified, JSON.stringified and Object.toStringified', () => {
const directive = new GraphQLDirective({
name: 'Foo',
Expand Down
Loading
Loading