diff --git a/src/generator/hook/graphql-document-parser.ts b/src/generator/hook/graphql-document-parser.ts index 365247a..f7f60e0 100644 --- a/src/generator/hook/graphql-document-parser.ts +++ b/src/generator/hook/graphql-document-parser.ts @@ -6,6 +6,7 @@ import ts from 'typescript' import { createArgumentDefinition, createInputValueDefinition, + defaultScalarTypes, getFieldHash, getFieldName, hasAQuery, @@ -19,7 +20,7 @@ import { updateName, updateVariableDefinitions, } from '../../gql' -import { createArrayType, createType } from '../../ts' +import { createArrayType, createType, traverse } from '../../ts' import { camelCase, className, toString } from '../../util' import { GraphQLParserContext } from './graphql-parser-context' @@ -41,6 +42,34 @@ function parseEnum(enumType: gql.GraphQLEnumType, context: GraphQLParserContext) ) } +function findScalarType(sourceFile: ts.SourceFile, name: string) { + let typeAliasDeclaration: ts.TypeAliasDeclaration | undefined + traverse(sourceFile, { + [ts.SyntaxKind.TypeAliasDeclaration]: (node: ts.TypeAliasDeclaration) => { + if (node.name.getText() === name) { + typeAliasDeclaration = node + return true + } + }, + }) + return typeAliasDeclaration +} + +function parseScalar(scalarType: gql.GraphQLScalarType, context: GraphQLParserContext) { + const name = scalarType.name + if (defaultScalarTypes.includes(name)) return + const existingType = findScalarType(context.sourceFile, name) + context.addScalar( + existingType ?? + ts.factory.createTypeAliasDeclaration( + [ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], + ts.factory.createIdentifier(scalarType.name), + undefined, + ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword), + ), + ) +} + function parseUnionType(unionType: gql.GraphQLUnionType, context: GraphQLParserContext) { context.addUnion( ts.factory.createTypeAliasDeclaration( @@ -138,6 +167,8 @@ function parseInputType(schemaType: gql.GraphQLInputType, context: GraphQLParser ) } else if (gql.isEnumType(type)) { parseEnum(type, context) + } else if (gql.isScalarType(type)) { + parseScalar(type, context) } } @@ -310,9 +341,10 @@ export function parseDocument( inputDocument: gql.DocumentNode, schema: gql.GraphQLSchema, hookName: string | undefined, + sourceFile: ts.SourceFile, ) { const typeInfo = new gql.TypeInfo(schema) - const context = new GraphQLParserContext(typeInfo) + const context = new GraphQLParserContext(typeInfo, sourceFile) const document = fixDocument(inputDocument, schema, hookName, context) gql.visit( document, @@ -330,6 +362,8 @@ export function parseDocument( parseObjectType(node, type, context) } else if (gql.isEnumType(type)) { parseEnum(type, context) + } else if (gql.isScalarType(type)) { + parseScalar(type, context) } else if (gql.isUnionType(type)) { parseUnionType(type, context) } diff --git a/src/generator/hook/graphql-parser-context.ts b/src/generator/hook/graphql-parser-context.ts index b2b5f12..6d8bef9 100644 --- a/src/generator/hook/graphql-parser-context.ts +++ b/src/generator/hook/graphql-parser-context.ts @@ -33,7 +33,10 @@ export class GraphQLParserContext { private readonly typeNameTracker = new TypeNameTracker() private readonly parameterNameTracker = new ParameterNameTracker() - constructor(public readonly typeInfo: gql.TypeInfo) {} + constructor( + public readonly typeInfo: gql.TypeInfo, + public readonly sourceFile: ts.SourceFile, + ) {} getParameters() { return Object.values(this.parameters) @@ -86,6 +89,12 @@ export class GraphQLParserContext { return this } + addScalar(type: ts.TypeAliasDeclaration) { + const name = type.name.escapedText ?? '' + this.types[name] = type + return this + } + addParameter(propertyConfig: PropertyConfig, variableDefinition: gql.VariableDefinitionNode) { this.parameters[propertyConfig.name] = propertyConfig this.variableDefinition[propertyConfig.name] = variableDefinition diff --git a/src/generator/hook/hook-generator.test.ts b/src/generator/hook/hook-generator.test.ts index ebed4f1..5568b7c 100644 --- a/src/generator/hook/hook-generator.test.ts +++ b/src/generator/hook/hook-generator.test.ts @@ -59,13 +59,15 @@ describe('generateHook', () => { } export interface User { - id: string + id: ID name?: string __typename?: 'User' } + export type ID = string + export interface Variables { - id: string | undefined + id: ID | undefined } export function useGetUserQuery( @@ -113,13 +115,15 @@ describe('generateHook', () => { } export interface User { - id: string + id: ID name?: string __typename?: 'User' } + export type ID = string + export interface Variables { - id: string | undefined + id: ID | undefined limit?: number | undefined } @@ -180,14 +184,16 @@ describe('generateHook', () => { __typename?: 'User' } + export type ID = string + export interface UserFollowers { - id: string + id: ID name?: string __typename?: 'User' } export interface Variables { - id: string | undefined + id: ID | undefined limit?: number | undefined } @@ -256,21 +262,23 @@ describe('generateHook', () => { __typename?: 'User' } + export type ID = string + export interface UserFollowers { - id: string + id: ID name?: string followers: UserFollower1[] __typename?: 'User' } export interface UserFollower1 { - id: string + id: ID name?: string __typename?: 'User' } export interface Variables { - id: string | undefined + id: ID | undefined limit?: number | undefined followerLimit?: number | undefined } @@ -331,15 +339,17 @@ describe('generateHook', () => { __typename?: 'User' } + export type ID = string + export interface UserFollower { - id: string + id: ID name?: string __typename?: 'User' } export interface Variables { - id: string | undefined - followerId: string | undefined + id: ID | undefined + followerId: ID | undefined } export function useUserQuery( @@ -406,23 +416,25 @@ describe('generateHook', () => { __typename?: 'User' } + export type ID = string + export interface UserFollower { - id: string + id: ID name?: string follower?: UserFollower1 __typename?: 'User' } export interface UserFollower1 { - id: string + id: ID name?: string __typename?: 'User' } export interface Variables { - id: string | undefined - followerId: string | undefined - followerId1: string | undefined + id: ID | undefined + followerId: ID | undefined + followerId1: ID | undefined } export function useUserQuery( @@ -485,8 +497,10 @@ describe('generateHook', () => { __typename?: 'User' } + export type ID = string + export interface Variables { - id: string | undefined + id: ID | undefined } export function useMyQuery(variables: Variables, options?: QueryHookOptions) { @@ -540,14 +554,16 @@ describe('generateHook', () => { __typename?: 'User' } + export type ID = string + export interface UserFollowers { - id: string + id: ID __typename?: 'User' } export interface Variables { - id: string | undefined - followerId: string | undefined + id: ID | undefined + followerId: ID | undefined limit?: number | undefined } @@ -605,14 +621,16 @@ describe('generateHook', () => { __typename?: 'User' } + export type ID = string + export interface UserFollowers { - id: string + id: ID __typename?: 'User' } export interface Variables { - userId: string | undefined - followersId: string | undefined + userId: ID | undefined + followersId: ID | undefined limit?: number | undefined } @@ -683,14 +701,16 @@ describe('generateHook', () => { } export interface Tweet { - id: string + id: ID author: User mentions?: User[] __typename?: 'Tweet' } + export type ID = string + export interface User { - id: string + id: ID photo?: Image __typename?: 'User' } @@ -707,7 +727,7 @@ describe('generateHook', () => { } export interface Variables { - id: string | undefined + id: ID | undefined size?: ImageSize | undefined } @@ -759,13 +779,15 @@ describe('generateHook', () => { } export interface User { - id: string + id: ID name?: string __typename?: 'User' } + export type ID = string + export interface Variables { - id: string | undefined + id: ID | undefined } export function useUserQuery( @@ -815,7 +837,7 @@ describe('generateHook', () => { } export interface User { - id: string + id: ID name?: string __typename?: 'User' } @@ -826,6 +848,8 @@ describe('generateHook', () => { __typename?: 'RegisterUserInput' } + export type ID = string + export interface Variables { input: RegisterUserInput } @@ -872,13 +896,15 @@ describe('generateHook', () => { } export interface User { - id: string + id: ID name?: string __typename?: 'User' } + export type ID = string + export interface Variables { - id: string + id: ID } export function useSubscribeToToOnUserUsageSubscription( @@ -921,7 +947,7 @@ describe('generateHook', () => { } export interface User { - id: string + id: ID name?: string __typename?: 'User' } @@ -932,6 +958,8 @@ describe('generateHook', () => { __typename?: 'RegisterUserInput' } + export type ID = string + export interface Variables { input: RegisterUserInput } @@ -977,13 +1005,15 @@ describe('generateHook', () => { } export interface User { - id: string + id: ID name?: string __typename?: 'User' } + export type ID = string + export interface Variables { - id: string + id: ID } export function useOnUserChangeSubscription( @@ -1026,18 +1056,20 @@ describe('generateHook', () => { } export interface User { - id: string + id: ID status?: UserStatus __typename?: 'User' } + export type ID = string + export enum UserStatus { ACTIVE = 'ACTIVE', INACTIVE = 'INACTIVE', } export interface Variables { - id: string | undefined + id: ID | undefined } export function useUserQuery( @@ -1123,13 +1155,15 @@ describe('generateHook', () => { export type Notification = FollowNotification | TweetNotification export interface FollowNotification { - id: string + id: ID user: User __typename?: 'FollowNotification' } + export type ID = string + export interface User { - id: string + id: ID photo?: Image __typename?: 'User' } @@ -1146,13 +1180,13 @@ describe('generateHook', () => { } export interface TweetNotification { - id: string + id: ID tweet: Tweet __typename?: 'TweetNotification' } export interface Tweet { - id: string + id: ID author: UserAuthor __typename?: 'Tweet' } @@ -1216,8 +1250,10 @@ describe('generateHook', () => { __typename?: 'User' } + export type ID = string + export interface Variables { - id: string | undefined + id: ID | undefined } export function useUserQuery( @@ -1270,8 +1306,10 @@ describe('generateHook', () => { __typename?: 'User' } + export type ID = string + export interface Variables { - id: string | undefined + id: ID | undefined } export function useUserQuery( @@ -1329,8 +1367,10 @@ describe('generateHook', () => { __typename?: 'User' } + export type ID = string + export interface Variables { - id: string | undefined + id: ID | undefined } export function useUserQuery( @@ -1387,8 +1427,10 @@ describe('generateHook', () => { __typename?: 'User' } + export type ID = string + export interface Variables { - id: string | undefined + id: ID | undefined } export function useUserQuery( @@ -1451,15 +1493,17 @@ describe('generateHook', () => { __typename?: 'User' } + export type ID = string + export interface Tweet { - id: string + id: ID content: string __typename?: 'Tweet' } export interface Variables { - id: string | undefined - tweetId: string | undefined + id: ID | undefined + tweetId: ID | undefined } export function useUserAndTweetQuery( @@ -1513,8 +1557,10 @@ describe('generateHook', () => { __typename?: 'User' } + export type ID = string + export interface Variables { - id: string | undefined + id: ID | undefined limit?: number | undefined } @@ -1567,18 +1613,20 @@ describe('generateHook', () => { } export interface User { - id: string + id: ID status?: UserStatus __typename?: 'User' } + export type ID = string + export enum UserStatus { ACTIVE = 'ACTIVE', INACTIVE = 'INACTIVE', } export interface Variables { - id: string | undefined + id: ID | undefined } export function useUserQuery( @@ -1629,11 +1677,13 @@ describe('generateHook', () => { } export interface User { - id: string + id: ID status?: UserStatus __typename?: 'User' } + export type ID = string + export enum UserStatus { ACTIVE = 'ACTIVE', INACTIVE = 'INACTIVE', @@ -1691,13 +1741,16 @@ describe('generateHook', () => { } export interface User { - id: string + id: ID createdAt?: DateTime __typename?: 'User' } + export type ID = string + export type DateTime = string + export interface Variables { - id: string | undefined + id: ID | undefined } export function useUserQuery( @@ -1751,7 +1804,7 @@ describe('generateHook', () => { } export interface User { - id: string + id: ID name?: string email?: string __typename?: 'User' @@ -1763,6 +1816,8 @@ describe('generateHook', () => { __typename?: 'RegisterUserInput' } + export type ID = string + export interface Variables { input: RegisterUserInput } @@ -1811,13 +1866,15 @@ describe('generateHook', () => { } export interface User { - id: string + id: ID name?: string __typename?: 'User' } + export type ID = string + export interface Variables { - id: string + id: ID } export function useOnUserChangeSubscription( @@ -1886,14 +1943,16 @@ describe('generateHook', () => { } export interface Tweet { - id: string + id: ID author: User mentions?: User[] __typename?: 'Tweet' } + export type ID = string + export interface User { - id: string + id: ID photo?: Image __typename?: 'User' } @@ -1910,7 +1969,7 @@ describe('generateHook', () => { } export interface Variables { - id: string | undefined + id: ID | undefined size?: ImageSize | undefined } @@ -1965,12 +2024,14 @@ describe('generateHook', () => { } export interface User { - id: string + id: ID name?: string email?: string __typename?: 'User' } + export type ID = string + export function useMeQuery(options?: QueryHookOptions) { return useQuery(query, options) } @@ -2015,12 +2076,14 @@ describe('generateHook', () => { } export interface User { - id: string + id: ID name?: string email?: string __typename?: 'User' } + export type ID = string + export function useMeQuery(options?: LazyQueryHookOptions) { return useLazyQuery(lazyQuery, options) } @@ -2065,8 +2128,10 @@ describe('generateHook', () => { __typename?: 'User' } + export type ID = string + export interface Variables { - id: string | undefined + id: ID | undefined } export function useUserQuery( @@ -2115,10 +2180,12 @@ describe('generateHook', () => { } export interface User { - id: string + id: ID __typename?: 'User' } + export type ID = string + export function useSignInMutation(options?: MutationHookOptions) { return useMutation(mutation, options) } @@ -2126,6 +2193,122 @@ describe('generateHook', () => { ) }) + test('should generate scalar types', async () => { + const query = ` + import gql from 'graphql-tag' + + const mutation = gql\` + query { + post { + id + } + } + \` + ` + const { hook, errors } = await generate('use-query.gql.ts', query) + expect(errors).toEqual([]) + expect(toParsedOutput(hook)).toEqual( + toParsedOutput(` + import { QueryHookOptions, useQuery } from '@apollo/client' + import gql from 'graphql-tag' + + const mutation = gql\` + query postQuery($id: PostId!) { + post(id: $id) { + id + } + } + \` + + export interface PostQuery { + post?: Post + __typename?: 'Query' + } + + export interface Post { + id: PostId + __typename?: 'Post' + } + + export type PostId = string + + export interface Variables { + id: PostId | undefined + } + + export function usePostQuery( + variables: Variables, + options?: QueryHookOptions, + ) { + return useQuery(mutation, { + variables, + skip: !variables.id, + ...options, + }) + } + `), + ) + }) + + test('should preserve scalar type', async () => { + const query = ` + import gql from 'graphql-tag' + + const mutation = gql\` + query { + post { + id + } + } + \` + + export type PostId = number + ` + const { hook, errors } = await generate('use-query.gql.ts', query) + expect(errors).toEqual([]) + expect(toParsedOutput(hook)).toEqual( + toParsedOutput(` + import { QueryHookOptions, useQuery } from '@apollo/client' + import gql from 'graphql-tag' + + const mutation = gql\` + query postQuery($id: PostId!) { + post(id: $id) { + id + } + } + \` + + export interface PostQuery { + post?: Post + __typename?: 'Query' + } + + export interface Post { + id: PostId + __typename?: 'Post' + } + + export type PostId = number + + export interface Variables { + id: PostId | undefined + } + + export function usePostQuery( + variables: Variables, + options?: QueryHookOptions, + ) { + return useQuery(mutation, { + variables, + skip: !variables.id, + ...options, + }) + } + `), + ) + }) + test('should generate query with union', async () => { const query = ` import gql from 'graphql-tag' @@ -2201,13 +2384,15 @@ describe('generateHook', () => { export type Notification = FollowNotification | TweetNotification export interface FollowNotification { - id: string + id: ID user: User __typename?: 'FollowNotification' } + export type ID = string + export interface User { - id: string + id: ID photo?: Image __typename?: 'User' } @@ -2224,13 +2409,13 @@ describe('generateHook', () => { } export interface TweetNotification { - id: string + id: ID tweet: Tweet __typename?: 'TweetNotification' } export interface Tweet { - id: string + id: ID author: User __typename?: 'Tweet' } diff --git a/src/generator/hook/hook-generator.ts b/src/generator/hook/hook-generator.ts index 03db2a5..eabccf2 100644 --- a/src/generator/hook/hook-generator.ts +++ b/src/generator/hook/hook-generator.ts @@ -53,7 +53,7 @@ async function generateGQLHook(sourceFile: ts.SourceFile, schema: GraphQLSchema, // parse graphql const initialDocument = gql.parse(query.replace(/\{[\s\n]*\}/g, '{ __typename }')) - const { document, types } = parseDocument(initialDocument, schema, hookName) + const { document, types } = parseDocument(initialDocument, schema, hookName, sourceFile) // create imports const libraryName = identifyLibrary(sourceFile, context.config) diff --git a/src/generator/hook/index.ts b/src/generator/hook/index.ts index e40fb02..711a461 100644 --- a/src/generator/hook/index.ts +++ b/src/generator/hook/index.ts @@ -1,2 +1 @@ export * from './hook-generator' -export * from './hook-generator' diff --git a/src/gql/gql-context.ts b/src/gql/gql-context.ts index b046f01..710f9f4 100644 --- a/src/gql/gql-context.ts +++ b/src/gql/gql-context.ts @@ -29,6 +29,6 @@ export function createGraphQLContext( diagnostics: context.diagnostics ?? [], path: toNonNullArray((context.path ?? []).concat(path as string)), parent: nextParent, - parents: !nextParent ? context.parents ?? [] : [...(context.parents ?? []), nextParent], + parents: !nextParent ? (context.parents ?? []) : [...(context.parents ?? []), nextParent], } } diff --git a/src/gql/to-js-type.ts b/src/gql/to-js-type.ts index 9ebb748..7029fcc 100644 --- a/src/gql/to-js-type.ts +++ b/src/gql/to-js-type.ts @@ -1,10 +1,10 @@ import * as gql from 'graphql' +export const defaultScalarTypes = ['String', 'Int', 'Float', 'Boolean'] + export function toJSType(type: gql.GraphQLNamedOutputType | gql.GraphQLNamedInputType) { if (gql.isScalarType(type)) { switch (type.name) { - case 'ID': - return 'string' case 'String': return 'string' case 'Int': diff --git a/src/ts/syntax-kind-to-declaration.ts b/src/ts/syntax-kind-to-declaration.ts new file mode 100644 index 0000000..0aff06a --- /dev/null +++ b/src/ts/syntax-kind-to-declaration.ts @@ -0,0 +1,361 @@ +import * as ts from 'typescript' + +export type SyntaxKindToDeclaration = { + [ts.SyntaxKind.Unknown]: null + [ts.SyntaxKind.EndOfFileToken]: ts.EndOfFileToken + [ts.SyntaxKind.SingleLineCommentTrivia]: ts.CommentRange + [ts.SyntaxKind.MultiLineCommentTrivia]: ts.CommentRange + [ts.SyntaxKind.NewLineTrivia]: ts.Node + [ts.SyntaxKind.WhitespaceTrivia]: ts.Node + [ts.SyntaxKind.ConflictMarkerTrivia]: ts.Node + [ts.SyntaxKind.NonTextFileMarkerTrivia]: ts.Node + [ts.SyntaxKind.NumericLiteral]: ts.NumericLiteral + [ts.SyntaxKind.BigIntLiteral]: ts.BigIntLiteral + [ts.SyntaxKind.StringLiteral]: ts.StringLiteral + [ts.SyntaxKind.JsxText]: ts.JsxText + [ts.SyntaxKind.JsxTextAllWhiteSpaces]: ts.JsxText + [ts.SyntaxKind.RegularExpressionLiteral]: ts.RegularExpressionLiteral + [ts.SyntaxKind.NoSubstitutionTemplateLiteral]: ts.NoSubstitutionTemplateLiteral + [ts.SyntaxKind.TemplateHead]: ts.TemplateHead + [ts.SyntaxKind.TemplateMiddle]: ts.TemplateMiddle + [ts.SyntaxKind.TemplateTail]: ts.TemplateTail + [ts.SyntaxKind.OpenBraceToken]: ts.Token + [ts.SyntaxKind.CloseBraceToken]: ts.Token + [ts.SyntaxKind.OpenParenToken]: ts.Token + [ts.SyntaxKind.CloseParenToken]: ts.Token + [ts.SyntaxKind.OpenBracketToken]: ts.Token + [ts.SyntaxKind.CloseBracketToken]: ts.Token + [ts.SyntaxKind.DotToken]: ts.Token + [ts.SyntaxKind.DotDotDotToken]: ts.Token + [ts.SyntaxKind.SemicolonToken]: ts.Token + [ts.SyntaxKind.CommaToken]: ts.Token + [ts.SyntaxKind.QuestionDotToken]: ts.Token + [ts.SyntaxKind.LessThanToken]: ts.Token + [ts.SyntaxKind.LessThanSlashToken]: ts.Token + [ts.SyntaxKind.GreaterThanToken]: ts.Token + [ts.SyntaxKind.LessThanEqualsToken]: ts.Token + [ts.SyntaxKind.GreaterThanEqualsToken]: ts.Token + [ts.SyntaxKind.EqualsEqualsToken]: ts.Token + [ts.SyntaxKind.ExclamationEqualsToken]: ts.Token + [ts.SyntaxKind.EqualsEqualsEqualsToken]: ts.Token + [ts.SyntaxKind.ExclamationEqualsEqualsToken]: ts.Token + [ts.SyntaxKind.EqualsGreaterThanToken]: ts.Token + [ts.SyntaxKind.PlusToken]: ts.Token + [ts.SyntaxKind.MinusToken]: ts.Token + [ts.SyntaxKind.AsteriskToken]: ts.Token + [ts.SyntaxKind.AsteriskAsteriskToken]: ts.Token + [ts.SyntaxKind.SlashToken]: ts.Token + [ts.SyntaxKind.PercentToken]: ts.Token + [ts.SyntaxKind.PlusPlusToken]: ts.Token + [ts.SyntaxKind.MinusMinusToken]: ts.Token + [ts.SyntaxKind.LessThanLessThanToken]: ts.Token + [ts.SyntaxKind.GreaterThanGreaterThanToken]: ts.Token + [ts.SyntaxKind + .GreaterThanGreaterThanGreaterThanToken]: ts.Token + [ts.SyntaxKind.AmpersandToken]: ts.Token + [ts.SyntaxKind.BarToken]: ts.Token + [ts.SyntaxKind.CaretToken]: ts.Token + [ts.SyntaxKind.ExclamationToken]: ts.Token + [ts.SyntaxKind.TildeToken]: ts.Token + [ts.SyntaxKind.AmpersandAmpersandToken]: ts.Token + [ts.SyntaxKind.BarBarToken]: ts.Token + [ts.SyntaxKind.QuestionToken]: ts.Token + [ts.SyntaxKind.ColonToken]: ts.Token + [ts.SyntaxKind.AtToken]: ts.Token + [ts.SyntaxKind.QuestionQuestionToken]: ts.Token + [ts.SyntaxKind.BacktickToken]: ts.Token + [ts.SyntaxKind.HashToken]: ts.Token + [ts.SyntaxKind.EqualsToken]: ts.Token + [ts.SyntaxKind.PlusEqualsToken]: ts.Token + [ts.SyntaxKind.MinusEqualsToken]: ts.Token + [ts.SyntaxKind.AsteriskEqualsToken]: ts.Token + [ts.SyntaxKind.AsteriskAsteriskEqualsToken]: ts.Token + [ts.SyntaxKind.SlashEqualsToken]: ts.Token + [ts.SyntaxKind.PercentEqualsToken]: ts.Token + [ts.SyntaxKind.LessThanLessThanEqualsToken]: ts.Token + [ts.SyntaxKind + .GreaterThanGreaterThanEqualsToken]: ts.Token + [ts.SyntaxKind + .GreaterThanGreaterThanGreaterThanEqualsToken]: ts.Token + [ts.SyntaxKind.AmpersandEqualsToken]: ts.Token + [ts.SyntaxKind.BarEqualsToken]: ts.Token + [ts.SyntaxKind.BarBarEqualsToken]: ts.Token + [ts.SyntaxKind + .AmpersandAmpersandEqualsToken]: ts.Token + [ts.SyntaxKind.QuestionQuestionEqualsToken]: ts.Token + [ts.SyntaxKind.CaretEqualsToken]: ts.Token + [ts.SyntaxKind.Identifier]: ts.Identifier + [ts.SyntaxKind.PrivateIdentifier]: ts.PrivateIdentifier + [ts.SyntaxKind.BreakKeyword]: ts.Token + [ts.SyntaxKind.CaseKeyword]: ts.Token + [ts.SyntaxKind.CatchKeyword]: ts.Token + [ts.SyntaxKind.ClassKeyword]: ts.Token + [ts.SyntaxKind.ConstKeyword]: ts.Token + [ts.SyntaxKind.ContinueKeyword]: ts.Token + [ts.SyntaxKind.DebuggerKeyword]: ts.Token + [ts.SyntaxKind.DefaultKeyword]: ts.Token + [ts.SyntaxKind.DeleteKeyword]: ts.Token + [ts.SyntaxKind.DoKeyword]: ts.Token + [ts.SyntaxKind.ElseKeyword]: ts.Token + [ts.SyntaxKind.EnumKeyword]: ts.Token + [ts.SyntaxKind.ExportKeyword]: ts.Token + [ts.SyntaxKind.ExtendsKeyword]: ts.Token + [ts.SyntaxKind.FalseKeyword]: ts.Token + [ts.SyntaxKind.FinallyKeyword]: ts.Token + [ts.SyntaxKind.ForKeyword]: ts.Token + [ts.SyntaxKind.FunctionKeyword]: ts.Token + [ts.SyntaxKind.IfKeyword]: ts.Token + [ts.SyntaxKind.ImportKeyword]: ts.Token + [ts.SyntaxKind.InKeyword]: ts.Token + [ts.SyntaxKind.InstanceOfKeyword]: ts.Token + [ts.SyntaxKind.NewKeyword]: ts.Token + [ts.SyntaxKind.NullKeyword]: ts.Token + [ts.SyntaxKind.ReturnKeyword]: ts.Token + + [ts.SyntaxKind.SuperKeyword]: ts.Token + [ts.SyntaxKind.SwitchKeyword]: ts.Token + [ts.SyntaxKind.ThisKeyword]: ts.Token + [ts.SyntaxKind.ThrowKeyword]: ts.Token + [ts.SyntaxKind.TrueKeyword]: ts.Token + [ts.SyntaxKind.TryKeyword]: ts.Token + [ts.SyntaxKind.TypeOfKeyword]: ts.Token + [ts.SyntaxKind.VarKeyword]: ts.Token + [ts.SyntaxKind.VoidKeyword]: ts.Token + [ts.SyntaxKind.WhileKeyword]: ts.Token + [ts.SyntaxKind.WithKeyword]: ts.Token + [ts.SyntaxKind.ImplementsKeyword]: ts.Token + [ts.SyntaxKind.InterfaceKeyword]: ts.Token + [ts.SyntaxKind.LetKeyword]: ts.Token + [ts.SyntaxKind.PackageKeyword]: ts.Token + [ts.SyntaxKind.PrivateKeyword]: ts.Token + [ts.SyntaxKind.ProtectedKeyword]: ts.Token + [ts.SyntaxKind.PublicKeyword]: ts.Token + [ts.SyntaxKind.StaticKeyword]: ts.Token + [ts.SyntaxKind.YieldKeyword]: ts.Token + [ts.SyntaxKind.AbstractKeyword]: ts.Token + [ts.SyntaxKind.AccessorKeyword]: ts.Token + [ts.SyntaxKind.AsKeyword]: ts.Token + [ts.SyntaxKind.AssertsKeyword]: ts.Token + [ts.SyntaxKind.AssertKeyword]: ts.Token + [ts.SyntaxKind.AnyKeyword]: ts.Token + [ts.SyntaxKind.AsyncKeyword]: ts.Token + [ts.SyntaxKind.AwaitKeyword]: ts.Token + [ts.SyntaxKind.BooleanKeyword]: ts.Token + [ts.SyntaxKind.ConstructorKeyword]: ts.Token + [ts.SyntaxKind.DeclareKeyword]: ts.Token + [ts.SyntaxKind.GetKeyword]: ts.Token + [ts.SyntaxKind.InferKeyword]: ts.Token + [ts.SyntaxKind.IntrinsicKeyword]: ts.Token + [ts.SyntaxKind.IsKeyword]: ts.Token + [ts.SyntaxKind.KeyOfKeyword]: ts.Token + [ts.SyntaxKind.ModuleKeyword]: ts.Token + [ts.SyntaxKind.NamespaceKeyword]: ts.Token + [ts.SyntaxKind.NeverKeyword]: ts.Token + [ts.SyntaxKind.OutKeyword]: ts.Token + [ts.SyntaxKind.ReadonlyKeyword]: ts.Token + [ts.SyntaxKind.RequireKeyword]: ts.Token + [ts.SyntaxKind.NumberKeyword]: ts.Token + [ts.SyntaxKind.ObjectKeyword]: ts.Token + [ts.SyntaxKind.SatisfiesKeyword]: ts.Token + [ts.SyntaxKind.SetKeyword]: ts.Token + [ts.SyntaxKind.StringKeyword]: ts.Token + [ts.SyntaxKind.SymbolKeyword]: ts.Token + [ts.SyntaxKind.TypeKeyword]: ts.Token + [ts.SyntaxKind.UndefinedKeyword]: ts.Token + [ts.SyntaxKind.UniqueKeyword]: ts.Token + [ts.SyntaxKind.UnknownKeyword]: ts.Token + [ts.SyntaxKind.UsingKeyword]: ts.Token + [ts.SyntaxKind.FromKeyword]: ts.Token + [ts.SyntaxKind.GlobalKeyword]: ts.Token + [ts.SyntaxKind.BigIntKeyword]: ts.Token + [ts.SyntaxKind.OverrideKeyword]: ts.Token + [ts.SyntaxKind.OfKeyword]: ts.Token + [ts.SyntaxKind.QualifiedName]: ts.QualifiedName + [ts.SyntaxKind.ComputedPropertyName]: ts.ComputedPropertyName + [ts.SyntaxKind.TypeParameter]: ts.TypeParameterDeclaration + [ts.SyntaxKind.Parameter]: ts.ParameterDeclaration + [ts.SyntaxKind.Decorator]: ts.Decorator + [ts.SyntaxKind.PropertySignature]: ts.PropertySignature + [ts.SyntaxKind.PropertyDeclaration]: ts.PropertyDeclaration + [ts.SyntaxKind.MethodSignature]: ts.MethodSignature + [ts.SyntaxKind.MethodDeclaration]: ts.MethodDeclaration + [ts.SyntaxKind.ClassStaticBlockDeclaration]: ts.ClassStaticBlockDeclaration + [ts.SyntaxKind.Constructor]: ts.ConstructorDeclaration + [ts.SyntaxKind.GetAccessor]: ts.GetAccessorDeclaration + [ts.SyntaxKind.SetAccessor]: ts.SetAccessorDeclaration + [ts.SyntaxKind.CallSignature]: ts.CallSignatureDeclaration + [ts.SyntaxKind.ConstructSignature]: ts.ConstructSignatureDeclaration + [ts.SyntaxKind.IndexSignature]: ts.IndexSignatureDeclaration + [ts.SyntaxKind.TypePredicate]: ts.TypePredicateNode + [ts.SyntaxKind.TypeReference]: ts.TypeReferenceNode + [ts.SyntaxKind.FunctionType]: ts.FunctionTypeNode + [ts.SyntaxKind.ConstructorType]: ts.ConstructorTypeNode + [ts.SyntaxKind.TypeQuery]: ts.TypeQueryNode + [ts.SyntaxKind.TypeLiteral]: ts.TypeLiteralNode + [ts.SyntaxKind.ArrayType]: ts.ArrayTypeNode + [ts.SyntaxKind.TupleType]: ts.TupleTypeNode + [ts.SyntaxKind.OptionalType]: ts.OptionalTypeNode + [ts.SyntaxKind.RestType]: ts.RestTypeNode + [ts.SyntaxKind.UnionType]: ts.UnionTypeNode + [ts.SyntaxKind.IntersectionType]: ts.IntersectionTypeNode + [ts.SyntaxKind.ConditionalType]: ts.ConditionalTypeNode + [ts.SyntaxKind.InferType]: ts.InferTypeNode + [ts.SyntaxKind.ParenthesizedType]: ts.ParenthesizedTypeNode + [ts.SyntaxKind.ThisType]: ts.ThisTypeNode + [ts.SyntaxKind.TypeOperator]: ts.TypeOperatorNode + [ts.SyntaxKind.IndexedAccessType]: ts.IndexedAccessTypeNode + [ts.SyntaxKind.MappedType]: ts.MappedTypeNode + [ts.SyntaxKind.LiteralType]: ts.LiteralTypeNode + [ts.SyntaxKind.NamedTupleMember]: ts.NamedTupleMember + [ts.SyntaxKind.TemplateLiteralType]: ts.TemplateLiteralTypeNode + [ts.SyntaxKind.TemplateLiteralTypeSpan]: ts.TemplateLiteralTypeSpan + [ts.SyntaxKind.ImportType]: ts.ImportTypeNode + [ts.SyntaxKind.ObjectBindingPattern]: ts.ObjectBindingPattern + [ts.SyntaxKind.ArrayBindingPattern]: ts.ArrayBindingPattern + [ts.SyntaxKind.BindingElement]: ts.BindingElement + [ts.SyntaxKind.ArrayLiteralExpression]: ts.ArrayLiteralExpression + [ts.SyntaxKind.ObjectLiteralExpression]: ts.ObjectLiteralExpression + [ts.SyntaxKind.PropertyAccessExpression]: ts.PropertyAccessExpression + [ts.SyntaxKind.ElementAccessExpression]: ts.ElementAccessExpression + [ts.SyntaxKind.CallExpression]: ts.CallExpression + [ts.SyntaxKind.NewExpression]: ts.NewExpression + [ts.SyntaxKind.TaggedTemplateExpression]: ts.TaggedTemplateExpression + [ts.SyntaxKind.TypeAssertionExpression]: ts.TypeAssertion + [ts.SyntaxKind.ParenthesizedExpression]: ts.ParenthesizedExpression + [ts.SyntaxKind.FunctionExpression]: ts.FunctionExpression + [ts.SyntaxKind.ArrowFunction]: ts.ArrowFunction + [ts.SyntaxKind.DeleteExpression]: ts.DeleteExpression + [ts.SyntaxKind.TypeOfExpression]: ts.TypeOfExpression + [ts.SyntaxKind.VoidExpression]: ts.VoidExpression + [ts.SyntaxKind.AwaitExpression]: ts.AwaitExpression + [ts.SyntaxKind.PrefixUnaryExpression]: ts.PrefixUnaryExpression + [ts.SyntaxKind.PostfixUnaryExpression]: ts.PostfixUnaryExpression + [ts.SyntaxKind.BinaryExpression]: ts.BinaryExpression + [ts.SyntaxKind.ConditionalExpression]: ts.ConditionalExpression + [ts.SyntaxKind.TemplateExpression]: ts.TemplateExpression + [ts.SyntaxKind.YieldExpression]: ts.YieldExpression + [ts.SyntaxKind.SpreadElement]: ts.SpreadElement + [ts.SyntaxKind.ClassExpression]: ts.ClassExpression + [ts.SyntaxKind.OmittedExpression]: ts.OmittedExpression + [ts.SyntaxKind.ExpressionWithTypeArguments]: ts.ExpressionWithTypeArguments + [ts.SyntaxKind.AsExpression]: ts.AsExpression + [ts.SyntaxKind.NonNullExpression]: ts.NonNullExpression + [ts.SyntaxKind.MetaProperty]: ts.MetaProperty + [ts.SyntaxKind.SyntheticExpression]: ts.SyntheticExpression + [ts.SyntaxKind.SatisfiesExpression]: ts.SatisfiesExpression + [ts.SyntaxKind.TemplateSpan]: ts.TemplateSpan + [ts.SyntaxKind.SemicolonClassElement]: ts.SemicolonClassElement + [ts.SyntaxKind.Block]: ts.Block + [ts.SyntaxKind.EmptyStatement]: ts.EmptyStatement + [ts.SyntaxKind.VariableStatement]: ts.VariableStatement + [ts.SyntaxKind.ExpressionStatement]: ts.ExpressionStatement + [ts.SyntaxKind.IfStatement]: ts.IfStatement + [ts.SyntaxKind.DoStatement]: ts.DoStatement + [ts.SyntaxKind.WhileStatement]: ts.WhileStatement + [ts.SyntaxKind.ForStatement]: ts.ForStatement + [ts.SyntaxKind.ForInStatement]: ts.ForInStatement + [ts.SyntaxKind.ForOfStatement]: ts.ForOfStatement + [ts.SyntaxKind.ContinueStatement]: ts.ContinueStatement + [ts.SyntaxKind.BreakStatement]: ts.BreakStatement + [ts.SyntaxKind.ReturnStatement]: ts.ReturnStatement + [ts.SyntaxKind.WithStatement]: ts.WithStatement + [ts.SyntaxKind.SwitchStatement]: ts.SwitchStatement + [ts.SyntaxKind.LabeledStatement]: ts.LabeledStatement + [ts.SyntaxKind.ThrowStatement]: ts.ThrowStatement + [ts.SyntaxKind.TryStatement]: ts.TryStatement + [ts.SyntaxKind.DebuggerStatement]: ts.DebuggerStatement + [ts.SyntaxKind.VariableDeclaration]: ts.VariableDeclaration + [ts.SyntaxKind.VariableDeclarationList]: ts.VariableDeclarationList + [ts.SyntaxKind.FunctionDeclaration]: ts.FunctionDeclaration + [ts.SyntaxKind.ClassDeclaration]: ts.ClassDeclaration + [ts.SyntaxKind.InterfaceDeclaration]: ts.InterfaceDeclaration + [ts.SyntaxKind.TypeAliasDeclaration]: ts.TypeAliasDeclaration + [ts.SyntaxKind.EnumDeclaration]: ts.EnumDeclaration + [ts.SyntaxKind.ModuleDeclaration]: ts.ModuleDeclaration + [ts.SyntaxKind.ModuleBlock]: ts.ModuleBlock + [ts.SyntaxKind.CaseBlock]: ts.CaseBlock + [ts.SyntaxKind.NamespaceExportDeclaration]: ts.NamespaceExportDeclaration + [ts.SyntaxKind.ImportEqualsDeclaration]: ts.ImportEqualsDeclaration + [ts.SyntaxKind.ImportDeclaration]: ts.ImportDeclaration + [ts.SyntaxKind.ImportClause]: ts.ImportClause + [ts.SyntaxKind.NamespaceImport]: ts.NamespaceImport + [ts.SyntaxKind.NamedImports]: ts.NamedImports + [ts.SyntaxKind.ImportSpecifier]: ts.ImportSpecifier + [ts.SyntaxKind.ExportAssignment]: ts.ExportAssignment + [ts.SyntaxKind.ExportDeclaration]: ts.ExportDeclaration + [ts.SyntaxKind.NamedExports]: ts.NamedExports + [ts.SyntaxKind.NamespaceExport]: ts.NamespaceExport + [ts.SyntaxKind.ExportSpecifier]: ts.ExportSpecifier + [ts.SyntaxKind.MissingDeclaration]: ts.MissingDeclaration + [ts.SyntaxKind.ExternalModuleReference]: ts.ExternalModuleReference + [ts.SyntaxKind.JsxElement]: ts.JsxElement + [ts.SyntaxKind.JsxSelfClosingElement]: ts.JsxSelfClosingElement + [ts.SyntaxKind.JsxOpeningElement]: ts.JsxOpeningElement + [ts.SyntaxKind.JsxClosingElement]: ts.JsxClosingElement + [ts.SyntaxKind.JsxFragment]: ts.JsxFragment + [ts.SyntaxKind.JsxOpeningFragment]: ts.JsxOpeningFragment + [ts.SyntaxKind.JsxClosingFragment]: ts.JsxClosingFragment + [ts.SyntaxKind.JsxAttribute]: ts.JsxAttribute + [ts.SyntaxKind.JsxAttributes]: ts.JsxAttributes + [ts.SyntaxKind.JsxSpreadAttribute]: ts.JsxSpreadAttribute + [ts.SyntaxKind.JsxExpression]: ts.JsxExpression + [ts.SyntaxKind.JsxNamespacedName]: ts.JsxNamespacedName + [ts.SyntaxKind.CaseClause]: ts.CaseClause + [ts.SyntaxKind.DefaultClause]: ts.DefaultClause + [ts.SyntaxKind.HeritageClause]: ts.HeritageClause + [ts.SyntaxKind.CatchClause]: ts.CatchClause + [ts.SyntaxKind.ImportAttributes]: ts.ImportAttributes + [ts.SyntaxKind.ImportAttribute]: ts.ImportAttribute + [ts.SyntaxKind.PropertyAssignment]: ts.PropertyAssignment + [ts.SyntaxKind.ShorthandPropertyAssignment]: ts.ShorthandPropertyAssignment + [ts.SyntaxKind.SpreadAssignment]: ts.SpreadAssignment + [ts.SyntaxKind.EnumMember]: ts.EnumMember + [ts.SyntaxKind.SourceFile]: ts.SourceFile + [ts.SyntaxKind.Bundle]: ts.Bundle + [ts.SyntaxKind.JSDocTypeExpression]: ts.JSDocTypeExpression + [ts.SyntaxKind.JSDocNameReference]: ts.JSDocNameReference + [ts.SyntaxKind.JSDocMemberName]: ts.JSDocMemberName + [ts.SyntaxKind.JSDocAllType]: ts.JSDocAllType + [ts.SyntaxKind.JSDocUnknownType]: ts.JSDocUnknownType + [ts.SyntaxKind.JSDocNullableType]: ts.JSDocNullableType + [ts.SyntaxKind.JSDocNonNullableType]: ts.JSDocNonNullableType + [ts.SyntaxKind.JSDocOptionalType]: ts.JSDocOptionalType + [ts.SyntaxKind.JSDocFunctionType]: ts.JSDocFunctionType + [ts.SyntaxKind.JSDocVariadicType]: ts.JSDocVariadicType + [ts.SyntaxKind.JSDocNamepathType]: ts.JSDocNamepathType + [ts.SyntaxKind.JSDoc]: ts.JSDoc + [ts.SyntaxKind.JSDocText]: ts.JSDocText + [ts.SyntaxKind.JSDocTypeLiteral]: ts.JSDocTypeLiteral + [ts.SyntaxKind.JSDocSignature]: ts.JSDocSignature + [ts.SyntaxKind.JSDocLink]: ts.JSDocLink + [ts.SyntaxKind.JSDocLinkCode]: ts.JSDocLinkCode + [ts.SyntaxKind.JSDocLinkPlain]: ts.JSDocLinkPlain + [ts.SyntaxKind.JSDocTag]: ts.JSDocTag + [ts.SyntaxKind.JSDocAugmentsTag]: ts.JSDocAugmentsTag + [ts.SyntaxKind.JSDocImplementsTag]: ts.JSDocImplementsTag + [ts.SyntaxKind.JSDocAuthorTag]: ts.JSDocAuthorTag + [ts.SyntaxKind.JSDocDeprecatedTag]: ts.JSDocDeprecatedTag + [ts.SyntaxKind.JSDocClassTag]: ts.JSDocClassTag + [ts.SyntaxKind.JSDocPublicTag]: ts.JSDocPublicTag + [ts.SyntaxKind.JSDocPrivateTag]: ts.JSDocPrivateTag + [ts.SyntaxKind.JSDocProtectedTag]: ts.JSDocProtectedTag + [ts.SyntaxKind.JSDocReadonlyTag]: ts.JSDocReadonlyTag + [ts.SyntaxKind.JSDocOverrideTag]: ts.JSDocOverrideTag + [ts.SyntaxKind.JSDocCallbackTag]: ts.JSDocCallbackTag + [ts.SyntaxKind.JSDocOverloadTag]: ts.JSDocOverloadTag + [ts.SyntaxKind.JSDocEnumTag]: ts.JSDocEnumTag + [ts.SyntaxKind.JSDocParameterTag]: ts.JSDocParameterTag + [ts.SyntaxKind.JSDocReturnTag]: ts.JSDocReturnTag + [ts.SyntaxKind.JSDocThisTag]: ts.JSDocThisTag + [ts.SyntaxKind.JSDocTypeTag]: ts.JSDocTypeTag + [ts.SyntaxKind.JSDocTemplateTag]: ts.JSDocTemplateTag + [ts.SyntaxKind.JSDocTypedefTag]: ts.JSDocTypedefTag + [ts.SyntaxKind.JSDocSeeTag]: ts.JSDocSeeTag + [ts.SyntaxKind.JSDocPropertyTag]: ts.JSDocPropertyTag + [ts.SyntaxKind.JSDocThrowsTag]: ts.JSDocThrowsTag + [ts.SyntaxKind.JSDocSatisfiesTag]: ts.JSDocSatisfiesTag + [ts.SyntaxKind.SyntaxList]: ts.SyntaxList + [ts.SyntaxKind.NotEmittedStatement]: ts.NotEmittedStatement + [ts.SyntaxKind.PartiallyEmittedExpression]: ts.PartiallyEmittedExpression + [ts.SyntaxKind.CommaListExpression]: ts.CommaListExpression +} diff --git a/src/ts/traverse-syntax-tree.ts b/src/ts/traverse-syntax-tree.ts index f27aecd..ca0734a 100644 --- a/src/ts/traverse-syntax-tree.ts +++ b/src/ts/traverse-syntax-tree.ts @@ -1,4 +1,5 @@ import ts from 'typescript' +import { SyntaxKindToDeclaration } from './syntax-kind-to-declaration' export function getChildren(node: ts.Node) { const children: ts.Node[] = [] @@ -33,3 +34,20 @@ export function printSyntaxTree(node: ts.Node) { return false }) } + +export type Traverser = { + [K in keyof SyntaxKindToDeclaration]?: ( + node: SyntaxKindToDeclaration[K], + ) => boolean | null | undefined +} + +export function traverse(node: ts.Node, traverser: Traverser) { + function visitNode(node: ts.Node): boolean { + const callback = (traverser as any)[node.kind] + if (callback) { + return callback(node) === true + } + return ts.forEachChild(node, visitNode) === true + } + visitNode(node) +} diff --git a/test/schema.gql b/test/schema.gql index f4b6e66..850d971 100644 --- a/test/schema.gql +++ b/test/schema.gql @@ -1,4 +1,5 @@ scalar DateTime +scalar PostId enum UserStatus { ACTIVE @@ -26,6 +27,20 @@ type User { photo: Image } +type Post { + id: PostId! + content: String! + author: User! + createdAt: DateTime + updatedAt: DateTime + status: PostStatus +} + +enum PostStatus { + DRAFT + ACTIVE +} + input RegisterUserInput { name: String! email: String! @@ -36,6 +51,7 @@ type Query { user(id: ID!): User users(next: String): [User]! tweet(id: ID!): Tweet + post(id: PostId!): Post followers(id: ID!, limit: Int): [User]! myNotifications: [Notification]! }