Skip to content

Commit 9985342

Browse files
captbaritoneclaude
andcommitted
Fix crash on invalid directive locations in playground
The playground crashed with an unhandled GraphQLError when using invalid directive locations like `DIRECTIVE_DEFINITION`. This happened because `instanceof GraphQLError` failed in the bundled environment due to duplicate module instances. Fix by replacing `parser.parseDirectiveLocation()` with manual name parsing and validation against the DirectiveLocation enum. Also use `err.name === "GraphQLError"` instead of `instanceof` for robustness in bundled environments. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent cdef261 commit 9985342

5 files changed

Lines changed: 61 additions & 4 deletions

src/Errors.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
INFO_TAG,
1515
DIRECTIVE_TAG,
1616
} from "./Extractor.js";
17+
import { DirectiveLocation } from "graphql";
1718

1819
export const ISSUE_URL = "https://github.com/captbaritone/grats/issues";
1920

@@ -654,6 +655,10 @@ export function directiveTagNoComment() {
654655
return "Expected `@gqlDirective` tag to specify at least one location.";
655656
}
656657

658+
export function invalidDirectiveLocation(name: string) {
659+
return `"${name}" is not a valid directive location. Valid locations are: ${Object.values(DirectiveLocation).join(", ")}.`;
660+
}
661+
657662
export function directiveFunctionNotNamed() {
658663
return "Expected `@gqlDirective` function to be named.";
659664
}

src/Extractor.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
DefinitionNode,
1818
version as graphqlJSVersion,
1919
TokenKind,
20-
GraphQLError,
20+
DirectiveLocation,
2121
} from "graphql";
2222
import { gte as semverGte } from "semver";
2323
import {
@@ -176,6 +176,9 @@ export function extract(
176176
return extractor.extract(sourceFile);
177177
}
178178

179+
/** Sentinel error thrown when an error has already been reported with precise location. */
180+
class AlreadyReportedError extends Error {}
181+
179182
class Extractor {
180183
// Snapshot data. See comments on fields on ExtractionSnapshot for details.
181184
definitions: DefinitionNode[] = [];
@@ -536,7 +539,15 @@ class Extractor {
536539
}
537540

538541
const locations = parser
539-
.delimitedMany(TokenKind.PIPE, () => parser.parseDirectiveLocation())
542+
.delimitedMany(TokenKind.PIPE, () => {
543+
const nameNode = parser.parseName();
544+
const validLocations = Object.values(DirectiveLocation) as string[];
545+
if (!validLocations.includes(nameNode.value)) {
546+
this.report(tag, E.invalidDirectiveLocation(nameNode.value));
547+
throw new AlreadyReportedError();
548+
}
549+
return nameNode;
550+
})
540551
.map((location) => ({ ...location, loc: loc(tag) }));
541552
return { name, repeatable, locations };
542553
});
@@ -909,7 +920,13 @@ class Extractor {
909920
parser.expectToken(TokenKind.EOF);
910921
return result;
911922
} catch (err) {
912-
if (err instanceof GraphQLError) {
923+
if (err instanceof AlreadyReportedError) {
924+
// Error was already reported with precise location info
925+
} else if (err instanceof Error && err.name === "GraphQLError") {
926+
// Note: We use a name check instead of `instanceof GraphQLError`
927+
// because in bundled environments (e.g. the playground), the
928+
// GraphQLError class from the parser may be a different instance
929+
// than the one we imported, causing `instanceof` to fail.
913930
this.report(node, err.message);
914931
} else {
915932
throw err;

src/tests/fixtures/directives/defineCustomDirectiveLocationInvalid.invalid.ts.expected.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export function customDirective() {}
1515
### Error Report
1616

1717
```text
18-
src/tests/fixtures/directives/defineCustomDirectiveLocationInvalid.invalid.ts:3:4 - error: Syntax Error: Unexpected Name "WHOOPS".
18+
src/tests/fixtures/directives/defineCustomDirectiveLocationInvalid.invalid.ts:3:4 - error: "WHOOPS" is not a valid directive location. Valid locations are: QUERY, MUTATION, SUBSCRIPTION, FIELD, FRAGMENT_DEFINITION, FRAGMENT_SPREAD, INLINE_FRAGMENT, VARIABLE_DEFINITION, SCHEMA, SCALAR, OBJECT, FIELD_DEFINITION, ARGUMENT_DEFINITION, INTERFACE, UNION, ENUM, ENUM_VALUE, INPUT_OBJECT, INPUT_FIELD_DEFINITION.
1919
2020
3 * @gqlDirective on WHOOPS
2121
~~~~~~~~~~~~~~~~~~~~~~~
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { Int } from "../../../Types";
2+
/**
3+
* @gqlDirective on DIRECTIVE_DEFINITION
4+
* @gqlAnnotate
5+
*/
6+
export function myDirective(args: { credits: Int }) {
7+
// ...
8+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# directives/directiveOnDirectiveDefinitionLocation.invalid.ts
2+
3+
## Input
4+
5+
```ts title="directives/directiveOnDirectiveDefinitionLocation.invalid.ts"
6+
import { Int } from "../../../Types";
7+
/**
8+
* @gqlDirective on DIRECTIVE_DEFINITION
9+
* @gqlAnnotate
10+
*/
11+
export function myDirective(args: { credits: Int }) {
12+
// ...
13+
}
14+
```
15+
16+
## Output
17+
18+
### Error Report
19+
20+
```text
21+
src/tests/fixtures/directives/directiveOnDirectiveDefinitionLocation.invalid.ts:3:4 - error: "DIRECTIVE_DEFINITION" is not a valid directive location. Valid locations are: QUERY, MUTATION, SUBSCRIPTION, FIELD, FRAGMENT_DEFINITION, FRAGMENT_SPREAD, INLINE_FRAGMENT, VARIABLE_DEFINITION, SCHEMA, SCALAR, OBJECT, FIELD_DEFINITION, ARGUMENT_DEFINITION, INTERFACE, UNION, ENUM, ENUM_VALUE, INPUT_OBJECT, INPUT_FIELD_DEFINITION.
22+
23+
3 * @gqlDirective on DIRECTIVE_DEFINITION
24+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
25+
4 * @gqlAnnotate
26+
~~~
27+
```

0 commit comments

Comments
 (0)