Skip to content

Commit 5690981

Browse files
fix(directives): allow multiline and leading spaces (#1669)
* fix: allow @directive definition multiline and leading spaces * test: better multiline and leading whitespaces tests * Fix assert directive --------- Co-authored-by: Michał Lytek <[email protected]>
1 parent 3ba84c1 commit 5690981

File tree

5 files changed

+124
-6
lines changed

5 files changed

+124
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
## Fixes
1212

1313
- properly override fields of `@ArgsType` classes in deeply nested inheritance chain (#1644)
14+
- allow for leading spaces and multiline directives definitions in `@Directive` decorator (#1423)
1415

1516
## v2.0.0-beta.6
1617

src/schema/definition-node.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ import { type DirectiveMetadata } from "@/metadata/definitions";
1818
import { type SetRequired } from "@/typings";
1919

2020
export function getDirectiveNode(directive: DirectiveMetadata): ConstDirectiveNode {
21-
const { nameOrDefinition, args } = directive;
21+
// Inline and trim start
22+
const nameOrDefinition = directive.nameOrDefinition.replaceAll("\n", " ").trimStart();
23+
const { args } = directive;
2224

2325
if (nameOrDefinition === "") {
2426
throw new InvalidDirectiveError(

tests/functional/directives.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,101 @@ describe("Directives", () => {
582582
});
583583
});
584584

585+
describe("multiline and leading white spaces", () => {
586+
let schema: GraphQLSchema;
587+
beforeAll(async () => {
588+
@Resolver()
589+
class SampleResolver {
590+
@Directive("\n@test")
591+
@Query()
592+
multiline(): boolean {
593+
return true;
594+
}
595+
596+
@Directive(" @test")
597+
@Query()
598+
leadingWhiteSpaces(): boolean {
599+
return true;
600+
}
601+
602+
@Directive("\n @test")
603+
@Query()
604+
multilineAndLeadingWhiteSpaces(): boolean {
605+
return true;
606+
}
607+
608+
@Directive(`
609+
@test(
610+
argNonNullDefault: "argNonNullDefault",
611+
argNullDefault: "argNullDefault",
612+
argNull: "argNull"
613+
)
614+
`)
615+
@Query()
616+
rawMultilineAndLeadingWhiteSpaces(): boolean {
617+
return true;
618+
}
619+
}
620+
621+
schema = await buildSchema({
622+
resolvers: [SampleResolver],
623+
directives: [testDirective],
624+
validate: false,
625+
});
626+
schema = testDirectiveTransformer(schema);
627+
});
628+
629+
it("should properly emit directive in AST", () => {
630+
const multilineInfo = schema.getRootType(OperationTypeNode.QUERY)!.getFields().multiline;
631+
const leadingWhiteSpacesInfo = schema
632+
.getRootType(OperationTypeNode.QUERY)!
633+
.getFields().leadingWhiteSpaces;
634+
const multilineAndLeadingWhiteSpacesInfo = schema
635+
.getRootType(OperationTypeNode.QUERY)!
636+
.getFields().multilineAndLeadingWhiteSpaces;
637+
const rawMultilineAndLeadingWhiteSpacesInfo = schema
638+
.getRootType(OperationTypeNode.QUERY)!
639+
.getFields().rawMultilineAndLeadingWhiteSpaces;
640+
641+
expect(() => {
642+
assertValidDirective(multilineInfo.astNode, "test");
643+
assertValidDirective(leadingWhiteSpacesInfo.astNode, "test");
644+
assertValidDirective(multilineAndLeadingWhiteSpacesInfo.astNode, "test");
645+
assertValidDirective(rawMultilineAndLeadingWhiteSpacesInfo.astNode, "test", {
646+
argNonNullDefault: `"argNonNullDefault"`,
647+
argNullDefault: `"argNullDefault"`,
648+
argNull: `"argNull"`,
649+
});
650+
}).not.toThrow();
651+
});
652+
653+
it("should properly apply directive mapper", async () => {
654+
const multilineInfo = schema.getRootType(OperationTypeNode.QUERY)!.getFields().multiline;
655+
const leadingWhiteSpacesInfo = schema
656+
.getRootType(OperationTypeNode.QUERY)!
657+
.getFields().leadingWhiteSpaces;
658+
const multilineAndLeadingWhiteSpacesInfo = schema
659+
.getRootType(OperationTypeNode.QUERY)!
660+
.getFields().multilineAndLeadingWhiteSpaces;
661+
const rawMultilineAndLeadingWhiteSpacesInfo = schema
662+
.getRootType(OperationTypeNode.QUERY)!
663+
.getFields().rawMultilineAndLeadingWhiteSpaces;
664+
665+
expect(multilineInfo.extensions).toMatchObject({
666+
TypeGraphQL: { isMappedByDirective: true },
667+
});
668+
expect(leadingWhiteSpacesInfo.extensions).toMatchObject({
669+
TypeGraphQL: { isMappedByDirective: true },
670+
});
671+
expect(multilineAndLeadingWhiteSpacesInfo.extensions).toMatchObject({
672+
TypeGraphQL: { isMappedByDirective: true },
673+
});
674+
expect(rawMultilineAndLeadingWhiteSpacesInfo.extensions).toMatchObject({
675+
TypeGraphQL: { isMappedByDirective: true },
676+
});
677+
});
678+
});
679+
585680
describe("errors", () => {
586681
beforeEach(async () => {
587682
getMetadataStorage().clear();

tests/helpers/directives/TestDirective.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ import {
99
type GraphQLInputObjectTypeConfig,
1010
GraphQLInterfaceType,
1111
type GraphQLInterfaceTypeConfig,
12+
GraphQLNonNull,
1213
GraphQLObjectType,
1314
type GraphQLObjectTypeConfig,
1415
type GraphQLSchema,
16+
GraphQLString,
1517
} from "graphql";
1618

1719
function mapConfig<
@@ -36,6 +38,19 @@ function mapConfig<
3638

3739
export const testDirective = new GraphQLDirective({
3840
name: "test",
41+
args: {
42+
argNonNullDefault: {
43+
type: new GraphQLNonNull(GraphQLString),
44+
defaultValue: "argNonNullDefault",
45+
},
46+
argNullDefault: {
47+
type: GraphQLString,
48+
defaultValue: "argNullDefault",
49+
},
50+
argNull: {
51+
type: GraphQLString,
52+
},
53+
},
3954
locations: [
4055
DirectiveLocation.OBJECT,
4156
DirectiveLocation.FIELD_DEFINITION,

tests/helpers/directives/assertValidDirective.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,17 @@ export function assertValidDirective(
3838
expect(directive.arguments).toBeFalsy();
3939
}
4040
} else {
41+
expect(directive.arguments).toHaveLength(Object.keys(args).length);
4142
expect(directive.arguments).toEqual(
42-
Object.keys(args).map(arg => ({
43-
kind: "Argument",
44-
name: { kind: "Name", value: arg },
45-
value: parseValue(args[arg]),
46-
})),
43+
expect.arrayContaining(
44+
Object.keys(args).map(arg =>
45+
expect.objectContaining({
46+
kind: "Argument",
47+
name: expect.objectContaining({ kind: "Name", value: arg }),
48+
value: expect.objectContaining(parseValue(args[arg], { noLocation: true })),
49+
}),
50+
),
51+
),
4752
);
4853
}
4954
}

0 commit comments

Comments
 (0)