Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
531cbf4
Add support for directives on directive definitions
BoD Dec 19, 2025
e46ed2e
Introspection related changes
BoD Jan 6, 2026
d44ff03
Add ParseOption.experimentalDirectivesOnDirectiveDefinition
martinbonnin Mar 17, 2026
ca49ec5
npm run prettier
martinbonnin Mar 17, 2026
ac59fc1
fix test coverage
martinbonnin Mar 17, 2026
46a73d4
fix imports
martinbonnin Mar 17, 2026
3d0a91c
try to fix coverage
martinbonnin Mar 17, 2026
8db915f
try to make test coverage pass
martinbonnin Mar 17, 2026
64a5698
make prettier happy
martinbonnin Mar 17, 2026
e91532e
Introspection: change `includeDeprecated: Boolean` to `Boolean!`
BoD Apr 3, 2026
c2dbb78
Fix indent
BoD Apr 3, 2026
a15d81b
Add an introspection test for multiple directives with arguments appl…
BoD Apr 3, 2026
e34401a
Update src/language/parser.ts
BoD Apr 7, 2026
9329e48
Update src/language/parser.ts
BoD Apr 7, 2026
30f7d58
Update src/utilities/__tests__/extendSchema-test.ts
BoD Apr 7, 2026
edcc324
Update src/utilities/extendSchema.ts
BoD Apr 7, 2026
788f090
Update src/utilities/extendSchema.ts
BoD Apr 7, 2026
e230a8c
Apply a few minor fixes suggested by Jerel
BoD Apr 7, 2026
1eb2a68
Forward parse options through buildSchema
yaacovCR Apr 24, 2026
a0349be
Preserve directive deprecations in extendSchema
yaacovCR Apr 24, 2026
7b9b581
Apply same-document directive extensions
yaacovCR Apr 24, 2026
bd1929c
Print deprecated directives in schemas
yaacovCR Apr 24, 2026
53b88d4
Add experimental directive deprecation to introspection queries
yaacovCR Apr 24, 2026
da43e82
Import directive deprecations from introspection
yaacovCR Apr 24, 2026
a475823
Refine directive introspection tests
yaacovCR Apr 24, 2026
e287ba1
Track directive definition directives across extensions
yaacovCR Apr 24, 2026
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
42 changes: 42 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,11 @@ export class Parser {
return this.parseEnumTypeExtension();
case 'input':
return this.parseInputObjectTypeExtension();
case 'directive':
if (this._options.experimentalDirectivesOnDirectiveDefinitions) {
return this.parseDirectiveDefinitionExtension();
}
break;
}
}

Expand Down Expand Up @@ -1386,6 +1405,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 +1435,10 @@ export class Parser {
this.expectToken(TokenKind.AT);
const name = this.parseName();
const args = this.parseArgumentDefs();
const directives = this._options
.experimentalDirectivesOnDirectiveDefinitions
? this.parseConstDirectives()
: [];
const repeatable = this.expectOptionalKeyword('repeatable');
this.expectKeyword('on');
const locations = this.parseDirectiveLocations();
Expand All @@ -1407,6 +1447,7 @@ export class Parser {
description,
name,
arguments: args,
directives,
repeatable,
locations,
});
Expand Down Expand Up @@ -1447,6 +1488,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