From 78936e22f869e6c50fb80ea9207ff79203ff8cb3 Mon Sep 17 00:00:00 2001 From: Phil Pluckthun Date: Sat, 13 Jun 2026 14:16:24 +0100 Subject: [PATCH] Adopt new kind types w/o breaking `graphql` compatibility on misaligned versions (#66) * Add switch between graphql and non-graphql Kind type * Bump graphql test dependencies * Add type overlap for Kind type * Bump direct graphql peer dependency * Add schema coordinate types * Remove temporary `NativeKind` shim (used for testing types) * Reformat parser to comply to Kind mismatch * Update AST overrides * Add changeset * Update src/parser.ts Co-authored-by: Jovi De Croock * Smooth over kind incompatibility between `graphql@16` versions (#69) * Fix Kind compatibility across GraphQL 16 versions * Add GraphQL 16 compatibility fixtures --------- Co-authored-by: kitten-agent --------- Co-authored-by: Jovi De Croock Co-authored-by: kitten-agent --- .changeset/many-ants-work.md | 5 + fixtures/graphql16-new/index.ts | 90 +++++++++++ fixtures/graphql16-new/tsconfig.json | 10 ++ fixtures/graphql16-old/index.ts | 44 ++++++ fixtures/graphql16-old/tsconfig.json | 10 ++ package.json | 9 +- pnpm-lock.yaml | 39 +++-- .../graphql-version-compat.test-d.ts | 44 ++++++ src/ast.ts | 20 ++- src/coordinateAst.ts | 44 ++++++ src/index.ts | 1 + src/kind.d.ts | 142 ++++++++++++------ src/kind.js | 6 + src/parser.ts | 85 ++++++----- vitest.config.ts | 1 + 15 files changed, 451 insertions(+), 99 deletions(-) create mode 100644 .changeset/many-ants-work.md create mode 100644 fixtures/graphql16-new/index.ts create mode 100644 fixtures/graphql16-new/tsconfig.json create mode 100644 fixtures/graphql16-old/index.ts create mode 100644 fixtures/graphql16-old/tsconfig.json create mode 100644 src/__tests__/graphql-version-compat.test-d.ts create mode 100644 src/coordinateAst.ts diff --git a/.changeset/many-ants-work.md b/.changeset/many-ants-work.md new file mode 100644 index 0000000..8018b1a --- /dev/null +++ b/.changeset/many-ants-work.md @@ -0,0 +1,5 @@ +--- +'@0no-co/graphql.web': minor +--- + +Update AST types to comply with schema coordinates additions (`graphql@^16.12.0`) diff --git a/fixtures/graphql16-new/index.ts b/fixtures/graphql16-new/index.ts new file mode 100644 index 0000000..9091187 --- /dev/null +++ b/fixtures/graphql16-new/index.ts @@ -0,0 +1,90 @@ +import * as graphql from 'graphql'; + +import { Kind, OperationTypeNode, parse, parseValue } from '../../src'; +import type { + ASTNode, + ArgumentCoordinateNode, + DirectiveArgumentCoordinateNode, + DirectiveCoordinateNode, + DocumentNode, + MemberCoordinateNode, + SchemaCoordinateNode, + TypeCoordinateNode, +} from '../../src'; + +export const constructedDocument: DocumentNode = { + kind: Kind.DOCUMENT, + definitions: [ + { + kind: Kind.OPERATION_DEFINITION, + operation: OperationTypeNode.QUERY, + selectionSet: { + kind: Kind.SELECTION_SET, + selections: [ + { + kind: Kind.FIELD, + name: { kind: Kind.NAME, value: 'id' }, + }, + ], + }, + }, + ], +}; + +export const typeCoordinate: TypeCoordinateNode = { + kind: Kind.TYPE_COORDINATE, + name: { kind: Kind.NAME, value: 'User' }, +}; + +export const memberCoordinate: MemberCoordinateNode = { + kind: Kind.MEMBER_COORDINATE, + name: { kind: Kind.NAME, value: 'User' }, + memberName: { kind: Kind.NAME, value: 'email' }, +}; + +export const argumentCoordinate: ArgumentCoordinateNode = { + kind: Kind.ARGUMENT_COORDINATE, + name: { kind: Kind.NAME, value: 'Query' }, + fieldName: { kind: Kind.NAME, value: 'user' }, + argumentName: { kind: Kind.NAME, value: 'id' }, +}; + +export const directiveCoordinate: DirectiveCoordinateNode = { + kind: Kind.DIRECTIVE_COORDINATE, + name: { kind: Kind.NAME, value: 'include' }, +}; + +export const directiveArgumentCoordinate: DirectiveArgumentCoordinateNode = { + kind: Kind.DIRECTIVE_ARGUMENT_COORDINATE, + name: { kind: Kind.NAME, value: 'include' }, + argumentName: { kind: Kind.NAME, value: 'if' }, +}; + +export function acceptsWebDocument(document: graphql.DocumentNode): DocumentNode { + return document; +} + +export function acceptsGraphQLDocument(document: DocumentNode): graphql.DocumentNode { + return document; +} + +export function acceptsWebASTNode(node: graphql.ASTNode): ASTNode { + return node; +} + +export function acceptsGraphQLASTNode(node: ASTNode): graphql.ASTNode { + return node; +} + +export function acceptsWebCoordinate(node: graphql.SchemaCoordinateNode): SchemaCoordinateNode { + return node; +} + +export function acceptsGraphQLCoordinate(node: SchemaCoordinateNode): graphql.SchemaCoordinateNode { + return node; +} + +export const graphqlParse: typeof graphql.parse = parse; +export const webParse: typeof parse = graphql.parse; +export const graphqlParseValue: typeof graphql.parseValue = parseValue; +export const webParseValue: typeof parseValue = graphql.parseValue; diff --git a/fixtures/graphql16-new/tsconfig.json b/fixtures/graphql16-new/tsconfig.json new file mode 100644 index 0000000..c346fc9 --- /dev/null +++ b/fixtures/graphql16-new/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": "../..", + "paths": { + "graphql": ["node_modules/graphql16new"], + }, + }, + "include": ["index.ts"], +} diff --git a/fixtures/graphql16-old/index.ts b/fixtures/graphql16-old/index.ts new file mode 100644 index 0000000..63b68c7 --- /dev/null +++ b/fixtures/graphql16-old/index.ts @@ -0,0 +1,44 @@ +import * as graphql from 'graphql'; + +import { Kind, OperationTypeNode, parse, parseValue } from '../../src'; +import type { ASTNode, DocumentNode } from '../../src'; + +export const constructedDocument: DocumentNode = { + kind: Kind.DOCUMENT, + definitions: [ + { + kind: Kind.OPERATION_DEFINITION, + operation: OperationTypeNode.QUERY, + selectionSet: { + kind: Kind.SELECTION_SET, + selections: [ + { + kind: Kind.FIELD, + name: { kind: Kind.NAME, value: 'id' }, + }, + ], + }, + }, + ], +}; + +export function acceptsWebDocument(document: graphql.DocumentNode): DocumentNode { + return document; +} + +export function acceptsGraphQLDocument(document: DocumentNode): graphql.DocumentNode { + return document; +} + +export function acceptsWebASTNode(node: graphql.ASTNode): ASTNode { + return node; +} + +export function acceptsGraphQLASTNode(node: ASTNode): graphql.ASTNode { + return node; +} + +export const graphqlParse: typeof graphql.parse = parse; +export const webParse: typeof parse = graphql.parse; +export const graphqlParseValue: typeof graphql.parseValue = parseValue; +export const webParseValue: typeof parseValue = graphql.parseValue; diff --git a/fixtures/graphql16-old/tsconfig.json b/fixtures/graphql16-old/tsconfig.json new file mode 100644 index 0000000..fe3a910 --- /dev/null +++ b/fixtures/graphql16-old/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": "../..", + "paths": { + "graphql": ["node_modules/graphql16old"], + }, + }, + "include": ["index.ts"], +} diff --git a/package.json b/package.json index 62e3248..ada6cc3 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "scripts": { "test": "vitest test", "bench": "vitest bench --typecheck.enabled=false", - "check": "tsc", + "check": "tsc && tsc -p fixtures/graphql16-old && tsc -p fixtures/graphql16-new", "lint": "eslint --ext=js,ts .", "build": "rollup -c scripts/rollup.config.mjs", "clean": "rimraf dist node_modules/.cache", @@ -95,8 +95,11 @@ "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-tsdoc": "^0.2.17", "graphql15": "npm:graphql@^15.8.0", - "graphql16": "npm:graphql@^16.8.1", - "graphql17": "npm:graphql@^17.0.0-alpha.3", + "graphql16old": "npm:graphql@16.11.0", + "graphql16new": "npm:graphql@16.12.0", + "graphql16": "npm:graphql@^16.12.0", + "graphql17": "npm:graphql@^17.0.0-alpha.9", + "graphql": "^16.12.0", "husky-v4": "^4.3.8", "jsr": "^0.12.1", "lint-staged": "^15.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 96127b5..13927e1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,10 +7,6 @@ settings: importers: .: - dependencies: - graphql: - specifier: ^14.0.0 || ^15.0.0 || ^16.0.0 - version: 16.8.1 devDependencies: '@actions/core': specifier: ^1.11.1 @@ -66,15 +62,24 @@ importers: eslint-plugin-tsdoc: specifier: ^0.2.17 version: 0.2.17 + graphql: + specifier: ^16.12.0 + version: 16.12.0 graphql15: specifier: npm:graphql@^15.8.0 version: graphql@15.8.0 graphql16: - specifier: npm:graphql@^16.8.1 - version: graphql@16.8.1 + specifier: npm:graphql@^16.12.0 + version: graphql@16.12.0 + graphql16new: + specifier: npm:graphql@16.12.0 + version: graphql@16.12.0 + graphql16old: + specifier: npm:graphql@16.11.0 + version: graphql@16.11.0 graphql17: - specifier: npm:graphql@^17.0.0-alpha.3 - version: graphql@17.0.0-alpha.3 + specifier: npm:graphql@^17.0.0-alpha.9 + version: graphql@17.0.0-alpha.9 husky-v4: specifier: ^4.3.8 version: 4.3.8 @@ -1436,12 +1441,16 @@ packages: resolution: {integrity: sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw==} engines: {node: '>= 10.x'} - graphql@16.8.1: - resolution: {integrity: sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==} + graphql@16.11.0: + resolution: {integrity: sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} - graphql@17.0.0-alpha.3: - resolution: {integrity: sha512-+3gX29B8lQ3Q1KkABSgy6CyBF7nTMObd6EHwcIHg4Pkz03D5hy/eJiHjD/io68+ti3goJmpF+OLJXKaUkgReXg==} + graphql@16.12.0: + resolution: {integrity: sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + + graphql@17.0.0-alpha.9: + resolution: {integrity: sha512-jVK1BsvX5pUIEpRDlEgeKJr80GAxl3B8ISsFDjXHtl2xAxMXVGTEFF4Q4R8NH0Gw7yMwcHDndkNjoNT5CbwHKA==} engines: {node: ^16.19.0 || ^18.14.0 || >=19.7.0} has-bigints@1.0.2: @@ -4132,9 +4141,11 @@ snapshots: graphql@15.8.0: {} - graphql@16.8.1: {} + graphql@16.11.0: {} + + graphql@16.12.0: {} - graphql@17.0.0-alpha.3: {} + graphql@17.0.0-alpha.9: {} has-bigints@1.0.2: {} diff --git a/src/__tests__/graphql-version-compat.test-d.ts b/src/__tests__/graphql-version-compat.test-d.ts new file mode 100644 index 0000000..f5ed42e --- /dev/null +++ b/src/__tests__/graphql-version-compat.test-d.ts @@ -0,0 +1,44 @@ +import { describe, it, expectTypeOf } from 'vitest'; +import type * as graphql16new from 'graphql16new'; + +import type { ASTNode, DocumentNode } from '../ast'; +import type { Kind } from '../kind'; +import type { + SchemaCoordinateNode, + TypeCoordinateNode, + MemberCoordinateNode, + ArgumentCoordinateNode, + DirectiveCoordinateNode, + DirectiveArgumentCoordinateNode, +} from '../coordinateAst'; + +describe('graphql@16.12 compatibility', () => { + it('keeps Kind assignable after GraphQL added schema coordinate kinds', () => { + expectTypeOf().toMatchTypeOf(); + expectTypeOf().toMatchTypeOf(); + expectTypeOf().toMatchTypeOf(); + expectTypeOf().toMatchTypeOf(); + }); + + it('keeps AST node types assignable to the current GraphQL 16 AST', () => { + expectTypeOf().toMatchTypeOf(); + expectTypeOf().toMatchTypeOf(); + expectTypeOf().toMatchTypeOf(); + expectTypeOf().toMatchTypeOf(); + }); + + it('matches GraphQL schema coordinate AST nodes', () => { + expectTypeOf().toMatchTypeOf(); + expectTypeOf().toMatchTypeOf(); + expectTypeOf().toMatchTypeOf(); + expectTypeOf().toMatchTypeOf(); + expectTypeOf().toMatchTypeOf(); + expectTypeOf().toMatchTypeOf(); + expectTypeOf().toMatchTypeOf(); + expectTypeOf().toMatchTypeOf(); + expectTypeOf().toMatchTypeOf(); + expectTypeOf().toMatchTypeOf(); + expectTypeOf().toMatchTypeOf(); + expectTypeOf().toMatchTypeOf(); + }); +}); diff --git a/src/ast.ts b/src/ast.ts index c94024d..c04df5a 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -27,6 +27,15 @@ import type { InputObjectTypeExtensionNode, } from './schemaAst'; +import type { + SchemaCoordinateNode, + TypeCoordinateNode, + MemberCoordinateNode, + ArgumentCoordinateNode, + DirectiveCoordinateNode, + DirectiveArgumentCoordinateNode, +} from './coordinateAst'; + export type ASTNode = Or< GraphQL.ASTNode, | NameNode @@ -72,6 +81,12 @@ export type ASTNode = Or< | UnionTypeExtensionNode | EnumTypeExtensionNode | InputObjectTypeExtensionNode + | SchemaCoordinateNode + | TypeCoordinateNode + | MemberCoordinateNode + | ArgumentCoordinateNode + | DirectiveCoordinateNode + | DirectiveArgumentCoordinateNode >; export type NameNode = Or< @@ -84,11 +99,12 @@ export type NameNode = Or< >; export type DocumentNode = Or< - GraphQL.DocumentNode, + GraphQL.DocumentNode & { readonly tokenCount?: number }, { readonly kind: Kind.DOCUMENT; readonly definitions: ReadonlyArray; readonly loc?: Location; + readonly tokenCount?: number; } >; @@ -103,7 +119,7 @@ export type ExecutableDefinitionNode = Or< >; export type OperationDefinitionNode = Or< - GraphQL.OperationDefinitionNode & { description?: StringValueNode }, + GraphQL.OperationDefinitionNode & { readonly description?: StringValueNode }, { readonly kind: Kind.OPERATION_DEFINITION; readonly operation: OperationTypeNode; diff --git a/src/coordinateAst.ts b/src/coordinateAst.ts new file mode 100644 index 0000000..c938f94 --- /dev/null +++ b/src/coordinateAst.ts @@ -0,0 +1,44 @@ +import type { Location } from './types'; +import type { Kind } from './kind'; +import type { NameNode } from './ast'; + +export declare type SchemaCoordinateNode = + | TypeCoordinateNode + | MemberCoordinateNode + | ArgumentCoordinateNode + | DirectiveCoordinateNode + | DirectiveArgumentCoordinateNode; + +export interface TypeCoordinateNode { + readonly kind: Kind.TYPE_COORDINATE; + readonly loc?: Location; + readonly name: NameNode; +} + +export interface MemberCoordinateNode { + readonly kind: Kind.MEMBER_COORDINATE; + readonly loc?: Location; + readonly name: NameNode; + readonly memberName: NameNode; +} + +export interface ArgumentCoordinateNode { + readonly kind: Kind.ARGUMENT_COORDINATE; + readonly loc?: Location; + readonly name: NameNode; + readonly fieldName: NameNode; + readonly argumentName: NameNode; +} + +export interface DirectiveCoordinateNode { + readonly kind: Kind.DIRECTIVE_COORDINATE; + readonly loc?: Location; + readonly name: NameNode; +} + +export interface DirectiveArgumentCoordinateNode { + readonly kind: Kind.DIRECTIVE_ARGUMENT_COORDINATE; + readonly loc?: Location; + readonly name: NameNode; + readonly argumentName: NameNode; +} diff --git a/src/index.ts b/src/index.ts index 7b93299..b41e5dc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ export type { Source, Location, Extensions } from './types'; export * from './ast'; export * from './schemaAst'; +export * from './coordinateAst'; export * from './kind'; export * from './error'; export * from './parser'; diff --git a/src/kind.d.ts b/src/kind.d.ts index ae9fd06..3f9a228 100644 --- a/src/kind.d.ts +++ b/src/kind.d.ts @@ -1,58 +1,112 @@ -export declare enum Kind { +import type * as GraphQL from 'graphql'; + +type GraphQLKind< + Key extends string, + Fallback extends string, +> = Key extends keyof typeof GraphQL.Kind ? (typeof GraphQL.Kind)[Key] : Fallback; + +export declare const Kind: typeof GraphQL.Kind & { + /** Coordinates */ + readonly TYPE_COORDINATE: GraphQLKind<'TYPE_COORDINATE', 'TypeCoordinate'>; + readonly MEMBER_COORDINATE: GraphQLKind<'MEMBER_COORDINATE', 'MemberCoordinate'>; + readonly ARGUMENT_COORDINATE: GraphQLKind<'ARGUMENT_COORDINATE', 'ArgumentCoordinate'>; + readonly DIRECTIVE_COORDINATE: GraphQLKind<'DIRECTIVE_COORDINATE', 'DirectiveCoordinate'>; + readonly DIRECTIVE_ARGUMENT_COORDINATE: GraphQLKind< + 'DIRECTIVE_ARGUMENT_COORDINATE', + 'DirectiveArgumentCoordinate' + >; +}; + +export type Kind = (typeof Kind)[keyof typeof Kind]; + +export declare namespace Kind { /** Name */ - NAME = 'Name', + export type NAME = GraphQLKind<'NAME', 'Name'>; /** Document */ - DOCUMENT = 'Document', - OPERATION_DEFINITION = 'OperationDefinition', - VARIABLE_DEFINITION = 'VariableDefinition', - SELECTION_SET = 'SelectionSet', - FIELD = 'Field', - ARGUMENT = 'Argument', + export type DOCUMENT = GraphQLKind<'DOCUMENT', 'Document'>; + export type OPERATION_DEFINITION = GraphQLKind<'OPERATION_DEFINITION', 'OperationDefinition'>; + export type VARIABLE_DEFINITION = GraphQLKind<'VARIABLE_DEFINITION', 'VariableDefinition'>; + export type SELECTION_SET = GraphQLKind<'SELECTION_SET', 'SelectionSet'>; + export type FIELD = GraphQLKind<'FIELD', 'Field'>; + export type ARGUMENT = GraphQLKind<'ARGUMENT', 'Argument'>; /** Fragments */ - FRAGMENT_SPREAD = 'FragmentSpread', - INLINE_FRAGMENT = 'InlineFragment', - FRAGMENT_DEFINITION = 'FragmentDefinition', + export type FRAGMENT_SPREAD = GraphQLKind<'FRAGMENT_SPREAD', 'FragmentSpread'>; + export type INLINE_FRAGMENT = GraphQLKind<'INLINE_FRAGMENT', 'InlineFragment'>; + export type FRAGMENT_DEFINITION = GraphQLKind<'FRAGMENT_DEFINITION', 'FragmentDefinition'>; /** Values */ - VARIABLE = 'Variable', - INT = 'IntValue', - FLOAT = 'FloatValue', - STRING = 'StringValue', - BOOLEAN = 'BooleanValue', - NULL = 'NullValue', - ENUM = 'EnumValue', - LIST = 'ListValue', - OBJECT = 'ObjectValue', - OBJECT_FIELD = 'ObjectField', + export type VARIABLE = GraphQLKind<'VARIABLE', 'Variable'>; + export type INT = GraphQLKind<'INT', 'IntValue'>; + export type FLOAT = GraphQLKind<'FLOAT', 'FloatValue'>; + export type STRING = GraphQLKind<'STRING', 'StringValue'>; + export type BOOLEAN = GraphQLKind<'BOOLEAN', 'BooleanValue'>; + export type NULL = GraphQLKind<'NULL', 'NullValue'>; + export type ENUM = GraphQLKind<'ENUM', 'EnumValue'>; + export type LIST = GraphQLKind<'LIST', 'ListValue'>; + export type OBJECT = GraphQLKind<'OBJECT', 'ObjectValue'>; + export type OBJECT_FIELD = GraphQLKind<'OBJECT_FIELD', 'ObjectField'>; /** Directives */ - DIRECTIVE = 'Directive', + export type DIRECTIVE = GraphQLKind<'DIRECTIVE', 'Directive'>; /** Types */ - NAMED_TYPE = 'NamedType', - LIST_TYPE = 'ListType', - NON_NULL_TYPE = 'NonNullType', + export type NAMED_TYPE = GraphQLKind<'NAMED_TYPE', 'NamedType'>; + export type LIST_TYPE = GraphQLKind<'LIST_TYPE', 'ListType'>; + export type NON_NULL_TYPE = GraphQLKind<'NON_NULL_TYPE', 'NonNullType'>; /** Type System Definitions */ - SCHEMA_DEFINITION = 'SchemaDefinition', - OPERATION_TYPE_DEFINITION = 'OperationTypeDefinition', + export type SCHEMA_DEFINITION = GraphQLKind<'SCHEMA_DEFINITION', 'SchemaDefinition'>; + export type OPERATION_TYPE_DEFINITION = GraphQLKind< + 'OPERATION_TYPE_DEFINITION', + 'OperationTypeDefinition' + >; /** Type Definitions */ - SCALAR_TYPE_DEFINITION = 'ScalarTypeDefinition', - OBJECT_TYPE_DEFINITION = 'ObjectTypeDefinition', - FIELD_DEFINITION = 'FieldDefinition', - INPUT_VALUE_DEFINITION = 'InputValueDefinition', - INTERFACE_TYPE_DEFINITION = 'InterfaceTypeDefinition', - UNION_TYPE_DEFINITION = 'UnionTypeDefinition', - ENUM_TYPE_DEFINITION = 'EnumTypeDefinition', - ENUM_VALUE_DEFINITION = 'EnumValueDefinition', - INPUT_OBJECT_TYPE_DEFINITION = 'InputObjectTypeDefinition', + export type SCALAR_TYPE_DEFINITION = GraphQLKind< + 'SCALAR_TYPE_DEFINITION', + 'ScalarTypeDefinition' + >; + export type OBJECT_TYPE_DEFINITION = GraphQLKind< + 'OBJECT_TYPE_DEFINITION', + 'ObjectTypeDefinition' + >; + export type FIELD_DEFINITION = GraphQLKind<'FIELD_DEFINITION', 'FieldDefinition'>; + export type INPUT_VALUE_DEFINITION = GraphQLKind< + 'INPUT_VALUE_DEFINITION', + 'InputValueDefinition' + >; + export type INTERFACE_TYPE_DEFINITION = GraphQLKind< + 'INTERFACE_TYPE_DEFINITION', + 'InterfaceTypeDefinition' + >; + export type UNION_TYPE_DEFINITION = GraphQLKind<'UNION_TYPE_DEFINITION', 'UnionTypeDefinition'>; + export type ENUM_TYPE_DEFINITION = GraphQLKind<'ENUM_TYPE_DEFINITION', 'EnumTypeDefinition'>; + export type ENUM_VALUE_DEFINITION = GraphQLKind<'ENUM_VALUE_DEFINITION', 'EnumValueDefinition'>; + export type INPUT_OBJECT_TYPE_DEFINITION = GraphQLKind< + 'INPUT_OBJECT_TYPE_DEFINITION', + 'InputObjectTypeDefinition' + >; /** Directive Definitions */ - DIRECTIVE_DEFINITION = 'DirectiveDefinition', + export type DIRECTIVE_DEFINITION = GraphQLKind<'DIRECTIVE_DEFINITION', 'DirectiveDefinition'>; /** Type System Extensions */ - SCHEMA_EXTENSION = 'SchemaExtension', + export type SCHEMA_EXTENSION = GraphQLKind<'SCHEMA_EXTENSION', 'SchemaExtension'>; /** Type Extensions */ - SCALAR_TYPE_EXTENSION = 'ScalarTypeExtension', - OBJECT_TYPE_EXTENSION = 'ObjectTypeExtension', - INTERFACE_TYPE_EXTENSION = 'InterfaceTypeExtension', - UNION_TYPE_EXTENSION = 'UnionTypeExtension', - ENUM_TYPE_EXTENSION = 'EnumTypeExtension', - INPUT_OBJECT_TYPE_EXTENSION = 'InputObjectTypeExtension', + export type SCALAR_TYPE_EXTENSION = GraphQLKind<'SCALAR_TYPE_EXTENSION', 'ScalarTypeExtension'>; + export type OBJECT_TYPE_EXTENSION = GraphQLKind<'OBJECT_TYPE_EXTENSION', 'ObjectTypeExtension'>; + export type INTERFACE_TYPE_EXTENSION = GraphQLKind< + 'INTERFACE_TYPE_EXTENSION', + 'InterfaceTypeExtension' + >; + export type UNION_TYPE_EXTENSION = GraphQLKind<'UNION_TYPE_EXTENSION', 'UnionTypeExtension'>; + export type ENUM_TYPE_EXTENSION = GraphQLKind<'ENUM_TYPE_EXTENSION', 'EnumTypeExtension'>; + export type INPUT_OBJECT_TYPE_EXTENSION = GraphQLKind< + 'INPUT_OBJECT_TYPE_EXTENSION', + 'InputObjectTypeExtension' + >; + /** Coordinates */ + export type TYPE_COORDINATE = GraphQLKind<'TYPE_COORDINATE', 'TypeCoordinate'>; + export type MEMBER_COORDINATE = GraphQLKind<'MEMBER_COORDINATE', 'MemberCoordinate'>; + export type ARGUMENT_COORDINATE = GraphQLKind<'ARGUMENT_COORDINATE', 'ArgumentCoordinate'>; + export type DIRECTIVE_COORDINATE = GraphQLKind<'DIRECTIVE_COORDINATE', 'DirectiveCoordinate'>; + export type DIRECTIVE_ARGUMENT_COORDINATE = GraphQLKind< + 'DIRECTIVE_ARGUMENT_COORDINATE', + 'DirectiveArgumentCoordinate' + >; } export declare enum OperationTypeNode { diff --git a/src/kind.js b/src/kind.js index 87ea346..0474a25 100644 --- a/src/kind.js +++ b/src/kind.js @@ -44,6 +44,12 @@ export const Kind = { UNION_TYPE_EXTENSION: 'UnionTypeExtension', ENUM_TYPE_EXTENSION: 'EnumTypeExtension', INPUT_OBJECT_TYPE_EXTENSION: 'InputObjectTypeExtension', + + TYPE_COORDINATE: 'TypeCoordinate', + MEMBER_COORDINATE: 'MemberCoordinate', + ARGUMENT_COORDINATE: 'ArgumentCoordinate', + DIRECTIVE_COORDINATE: 'DirectiveCoordinate', + DIRECTIVE_ARGUMENT_COORDINATE: 'DirectiveArgumentCoordinate', */ }; diff --git a/src/parser.ts b/src/parser.ts index afdc0de..2531133 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -4,9 +4,13 @@ * in graphql.js it will only parse the query language, but not the schema * language. */ + +import type * as GraphQL from 'graphql'; + import type { Kind, OperationTypeNode } from './kind'; import { GraphQLError } from './error'; -import type { Location, Source } from './types'; +import type { Or, Location, Source } from './types'; +import type { StringValueNode } from './ast'; import type * as ast from './ast'; let input: string; @@ -84,7 +88,7 @@ function name(): string { function nameNode(): ast.NameNode { return { - kind: 'Name' as Kind.NAME, + kind: 'Name' as Or, value: name(), }; } @@ -106,7 +110,7 @@ function value(constant: boolean): ast.ValueNode { idx++; ignored(); return { - kind: 'ListValue' as Kind.LIST, + kind: 'ListValue' as Or, values, }; @@ -119,7 +123,7 @@ function value(constant: boolean): ast.ValueNode { if (input.charCodeAt(idx++) !== 58 /*':'*/) throw error('ObjectField'); ignored(); fields.push({ - kind: 'ObjectField' as Kind.OBJECT_FIELD, + kind: 'ObjectField' as Or, name, value: value(constant), }); @@ -127,7 +131,7 @@ function value(constant: boolean): ast.ValueNode { idx++; ignored(); return { - kind: 'ObjectValue' as Kind.OBJECT, + kind: 'ObjectValue' as Or, fields, }; @@ -135,7 +139,7 @@ function value(constant: boolean): ast.ValueNode { if (constant) throw error('Variable'); idx++; return { - kind: 'Variable' as Kind.VARIABLE, + kind: 'Variable' as Or, name: nameNode(), }; @@ -145,7 +149,7 @@ function value(constant: boolean): ast.ValueNode { if ((match = advance(restBlockStringRe)) == null) throw error('StringValue'); ignored(); return { - kind: 'StringValue' as Kind.STRING, + kind: 'StringValue' as Or, value: blockString(match.slice(0, -3)), block: true, }; @@ -164,7 +168,7 @@ function value(constant: boolean): ast.ValueNode { match = input.slice(start, idx); ignored(); return { - kind: 'StringValue' as Kind.STRING, + kind: 'StringValue' as Or, value: isComplex ? (JSON.parse(match) as string) : match.slice(1, -1), block: false, }; @@ -193,13 +197,13 @@ function value(constant: boolean): ast.ValueNode { if ((match = advance(floatPartRe)) == null) throw error('FloatValue'); ignored(); return { - kind: 'FloatValue' as Kind.FLOAT, + kind: 'FloatValue' as Or, value: intPart + match, }; } else { ignored(); return { - kind: 'IntValue' as Kind.INT, + kind: 'IntValue' as Or, value: intPart, }; } @@ -212,7 +216,7 @@ function value(constant: boolean): ast.ValueNode { ) { idx += 4; ignored(); - return { kind: 'NullValue' as Kind.NULL }; + return { kind: 'NullValue' as Or }; } else break; case 116: // 't' @@ -223,7 +227,7 @@ function value(constant: boolean): ast.ValueNode { ) { idx += 4; ignored(); - return { kind: 'BooleanValue' as Kind.BOOLEAN, value: true }; + return { kind: 'BooleanValue' as Or, value: true }; } else break; case 102: // 'f' @@ -235,12 +239,12 @@ function value(constant: boolean): ast.ValueNode { ) { idx += 5; ignored(); - return { kind: 'BooleanValue' as Kind.BOOLEAN, value: false }; + return { kind: 'BooleanValue' as Or, value: false }; } else break; } return { - kind: 'EnumValue' as Kind.ENUM, + kind: 'EnumValue' as Or, value: name(), }; } @@ -255,7 +259,7 @@ function arguments_(constant: boolean): ast.ArgumentNode[] | undefined { if (input.charCodeAt(idx++) !== 58 /*':'*/) throw error('Argument'); ignored(); args.push({ - kind: 'Argument' as Kind.ARGUMENT, + kind: 'Argument' as Or, name, value: value(constant), }); @@ -275,7 +279,7 @@ function directives(constant: boolean): ast.DirectiveNode[] | undefined { do { idx++; directives.push({ - kind: 'Directive' as Kind.DIRECTIVE, + kind: 'Directive' as Or, name: nameNode(), arguments: arguments_(constant), }); @@ -292,7 +296,7 @@ function type(): ast.TypeNode { ignored(); } let type: ast.TypeNode = { - kind: 'NamedType' as Kind.NAMED_TYPE, + kind: 'NamedType' as Or, name: nameNode(), }; do { @@ -300,7 +304,7 @@ function type(): ast.TypeNode { idx++; ignored(); type = { - kind: 'NonNullType' as Kind.NON_NULL_TYPE, + kind: 'NonNullType' as Or, type: type as ast.NamedTypeNode | ast.ListTypeNode, } satisfies ast.NonNullTypeNode; } @@ -308,7 +312,7 @@ function type(): ast.TypeNode { if (input.charCodeAt(idx++) !== 93 /*']'*/) throw error('NamedType'); ignored(); type = { - kind: 'ListType' as Kind.LIST_TYPE, + kind: 'ListType' as Or, type: type as ast.NamedTypeNode | ast.ListTypeNode, } satisfies ast.ListTypeNode; } @@ -333,7 +337,7 @@ function selectionSet(): ast.SelectionSetNode { switch (input.charCodeAt(idx)) { case 64 /*'@'*/: selections.push({ - kind: 'InlineFragment' as Kind.INLINE_FRAGMENT, + kind: 'InlineFragment' as Or, typeCondition: undefined, directives: directives(false), selectionSet: selectionSetStart(), @@ -345,9 +349,9 @@ function selectionSet(): ast.SelectionSetNode { idx += 2; ignored(); selections.push({ - kind: 'InlineFragment' as Kind.INLINE_FRAGMENT, + kind: 'InlineFragment' as Or, typeCondition: { - kind: 'NamedType' as Kind.NAMED_TYPE, + kind: 'NamedType' as Or, name: nameNode(), }, directives: directives(false), @@ -355,7 +359,7 @@ function selectionSet(): ast.SelectionSetNode { }); } else { selections.push({ - kind: 'FragmentSpread' as Kind.FRAGMENT_SPREAD, + kind: 'FragmentSpread' as Or, name: nameNode(), directives: directives(false), }); @@ -366,7 +370,7 @@ function selectionSet(): ast.SelectionSetNode { idx++; ignored(); selections.push({ - kind: 'InlineFragment' as Kind.INLINE_FRAGMENT, + kind: 'InlineFragment' as Or, typeCondition: undefined, directives: undefined, selectionSet: selectionSet(), @@ -375,7 +379,7 @@ function selectionSet(): ast.SelectionSetNode { default: selections.push({ - kind: 'FragmentSpread' as Kind.FRAGMENT_SPREAD, + kind: 'FragmentSpread' as Or, name: nameNode(), directives: directives(false), }); @@ -398,7 +402,7 @@ function selectionSet(): ast.SelectionSetNode { _selectionSet = selectionSet(); } selections.push({ - kind: 'Field' as Kind.FIELD, + kind: 'Field' as Or, alias, name, arguments: _arguments, @@ -410,7 +414,7 @@ function selectionSet(): ast.SelectionSetNode { idx++; ignored(); return { - kind: 'SelectionSet' as Kind.SELECTION_SET, + kind: 'SelectionSet' as Or, selections, }; } @@ -439,9 +443,12 @@ function variableDefinitions(): ast.VariableDefinitionNode[] | undefined { } ignored(); const varDef: ast.VariableDefinitionNode = { - kind: 'VariableDefinition' as Kind.VARIABLE_DEFINITION, + kind: 'VariableDefinition' as Or< + GraphQL.Kind.VARIABLE_DEFINITION, + Kind.VARIABLE_DEFINITION + >, variable: { - kind: 'Variable' as Kind.VARIABLE, + kind: 'Variable' as Or, name, }, type: _type, @@ -465,10 +472,10 @@ function fragmentDefinition(description?: ast.StringValueNode): ast.FragmentDefi throw error('FragmentDefinition'); ignored(); const fragDef: ast.FragmentDefinitionNode = { - kind: 'FragmentDefinition' as Kind.FRAGMENT_DEFINITION, + kind: 'FragmentDefinition' as Or, name, typeCondition: { - kind: 'NamedType' as Kind.NAMED_TYPE, + kind: 'NamedType' as Or, name: nameNode(), }, directives: directives(false), @@ -493,7 +500,10 @@ function definitions(): ast.DefinitionNode[] { idx++; ignored(); _definitions.push({ - kind: 'OperationDefinition' as Kind.OPERATION_DEFINITION, + kind: 'OperationDefinition' as Or< + GraphQL.Kind.OPERATION_DEFINITION, + Kind.OPERATION_DEFINITION + >, operation: 'query' as OperationTypeNode.QUERY, name: undefined, variableDefinitions: undefined, @@ -519,7 +529,10 @@ function definitions(): ast.DefinitionNode[] { name = nameNode(); } const opDef: ast.OperationDefinitionNode = { - kind: 'OperationDefinition' as Kind.OPERATION_DEFINITION, + kind: 'OperationDefinition' as Or< + GraphQL.Kind.OPERATION_DEFINITION, + Kind.OPERATION_DEFINITION + >, operation: definition as OperationTypeNode, name, variableDefinitions: variableDefinitions(), @@ -527,7 +540,7 @@ function definitions(): ast.DefinitionNode[] { selectionSet: selectionSetStart(), }; if (_description) { - opDef.description = _description; + (opDef as { description?: StringValueNode }).description = _description; } _definitions.push(opDef); break; @@ -552,12 +565,12 @@ export function parse( ignored(); if (options && options.noLocation) { return { - kind: 'Document' as Kind.DOCUMENT, + kind: 'Document' as Or, definitions: definitions(), }; } else { return { - kind: 'Document' as Kind.DOCUMENT, + kind: 'Document' as Or, definitions: definitions(), loc: { start: 0, diff --git a/vitest.config.ts b/vitest.config.ts index 46034e7..8f759f3 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -14,6 +14,7 @@ export default defineConfig({ '**/__tests__/**', '**/*.d.ts', 'src/ast.ts', + 'src/coordinateAst.ts', 'src/index.ts', 'src/schemaAst.ts', 'src/types.ts', -- knotmirror 0.0.1