Skip to content

Commit

Permalink
Tag context value
Browse files Browse the repository at this point in the history
  • Loading branch information
captbaritone committed Mar 26, 2024
1 parent ff8d27e commit f6daf11
Show file tree
Hide file tree
Showing 33 changed files with 187 additions and 35 deletions.
1 change: 1 addition & 0 deletions examples/production-app/ViewerContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,5 @@ export class VC {
}
}

/** @gqlContext */
export type Ctx = YogaInitialContext & { vc: VC };
10 changes: 10 additions & 0 deletions src/Errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
TYPE_TAG,
UNION_TAG,
SPECIFIED_BY_TAG,
CONTEXT_TAG,
} from "./Extractor";

export const ISSUE_URL = "https://github.com/captbaritone/grats/issues";
Expand Down Expand Up @@ -377,6 +378,15 @@ export function multipleContextTypes() {
return "Context argument's type does not match. Grats expects all resolvers that read the context argument to use the same type for that argument. Did you use the incorrect type in one of your resolvers?";
}

export function contextTagOnWrongNode() {
// TODO: Word this better
return `Expected \`@${CONTEXT_TAG}\` tag to be applied to a type alias declaration..`;
}

export function multipleContextDefinitions() {
return `Unexpected multiple \`@${CONTEXT_TAG}\` definitions. Only one type may be annotated with \`@${CONTEXT_TAG}\` in a Grats project.`;
}

export function graphQLNameHasLeadingNewlines(
name: string,
tagName: string,
Expand Down
24 changes: 24 additions & 0 deletions src/Extractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const ENUM_TAG = "gqlEnum";
export const UNION_TAG = "gqlUnion";
export const INPUT_TAG = "gqlInput";

export const CONTEXT_TAG = "gqlContext";
export const IMPLEMENTS_TAG_DEPRECATED = "gqlImplements";
export const KILLS_PARENT_ON_EXCEPTION_TAG = "killsParentOnException";

Expand All @@ -57,6 +58,7 @@ export const ALL_TAGS = [
ENUM_TAG,
UNION_TAG,
INPUT_TAG,
CONTEXT_TAG,
];

const DEPRECATED_TAG = "deprecated";
Expand All @@ -70,6 +72,7 @@ export type ExtractionSnapshot = {
readonly unresolvedNames: Map<ts.EntityName, NameNode>;
readonly nameDefinitions: Map<ts.DeclarationStatement, NameDefinition>;
readonly contextReferences: Array<ts.Node>;
readonly contextDefinitions: Array<ContextNodeType>;
readonly typesWithTypename: Set<string>;
readonly interfaceDeclarations: Array<ts.InterfaceDeclaration>;
};
Expand All @@ -95,13 +98,19 @@ export function extract(
return extractor.extract(sourceFile);
}

type ContextNodeType =
| ts.TypeAliasDeclaration
| ts.InterfaceDeclaration
| ts.ClassDeclaration;

class Extractor {
definitions: DefinitionNode[] = [];

// Snapshot data
unresolvedNames: Map<ts.EntityName, NameNode> = new Map();
nameDefinitions: Map<ts.DeclarationStatement, NameDefinition> = new Map();
contextReferences: Array<ts.Node> = [];
contextDefinitions: Array<ContextNodeType> = [];
typesWithTypename: Set<string> = new Set();
interfaceDeclarations: Array<ts.InterfaceDeclaration> = [];

Expand Down Expand Up @@ -195,6 +204,20 @@ class Extractor {
}
}
break;
case CONTEXT_TAG: {
if (
!(
ts.isTypeAliasDeclaration(node) ||
ts.isInterfaceDeclaration(node) ||
ts.isClassDeclaration(node)
)
) {
console.log("node kind", node.kind);
return this.report(node, E.contextTagOnWrongNode());
}
this.contextDefinitions.push(node);
break;
}
case KILLS_PARENT_ON_EXCEPTION_TAG: {
if (!this.hasTag(node, FIELD_TAG)) {
this.report(
Expand Down Expand Up @@ -257,6 +280,7 @@ class Extractor {
unresolvedNames: this.unresolvedNames,
nameDefinitions: this.nameDefinitions,
contextReferences: this.contextReferences,
contextDefinitions: this.contextDefinitions,
typesWithTypename: this.typesWithTypename,
interfaceDeclarations: this.interfaceDeclarations,
});
Expand Down
7 changes: 6 additions & 1 deletion src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export function extractSchemaAndDoc(
// Collect validation errors
const validationResult = concatResults(
validateMergedInterfaces(checker, snapshot.interfaceDeclarations),
validateContextReferences(ctx, snapshot.contextReferences),
validateContextReferences(ctx, snapshot),
);

const docResult = new ResultPipe(validationResult)
Expand Down Expand Up @@ -167,6 +167,7 @@ function combineSnapshots(snapshots: ExtractionSnapshot[]): ExtractionSnapshot {
nameDefinitions: new Map(),
unresolvedNames: new Map(),
contextReferences: [],
contextDefinitions: [],
typesWithTypename: new Set(),
interfaceDeclarations: [],
};
Expand All @@ -188,6 +189,10 @@ function combineSnapshots(snapshots: ExtractionSnapshot[]): ExtractionSnapshot {
result.contextReferences.push(contextReference);
}

for (const contextDefinition of snapshot.contextDefinitions) {
result.contextDefinitions.push(contextDefinition);
}

for (const typeName of snapshot.typesWithTypename) {
result.typesWithTypename.add(typeName);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ INPUT
-----------------
OUTPUT
-----------------
src/tests/fixtures/comments/detachedBlockCommentWithInvalidTagName.invalid.ts:2:4 - error: `@gqlTyp` is not a valid Grats tag. Valid tags are: `@gqlType`, `@gqlField`, `@gqlScalar`, `@gqlInterface`, `@gqlEnum`, `@gqlUnion`, `@gqlInput`.
src/tests/fixtures/comments/detachedBlockCommentWithInvalidTagName.invalid.ts:2:4 - error: `@gqlTyp` is not a valid Grats tag. Valid tags are: `@gqlType`, `@gqlField`, `@gqlScalar`, `@gqlInterface`, `@gqlEnum`, `@gqlUnion`, `@gqlInput`, `@gqlContext`.

2 * @gqlTyp
~~~~~~~
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default class Composer {
OUTPUT
-----------------
-- Error Report --
src/tests/fixtures/comments/invalidTagInLinecomment.ts:1:4 - error: `@gqlTyp` is not a valid Grats tag. Valid tags are: `@gqlType`, `@gqlField`, `@gqlScalar`, `@gqlInterface`, `@gqlEnum`, `@gqlUnion`, `@gqlInput`.
src/tests/fixtures/comments/invalidTagInLinecomment.ts:1:4 - error: `@gqlTyp` is not a valid Grats tag. Valid tags are: `@gqlType`, `@gqlField`, `@gqlScalar`, `@gqlInterface`, `@gqlEnum`, `@gqlUnion`, `@gqlInput`, `@gqlContext`.

1 // @gqlTyp
~~~~~~~
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default class Composer {
OUTPUT
-----------------
-- Error Report --
src/tests/fixtures/comments/lineCommentWrongCasing.invalid.ts:1:4 - error: `@GQLtYPE` is not a valid Grats tag. Valid tags are: `@gqlType`, `@gqlField`, `@gqlScalar`, `@gqlInterface`, `@gqlEnum`, `@gqlUnion`, `@gqlInput`.
src/tests/fixtures/comments/lineCommentWrongCasing.invalid.ts:1:4 - error: `@GQLtYPE` is not a valid Grats tag. Valid tags are: `@gqlType`, `@gqlField`, `@gqlScalar`, `@gqlInterface`, `@gqlEnum`, `@gqlUnion`, `@gqlInput`, `@gqlContext`.

1 // @GQLtYPE
~~~~~~~~
Expand All @@ -22,7 +22,7 @@ src/tests/fixtures/comments/lineCommentWrongCasing.invalid.ts:1:4 - error: Unexp
1 // @GQLtYPE
~~~~~~~~

src/tests/fixtures/comments/lineCommentWrongCasing.invalid.ts:3:6 - error: `@gqlfield` is not a valid Grats tag. Valid tags are: `@gqlType`, `@gqlField`, `@gqlScalar`, `@gqlInterface`, `@gqlEnum`, `@gqlUnion`, `@gqlInput`.
src/tests/fixtures/comments/lineCommentWrongCasing.invalid.ts:3:6 - error: `@gqlfield` is not a valid Grats tag. Valid tags are: `@gqlType`, `@gqlField`, `@gqlScalar`, `@gqlInterface`, `@gqlEnum`, `@gqlUnion`, `@gqlInput`, `@gqlContext`.

3 // @gqlfield
~~~~~~~~~
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ function hello() {
-----------------
OUTPUT
-----------------
src/tests/fixtures/interfaces/tag/ImplementsTagWithoutTypeOrInterface.invalid.ts:1:6 - error: `@gqlImplements` is not a valid Grats tag. Valid tags are: `@gqlType`, `@gqlField`, `@gqlScalar`, `@gqlInterface`, `@gqlEnum`, `@gqlUnion`, `@gqlInput`.
src/tests/fixtures/interfaces/tag/ImplementsTagWithoutTypeOrInterface.invalid.ts:1:6 - error: `@gqlImplements` is not a valid Grats tag. Valid tags are: `@gqlType`, `@gqlField`, `@gqlScalar`, `@gqlInterface`, `@gqlEnum`, `@gqlUnion`, `@gqlInput`, `@gqlContext`.

1 /** @gqlImplements Node */
~~~~~~~~~~~~~
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/** @gqlContext */
type GratsContext = {
greeting: string;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
-----------------
INPUT
-----------------
/** @gqlContext */
type GratsContext = {
greeting: string;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/** @gqlContext */
export type GratsContext = {
greeting: string;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
-----------------
INPUT
-----------------
/** @gqlContext */
export type GratsContext = {
greeting: string;
};
Expand Down
5 changes: 3 additions & 2 deletions src/tests/fixtures/resolver_context/ContextValueOptional.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
/** @gqlType */
export class SomeType {
/** @gqlField */
greeting(args: unknown, ctx?: SomeType): string {
greeting(args: unknown, ctx?: SomeCtx): string {
// This is fine since Grats will always pass ctx. It's fine for
// the resolver to _also_ work _without_ ctx, as long as it's
// safe for Grats to pass ctx.
return ctx?.greeting ?? "Hello, World!";
}
}

type SomeType = { greeting: string };
/** @gqlContext */
type SomeCtx = { greeting: string };
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ INPUT
/** @gqlType */
export class SomeType {
/** @gqlField */
greeting(args: unknown, ctx?: SomeType): string {
greeting(args: unknown, ctx?: SomeCtx): string {
// This is fine since Grats will always pass ctx. It's fine for
// the resolver to _also_ work _without_ ctx, as long as it's
// safe for Grats to pass ctx.
return ctx?.greeting ?? "Hello, World!";
}
}

type SomeType = { greeting: string };
/** @gqlContext */
type SomeCtx = { greeting: string };

-----------------
OUTPUT
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/** @gqlContext */
type GratsContext = {
greeting: string;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
-----------------
INPUT
-----------------
/** @gqlContext */
type GratsContext = {
greeting: string;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/** @gqlContext */
type GratsContext = {
greeting: string;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
-----------------
INPUT
-----------------
/** @gqlContext */
type GratsContext = {
greeting: string;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
/** @gqlContext */
type GratsContext = {
greeting: string;
};

/** @gqlContext */
type AlsoGratsContext = {
greeting: string;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
-----------------
INPUT
-----------------
/** @gqlContext */
type GratsContext = {
greeting: string;
};

/** @gqlContext */
type AlsoGratsContext = {
greeting: string;
};
Expand All @@ -24,12 +26,20 @@ export class SomeType {
-----------------
OUTPUT
-----------------
src/tests/fixtures/resolver_context/MultipleContextValuesUsed.invalid.ts:16:36 - error: Context argument's type does not match. Grats expects all resolvers that read the context argument to use the same type for that argument. Did you use the incorrect type in one of your resolvers?
src/tests/fixtures/resolver_context/MultipleContextValuesUsed.invalid.ts:7:1 - error: Unexpected multiple `@gqlContext` definitions. Only one type may be annotated with `@gqlContext` in a Grats project.

16 alsoGreeting(args: unknown, ctx: AlsoGratsContext): string {
~~~~~~~~~~~~~~~~
7 type AlsoGratsContext = {
~~~~~~~~~~~~~~~~~~~~~~~~~
8 greeting: string;
~~~~~~~~~~~~~~~~~~~
9 };
~~

src/tests/fixtures/resolver_context/MultipleContextValuesUsed.invalid.ts:12:32
12 greeting(args: unknown, ctx: GratsContext): string {
~~~~~~~~~~~~
A different type reference was used here
src/tests/fixtures/resolver_context/MultipleContextValuesUsed.invalid.ts:2:1
2 type GratsContext = {
~~~~~~~~~~~~~~~~~~~~~
3 greeting: string;
~~~~~~~~~~~~~~~~~~~
4 };
~~
A different context definition was found here
2 changes: 2 additions & 0 deletions src/tests/fixtures/resolver_context/contextTag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/** @gqlContext */
type Context = {};
18 changes: 18 additions & 0 deletions src/tests/fixtures/resolver_context/contextTag.ts.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-----------------
INPUT
-----------------
/** @gqlContext */
type Context = {};

-----------------
OUTPUT
-----------------
-- SDL --

-- TypeScript --
import { GraphQLSchema } from "graphql";
export function getSchema(): GraphQLSchema {
return new GraphQLSchema({
types: []
});
}
9 changes: 9 additions & 0 deletions src/tests/fixtures/resolver_context/contextTypeNotTagged.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
type Ctx = {};

/** @gqlType */
class User {
/** @gqlField */
greeting(_args: unknown, _ctx: Ctx): string {
return "Hello";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
-----------------
INPUT
-----------------
type Ctx = {};

/** @gqlType */
class User {
/** @gqlField */
greeting(_args: unknown, _ctx: Ctx): string {
return "Hello";
}
}

-----------------
OUTPUT
-----------------
-- Error Report --
src/tests/fixtures/resolver_context/contextTypeNotTagged.ts:6:34 - error: Add a @gqlContext tag

6 greeting(_args: unknown, _ctx: Ctx): string {
~~~

src/tests/fixtures/resolver_context/contextTypeNotTagged.ts:1:1
1 type Ctx = {};
~~~~~~~~~~~~~~
This is the type declaration

-- Code Action: "Tag context declaration with @gqlContext" (add-context-tag) --
- Original
+ Fixed

@@ -1,1 +1,2 @@
+ /** @gqlContext */
type Ctx = {};
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ src/tests/fixtures/type_definitions/TypeFromClassDefinitionImplementsInterfaceWi
~~~~~~~~~~~~~~~~~~~~~
10 */
~
src/tests/fixtures/type_definitions/TypeFromClassDefinitionImplementsInterfaceWithDeprecatedTag.invalid.ts:9:5 - error: `@gqlImplements` is not a valid Grats tag. Valid tags are: `@gqlType`, `@gqlField`, `@gqlScalar`, `@gqlInterface`, `@gqlEnum`, `@gqlUnion`, `@gqlInput`.
src/tests/fixtures/type_definitions/TypeFromClassDefinitionImplementsInterfaceWithDeprecatedTag.invalid.ts:9:5 - error: `@gqlImplements` is not a valid Grats tag. Valid tags are: `@gqlType`, `@gqlField`, `@gqlScalar`, `@gqlInterface`, `@gqlEnum`, `@gqlUnion`, `@gqlInput`, `@gqlContext`.

9 * @gqlImplements Person
~~~~~~~~~~~~~
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ src/tests/fixtures/type_definitions_from_alias/AliasTypeImplementsInterface.inva
~~~~~~~~~~~~~~~~~~~~~
4 */
~
src/tests/fixtures/type_definitions_from_alias/AliasTypeImplementsInterface.invalid.ts:3:5 - error: `@gqlImplements` is not a valid Grats tag. Valid tags are: `@gqlType`, `@gqlField`, `@gqlScalar`, `@gqlInterface`, `@gqlEnum`, `@gqlUnion`, `@gqlInput`.
src/tests/fixtures/type_definitions_from_alias/AliasTypeImplementsInterface.invalid.ts:3:5 - error: `@gqlImplements` is not a valid Grats tag. Valid tags are: `@gqlType`, `@gqlField`, `@gqlScalar`, `@gqlInterface`, `@gqlEnum`, `@gqlUnion`, `@gqlInput`, `@gqlContext`.

3 * @gqlImplements Person
~~~~~~~~~~~~~

0 comments on commit f6daf11

Please sign in to comment.