Mirror: The spec-compliant minimum of client-side GraphQL.

Add initial utilities

+109 -61
pnpm-lock.yaml
···
lockfileVersion: 5.4
-
specifiers:
-
'@changesets/cli': ^2.26.0
-
'@changesets/get-github-info': ^0.5.2
-
'@rollup/plugin-buble': ^1.0.2
-
'@rollup/plugin-commonjs': ^24.0.1
-
'@rollup/plugin-node-resolve': ^15.0.1
-
'@rollup/plugin-sucrase': ^5.0.1
-
'@rollup/plugin-terser': ^0.4.0
-
'@typescript-eslint/eslint-plugin': ^5.55.0
-
'@typescript-eslint/parser': ^5.55.0
-
eslint: ^8.36.0
-
eslint-config-prettier: ^8.7.0
-
eslint-plugin-prettier: ^4.2.1
-
eslint-plugin-tsdoc: ^0.2.17
-
husky-v4: ^4.3.8
-
lint-staged: ^13.2.0
-
npm-run-all: ^4.1.5
-
prettier: ^2.8.4
-
rimraf: ^4.4.0
-
rollup: ^3.19.1
-
rollup-plugin-cjs-check: ^1.0.2
-
rollup-plugin-dts: ^5.3.0
-
terser: ^5.16.6
-
typescript: ^5.0.2
-
vitest: ^0.29.3
+
importers:
-
devDependencies:
-
'@changesets/cli': 2.26.0
-
'@changesets/get-github-info': 0.5.2
-
'@rollup/plugin-buble': 1.0.2_rollup@3.19.1
-
'@rollup/plugin-commonjs': 24.0.1_rollup@3.19.1
-
'@rollup/plugin-node-resolve': 15.0.1_rollup@3.19.1
-
'@rollup/plugin-sucrase': 5.0.1_rollup@3.19.1
-
'@rollup/plugin-terser': 0.4.0_rollup@3.19.1
-
'@typescript-eslint/eslint-plugin': 5.55.0_qsnvknysi52qtaxqdyqyohkcku
-
'@typescript-eslint/parser': 5.55.0_j4766f7ecgqbon3u7zlxn5zszu
-
eslint: 8.36.0
-
eslint-config-prettier: 8.7.0_eslint@8.36.0
-
eslint-plugin-prettier: 4.2.1_eqzx3hpkgx5nnvxls3azrcc7dm
-
eslint-plugin-tsdoc: 0.2.17
-
husky-v4: 4.3.8
-
lint-staged: 13.2.0
-
npm-run-all: 4.1.5
-
prettier: 2.8.4
-
rimraf: 4.4.0
-
rollup: 3.19.1
-
rollup-plugin-cjs-check: 1.0.2_rollup@3.19.1
-
rollup-plugin-dts: 5.3.0_7iejawhbqmte5pthjozf4tfuqy
-
terser: 5.16.6
-
typescript: 5.0.2
-
vitest: 0.29.3_terser@5.16.6
+
.:
+
specifiers:
+
'@changesets/cli': ^2.26.0
+
'@changesets/get-github-info': ^0.5.2
+
'@rollup/plugin-buble': ^1.0.2
+
'@rollup/plugin-commonjs': ^24.0.1
+
'@rollup/plugin-node-resolve': ^15.0.1
+
'@rollup/plugin-sucrase': ^5.0.1
+
'@rollup/plugin-terser': ^0.4.0
+
'@typescript-eslint/eslint-plugin': ^5.55.0
+
'@typescript-eslint/parser': ^5.55.0
+
eslint: ^8.36.0
+
eslint-config-prettier: ^8.7.0
+
eslint-plugin-prettier: ^4.2.1
+
eslint-plugin-tsdoc: ^0.2.17
+
husky-v4: ^4.3.8
+
lint-staged: ^13.2.0
+
npm-run-all: ^4.1.5
+
prettier: ^2.8.4
+
rimraf: ^4.4.0
+
rollup: ^3.19.1
+
rollup-plugin-cjs-check: ^1.0.2
+
rollup-plugin-dts: ^5.3.0
+
terser: ^5.16.6
+
typescript: ^5.0.2
+
vitest: ^0.29.3
+
devDependencies:
+
'@changesets/cli': 2.26.0
+
'@changesets/get-github-info': 0.5.2
+
'@rollup/plugin-buble': 1.0.2_rollup@3.19.1
+
'@rollup/plugin-commonjs': 24.0.1_rollup@3.19.1
+
'@rollup/plugin-node-resolve': 15.0.1_rollup@3.19.1
+
'@rollup/plugin-sucrase': 5.0.1_rollup@3.19.1
+
'@rollup/plugin-terser': 0.4.0_rollup@3.19.1
+
'@typescript-eslint/eslint-plugin': 5.55.0_qsnvknysi52qtaxqdyqyohkcku
+
'@typescript-eslint/parser': 5.55.0_j4766f7ecgqbon3u7zlxn5zszu
+
eslint: 8.36.0
+
eslint-config-prettier: 8.7.0_eslint@8.36.0
+
eslint-plugin-prettier: 4.2.1_eqzx3hpkgx5nnvxls3azrcc7dm
+
eslint-plugin-tsdoc: 0.2.17
+
husky-v4: 4.3.8
+
lint-staged: 13.2.0
+
npm-run-all: 4.1.5
+
prettier: 2.8.4
+
rimraf: 4.4.0
+
rollup: 3.19.1
+
rollup-plugin-cjs-check: 1.0.2_rollup@3.19.1
+
rollup-plugin-dts: 5.3.0_7iejawhbqmte5pthjozf4tfuqy
+
terser: 5.16.6
+
typescript: 5.0.2
+
vitest: 0.29.3_terser@5.16.6
+
+
benchmark:
+
specifiers:
+
benchr: 4.3.0
+
graphql: ^16.6.0
+
dependencies:
+
benchr: 4.3.0
+
graphql: 16.6.0
packages:
···
/ansi-regex/5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
-
dev: true
/ansi-regex/6.0.1:
resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==}
···
engines: {node: '>=4'}
dependencies:
color-convert: 1.9.3
-
dev: true
/ansi-styles/4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
···
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: true
+
/benchmark/2.1.4:
+
resolution: {integrity: sha512-l9MlfN4M1K/H2fbhfMy3B7vJd6AGKJVQn2h6Sg/Yx+KckoUA7ewS5Vv6TjSq18ooE1kS9hhAlQRH3AkXIh/aOQ==}
+
dependencies:
+
lodash: 4.17.21
+
platform: 1.3.6
+
dev: false
+
+
/benchr/4.3.0:
+
resolution: {integrity: sha512-ZOJkEOLOI4vyf34fnUSmh+xvg8P86cmVfyORBgIJHLfXMuvrfkMsUEThMaJND406KGCigtBO6sMjExGbp+WeLw==}
+
engines: {node: '>=6.0.0'}
+
hasBin: true
+
dependencies:
+
benchmark: 2.1.4
+
chalk: 2.4.2
+
docopt: 0.6.2
+
easy-table: 1.2.0
+
require-all: 2.2.0
+
dev: false
+
/better-path-resolve/1.0.0:
resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==}
engines: {node: '>=4'}
···
ansi-styles: 3.2.1
escape-string-regexp: 1.0.5
supports-color: 5.5.0
-
dev: true
/chalk/4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
···
/clone/1.0.4:
resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
engines: {node: '>=0.8'}
-
dev: true
/color-convert/1.9.3:
resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
dependencies:
color-name: 1.1.3
-
dev: true
/color-convert/2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
···
/color-name/1.1.3:
resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
-
dev: true
/color-name/1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
···
resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==}
dependencies:
clone: 1.0.4
-
dev: true
/define-properties/1.2.0:
resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==}
···
path-type: 4.0.0
dev: true
+
/docopt/0.6.2:
+
resolution: {integrity: sha512-NqTbaYeE4gA/wU1hdKFdU+AFahpDOpgGLzHP42k6H6DKExJd0A55KEVWYhL9FEmHmgeLvEU2vuKXDuU+4yToOw==}
+
engines: {node: '>=0.10.0'}
+
dev: false
+
/doctrine/3.0.0:
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
engines: {node: '>=6.0.0'}
···
/eastasianwidth/0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
dev: true
+
+
/easy-table/1.2.0:
+
resolution: {integrity: sha512-OFzVOv03YpvtcWGe5AayU5G2hgybsg3iqA6drU8UaoZyB9jLGMTrz9+asnLp/E+6qPh88yEI1gvyZFZ41dmgww==}
+
dependencies:
+
ansi-regex: 5.0.1
+
optionalDependencies:
+
wcwidth: 1.0.1
+
dev: false
/emoji-regex/8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
···
/escape-string-regexp/1.0.5:
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
engines: {node: '>=0.8.0'}
-
dev: true
/escape-string-regexp/4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
···
/grapheme-splitter/1.0.4:
resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
dev: true
+
+
/graphql/16.6.0:
+
resolution: {integrity: sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==}
+
engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0}
+
dev: false
/hard-rejection/2.1.0:
resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==}
···
/has-flag/3.0.0:
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
engines: {node: '>=4'}
-
dev: true
/has-flag/4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
···
/lodash.startcase/4.4.0:
resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==}
dev: true
+
+
/lodash/4.17.21:
+
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+
dev: false
/log-update/4.0.0:
resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==}
···
pathe: 1.1.0
dev: true
+
/platform/1.3.6:
+
resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==}
+
dev: false
+
/please-upgrade-node/3.2.0:
resolution: {integrity: sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==}
dependencies:
···
jsesc: 0.5.0
dev: true
+
/require-all/2.2.0:
+
resolution: {integrity: sha512-YWj/WNCxs+KxppuN3j11Ztqzl8MI/oWj4ERwEwgJ5gsHzWi8OAK7FepPu8MLv/Rn8Pov6aPdpRkaoO2Tb6m+zQ==}
+
dev: false
+
/require-directory/2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
···
engines: {node: '>=4'}
dependencies:
has-flag: 3.0.0
-
dev: true
/supports-color/7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
···
resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
dependencies:
defaults: 1.0.4
-
dev: true
/webidl-conversions/3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
+251
src/ast.ts
···
+
import type { Kind } from './kind';
+
+
export interface Location {
+
readonly start: number;
+
readonly end: number;
+
readonly source: Source;
+
}
+
+
export interface Source {
+
body: string;
+
name: string;
+
locationOffset: {
+
line: number;
+
column: number;
+
};
+
}
+
+
export declare type ASTNode =
+
| NameNode
+
| DocumentNode
+
| OperationDefinitionNode
+
| VariableDefinitionNode
+
| VariableNode
+
| SelectionSetNode
+
| FieldNode
+
| ArgumentNode
+
| FragmentSpreadNode
+
| InlineFragmentNode
+
| FragmentDefinitionNode
+
| IntValueNode
+
| FloatValueNode
+
| StringValueNode
+
| BooleanValueNode
+
| NullValueNode
+
| EnumValueNode
+
| ListValueNode
+
| ObjectValueNode
+
| ObjectFieldNode
+
| DirectiveNode
+
| NamedTypeNode
+
| ListTypeNode
+
| NonNullTypeNode;
+
+
export interface NameNode {
+
readonly kind: Kind.NAME;
+
readonly value: string;
+
}
+
+
export interface DocumentNode {
+
readonly kind: Kind.DOCUMENT;
+
readonly definitions: ReadonlyArray<ExecutableDefinitionNode>;
+
readonly loc?: Location;
+
}
+
+
export type DefinitionNode =
+
| OperationDefinitionNode
+
| FragmentDefinitionNode;
+
export type ExecutableDefinitionNode =
+
| OperationDefinitionNode
+
| FragmentDefinitionNode;
+
+
export interface OperationDefinitionNode {
+
readonly kind: Kind.OPERATION_DEFINITION;
+
readonly operation: OperationTypeNode;
+
readonly name?: NameNode;
+
readonly variableDefinitions?: ReadonlyArray<VariableDefinitionNode>;
+
readonly directives?: ReadonlyArray<DirectiveNode>;
+
readonly selectionSet: SelectionSetNode;
+
}
+
+
export const enum OperationTypeNode {
+
QUERY = 'query',
+
MUTATION = 'mutation',
+
SUBSCRIPTION = 'subscription',
+
}
+
+
export interface VariableDefinitionNode {
+
readonly kind: Kind.VARIABLE_DEFINITION;
+
readonly variable: VariableNode;
+
readonly type: TypeNode;
+
readonly defaultValue?: ConstValueNode;
+
readonly directives?: ReadonlyArray<ConstDirectiveNode>;
+
}
+
+
export interface VariableNode {
+
readonly kind: Kind.VARIABLE;
+
readonly name: NameNode;
+
}
+
+
export interface SelectionSetNode {
+
kind: Kind.SELECTION_SET;
+
selections: ReadonlyArray<SelectionNode>;
+
}
+
+
export declare type SelectionNode =
+
| FieldNode
+
| FragmentSpreadNode
+
| InlineFragmentNode;
+
+
export interface FieldNode {
+
readonly kind: Kind.FIELD;
+
readonly alias?: NameNode;
+
readonly name: NameNode;
+
readonly arguments?: ReadonlyArray<ArgumentNode>;
+
readonly directives?: ReadonlyArray<DirectiveNode>;
+
readonly selectionSet?: SelectionSetNode;
+
}
+
+
export interface ArgumentNode {
+
readonly kind: Kind.ARGUMENT;
+
readonly name: NameNode;
+
readonly value: ValueNode;
+
}
+
+
export interface ConstArgumentNode {
+
readonly kind: Kind.ARGUMENT;
+
readonly name: NameNode;
+
readonly value: ConstValueNode;
+
}
+
+
export interface FragmentSpreadNode {
+
readonly kind: Kind.FRAGMENT_SPREAD;
+
readonly name: NameNode;
+
readonly directives?: ReadonlyArray<DirectiveNode>;
+
}
+
+
export interface InlineFragmentNode {
+
readonly kind: Kind.INLINE_FRAGMENT;
+
readonly typeCondition?: NamedTypeNode;
+
readonly directives?: ReadonlyArray<DirectiveNode>;
+
readonly selectionSet: SelectionSetNode;
+
}
+
+
export interface FragmentDefinitionNode {
+
readonly kind: Kind.FRAGMENT_DEFINITION;
+
readonly name: NameNode;
+
readonly typeCondition: NamedTypeNode;
+
readonly directives?: ReadonlyArray<DirectiveNode>;
+
readonly selectionSet: SelectionSetNode;
+
}
+
+
export type ValueNode =
+
| VariableNode
+
| IntValueNode
+
| FloatValueNode
+
| StringValueNode
+
| BooleanValueNode
+
| NullValueNode
+
| EnumValueNode
+
| ListValueNode
+
| ObjectValueNode;
+
+
export type ConstValueNode =
+
| IntValueNode
+
| FloatValueNode
+
| StringValueNode
+
| BooleanValueNode
+
| NullValueNode
+
| EnumValueNode
+
| ConstListValueNode
+
| ConstObjectValueNode;
+
+
export interface IntValueNode {
+
readonly kind: Kind.INT;
+
readonly value: string;
+
}
+
+
export interface FloatValueNode {
+
readonly kind: Kind.FLOAT;
+
readonly value: string;
+
}
+
export interface StringValueNode {
+
readonly kind: Kind.STRING;
+
readonly value: string;
+
readonly block?: boolean;
+
}
+
+
export interface BooleanValueNode {
+
readonly kind: Kind.BOOLEAN;
+
readonly value: boolean;
+
}
+
+
export interface NullValueNode {
+
readonly kind: Kind.NULL;
+
}
+
+
export interface EnumValueNode {
+
readonly kind: Kind.ENUM;
+
readonly value: string;
+
}
+
+
export interface ListValueNode {
+
readonly kind: Kind.LIST;
+
readonly values: ReadonlyArray<ValueNode>;
+
}
+
+
export interface ConstListValueNode {
+
readonly kind: Kind.LIST;
+
readonly values: ReadonlyArray<ConstValueNode>;
+
}
+
+
export interface ObjectValueNode {
+
readonly kind: Kind.OBJECT;
+
readonly fields: ReadonlyArray<ObjectFieldNode>;
+
}
+
+
export interface ConstObjectValueNode {
+
readonly kind: Kind.OBJECT;
+
readonly fields: ReadonlyArray<ConstObjectFieldNode>;
+
}
+
+
export interface ObjectFieldNode {
+
readonly kind: Kind.OBJECT_FIELD;
+
readonly name: NameNode;
+
readonly value: ValueNode;
+
}
+
+
export interface ConstObjectFieldNode {
+
readonly kind: Kind.OBJECT_FIELD;
+
readonly name: NameNode;
+
readonly value: ConstValueNode;
+
}
+
+
export interface DirectiveNode {
+
readonly kind: Kind.DIRECTIVE;
+
readonly name: NameNode;
+
readonly arguments?: ReadonlyArray<ArgumentNode>;
+
}
+
+
export interface ConstDirectiveNode {
+
readonly kind: Kind.DIRECTIVE;
+
readonly name: NameNode;
+
readonly arguments?: ReadonlyArray<ConstArgumentNode>;
+
}
+
+
export declare type TypeNode = NamedTypeNode | ListTypeNode | NonNullTypeNode;
+
+
export interface NamedTypeNode {
+
readonly kind: Kind.NAMED_TYPE;
+
readonly name: NameNode;
+
}
+
+
export interface ListTypeNode {
+
readonly kind: Kind.LIST_TYPE;
+
readonly type: TypeNode;
+
}
+
+
export interface NonNullTypeNode {
+
readonly kind: Kind.NON_NULL_TYPE;
+
readonly type: NamedTypeNode | ListTypeNode;
+
}
+51
src/error.ts
···
+
import { Maybe, Extensions } from './types';
+
import { ASTNode, Source } from './ast';
+
+
export class GraphQLError extends Error {
+
readonly locations: undefined;
+
readonly path: ReadonlyArray<string | number> | undefined;
+
readonly nodes: ReadonlyArray<ASTNode> | undefined;
+
readonly source: Source | undefined;
+
readonly positions: ReadonlyArray<number> | undefined;
+
readonly originalError: Error | undefined;
+
readonly extensions: Extensions;
+
+
constructor(
+
message: string,
+
nodes?: ReadonlyArray<ASTNode> | ASTNode | null,
+
source?: Maybe<Source>,
+
positions?: Maybe<ReadonlyArray<number>>,
+
path?: Maybe<ReadonlyArray<string | number>>,
+
originalError?: Maybe<Error>,
+
extensions?: Maybe<Extensions>,
+
) {
+
super(message);
+
+
this.name = 'GraphQLError';
+
this.message = message;
+
+
if (path) this.path = path;
+
if (nodes) this.nodes = (Array.isArray(nodes) ? nodes : [nodes]) as ASTNode[];
+
if (source) this.source = source;
+
if (positions) this.positions = positions;
+
if (originalError) this.originalError = originalError;
+
+
let _extensions = extensions;
+
if (!_extensions && originalError) {
+
const originalExtensions = (originalError as any).extensions;
+
if (originalExtensions && typeof originalExtensions === 'object') {
+
_extensions = originalExtensions;
+
}
+
}
+
+
this.extensions = extensions || {};
+
}
+
+
toJSON() {
+
return { ...this };
+
}
+
+
toString() {
+
return this.message;
+
}
+
}
+6
src/index.ts
···
+
export * from './ast';
+
export * from './kind';
+
export * from './error';
+
export * from './parser';
+
export * from './visitor';
+
export * from './printer';
+32
src/kind.ts
···
+
export enum Kind {
+
/** Name */
+
NAME = 'Name',
+
/** Document */
+
DOCUMENT = 'Document',
+
OPERATION_DEFINITION = 'OperationDefinition',
+
VARIABLE_DEFINITION = 'VariableDefinition',
+
SELECTION_SET = 'SelectionSet',
+
FIELD = 'Field',
+
ARGUMENT = 'Argument',
+
/** Fragments */
+
FRAGMENT_SPREAD = 'FragmentSpread',
+
INLINE_FRAGMENT = 'InlineFragment',
+
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',
+
/** Directives */
+
DIRECTIVE = 'Directive',
+
/** Types */
+
NAMED_TYPE = 'NamedType',
+
LIST_TYPE = 'ListType',
+
NON_NULL_TYPE = 'NonNullType',
+
}
+480
src/parser.ts
···
+
/**
+
* This is a spec-compliant implementation of a GraphQL query language parser,
+
* up-to-date with the October 2021 Edition. Unlike the reference implementation
+
* in graphql.js it will only parse the query language, but not the schema
+
* language.
+
*/
+
import { Kind } from './kind';
+
import { GraphQLError } from './error';
+
import type * as ast from './ast';
+
+
let input: string;
+
let idx: number;
+
+
function error(kind: Kind) {
+
return new GraphQLError(`Syntax Error: Unexpected token at ${idx} in ${kind}`);
+
}
+
+
function advance(pattern: RegExp) {
+
pattern.lastIndex = idx;
+
if (pattern.test(input)) {
+
const match = input.slice(idx, idx = pattern.lastIndex);
+
return match;
+
}
+
}
+
+
const leadingRe = / +(?=[^\s])/y;
+
export function blockString(string: string) {
+
let out = '';
+
let commonIndent = 0;
+
let firstNonEmptyLine = 0;
+
let lastNonEmptyLine = -1;
+
const lines = string.split('\n');
+
for (let i = 0; i < lines.length; i++) {
+
leadingRe.lastIndex = 0;
+
if (leadingRe.test(lines[i])) {
+
if (i && (!commonIndent || leadingRe.lastIndex < commonIndent))
+
commonIndent = leadingRe.lastIndex;
+
firstNonEmptyLine = firstNonEmptyLine || i;
+
lastNonEmptyLine = i;
+
}
+
}
+
for (let i = firstNonEmptyLine; i <= lastNonEmptyLine; i++) {
+
if (i !== firstNonEmptyLine) out += '\n';
+
out += lines[i].slice(commonIndent);
+
}
+
return out;
+
}
+
+
const ignoredRe = /(?:[\s,]*|#[^\n\r]*)*/y;
+
function ignored() {
+
ignoredRe.lastIndex = idx;
+
ignoredRe.test(input);
+
idx = ignoredRe.lastIndex;
+
}
+
+
const nameRe = /[_\w][_\d\w]*/y;
+
function name(): ast.NameNode | undefined {
+
let match: string | undefined;
+
if (match = advance(nameRe)) {
+
return {
+
kind: Kind.NAME,
+
value: match,
+
};
+
}
+
}
+
+
const nullRe = /null/y;
+
const boolRe = /true|false/y;
+
const variableRe = /\$[_\w][_\d\w]*/y;
+
const intRe = /[-]?\d+/y;
+
const floatRe = /[-]?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/y;
+
const complexStringRe = /\\/g;
+
const blockStringRe = /"""(?:[\s\S]+(?="""))?"""/y;
+
const stringRe = /"(?:[^"\r\n]+)?"/y;
+
+
function value(constant: true): ast.ConstValueNode;
+
function value(constant: boolean): ast.ValueNode;
+
+
function value(constant: boolean): ast.ValueNode | undefined {
+
let out: ast.ValueNode | undefined;
+
let match: string | undefined;
+
if (advance(nullRe)) {
+
out = { kind: Kind.NULL };
+
} else if (match = advance(boolRe)) {
+
out = {
+
kind: Kind.BOOLEAN,
+
value: match === 'true',
+
};
+
} else if (!constant && (match = advance(variableRe))) {
+
out = {
+
kind: Kind.VARIABLE,
+
name: {
+
kind: Kind.NAME,
+
value: match.slice(1),
+
},
+
};
+
} else if (match = advance(floatRe)) {
+
out = {
+
kind: Kind.FLOAT,
+
value: match,
+
};
+
} else if (match = advance(intRe)) {
+
out = {
+
kind: Kind.INT,
+
value: match,
+
};
+
} else if (match = advance(nameRe)) {
+
out = {
+
kind: Kind.ENUM,
+
value: match,
+
};
+
} else if (match = advance(blockStringRe)) {
+
out = {
+
kind: Kind.STRING,
+
value: blockString(match.slice(3, -3)),
+
block: true,
+
};
+
} else if (match = advance(stringRe)) {
+
out = {
+
kind: Kind.STRING,
+
value: complexStringRe.test(match)
+
? JSON.parse(match) as string
+
: match.slice(1, -1),
+
block: false,
+
};
+
} else if (out = list(constant) || object(constant)) {
+
return out;
+
}
+
+
ignored();
+
return out;
+
}
+
+
function list(constant: boolean): ast.ListValueNode | undefined {
+
let match: ast.ValueNode | undefined;
+
if (input.charCodeAt(idx) === 91 /*'['*/) {
+
idx++;
+
ignored();
+
const values: ast.ValueNode[] = [];
+
while (match = value(constant))
+
values.push(match);
+
if (input.charCodeAt(idx++) !== 93 /*']'*/)
+
throw error(Kind.LIST);
+
ignored();
+
return {
+
kind: Kind.LIST,
+
values,
+
};
+
}
+
}
+
+
function object(constant: boolean): ast.ObjectValueNode | undefined {
+
if (input.charCodeAt(idx) === 123 /*'{'*/) {
+
idx++;
+
ignored();
+
const fields: ast.ObjectFieldNode[] = [];
+
let _name: ast.NameNode | undefined;
+
while (_name = name()) {
+
ignored();
+
if (input.charCodeAt(idx++) !== 58 /*':'*/)
+
throw error(Kind.OBJECT_FIELD);
+
ignored();
+
const _value = value(constant);
+
if (!_value)
+
throw error(Kind.OBJECT_FIELD);
+
fields.push({
+
kind: Kind.OBJECT_FIELD,
+
name: _name,
+
value: _value,
+
});
+
}
+
if (input.charCodeAt(idx++) !== 125 /*'}'*/)
+
throw error(Kind.OBJECT);
+
ignored();
+
return {
+
kind: Kind.OBJECT,
+
fields,
+
};
+
}
+
}
+
+
function arguments_(constant: boolean): ast.ArgumentNode[] {
+
const args: ast.ArgumentNode[] = [];
+
ignored();
+
if (input.charCodeAt(idx) === 40 /*'('*/) {
+
idx++;
+
ignored();
+
let _name: ast.NameNode | undefined;
+
while (_name = name()) {
+
ignored();
+
if (input.charCodeAt(idx++) !== 58 /*':'*/)
+
throw error(Kind.ARGUMENT);
+
ignored();
+
const _value = value(constant);
+
if (!_value)
+
throw error(Kind.ARGUMENT);
+
args.push({
+
kind: Kind.ARGUMENT,
+
name: _name,
+
value: _value,
+
});
+
}
+
if (!args.length || input.charCodeAt(idx++) !== 41 /*')'*/)
+
throw error(Kind.ARGUMENT);
+
ignored();
+
}
+
return args;
+
}
+
+
function directives(constant: true): ast.ConstDirectiveNode[];
+
function directives(constant: boolean): ast.DirectiveNode[];
+
+
function directives(constant: boolean): ast.DirectiveNode[] {
+
const directives: ast.DirectiveNode[] = [];
+
ignored();
+
while (input.charCodeAt(idx) === 64 /*'@'*/) {
+
idx++;
+
const _name = name();
+
if (!_name)
+
throw error(Kind.DIRECTIVE);
+
ignored();
+
directives.push({
+
kind: Kind.DIRECTIVE,
+
name: _name,
+
arguments: arguments_(constant),
+
});
+
}
+
return directives;
+
}
+
+
function field(): ast.FieldNode | undefined {
+
ignored();
+
let _name = name();
+
if (_name) {
+
ignored();
+
let _alias: ast.NameNode | undefined;
+
if (input.charCodeAt(idx) === 58 /*':'*/) {
+
idx++;
+
ignored();
+
_alias = _name;
+
_name = name();
+
if (!_name)
+
throw error(Kind.FIELD);
+
ignored();
+
}
+
return {
+
kind: Kind.FIELD,
+
alias: _alias,
+
name: _name,
+
arguments: arguments_(false),
+
directives: directives(false),
+
selectionSet: selectionSet(),
+
};
+
}
+
}
+
+
function type(): ast.TypeNode | undefined {
+
let match: ast.NameNode | ast.TypeNode | undefined;
+
ignored();
+
if (input.charCodeAt(idx) === 91 /*'['*/) {
+
idx++;
+
ignored();
+
const _type = type();
+
if (!_type || input.charCodeAt(idx++) !== 93 /*']'*/)
+
throw error(Kind.LIST_TYPE);
+
match = {
+
kind: Kind.LIST_TYPE,
+
type: _type,
+
};
+
} else if (match = name()) {
+
match = {
+
kind: Kind.NAMED_TYPE,
+
name: match,
+
};
+
} else {
+
throw error(Kind.NAMED_TYPE);
+
}
+
+
ignored();
+
if (input.charCodeAt(idx) === 33 /*'!'*/) {
+
idx++;
+
ignored();
+
return {
+
kind: Kind.NON_NULL_TYPE,
+
type: match,
+
};
+
} else {
+
return match;
+
}
+
}
+
+
const typeConditionRe = /on/y;
+
function typeCondition(): ast.NamedTypeNode | undefined {
+
if (advance(typeConditionRe)) {
+
ignored();
+
const _name = name();
+
if (!_name)
+
throw error(Kind.NAMED_TYPE);
+
ignored();
+
return {
+
kind: Kind.NAMED_TYPE,
+
name: _name,
+
};
+
}
+
}
+
+
const fragmentSpreadRe = /\.\.\./y;
+
+
function fragmentSpread(): ast.FragmentSpreadNode | ast.InlineFragmentNode | undefined {
+
ignored();
+
if (advance(fragmentSpreadRe)) {
+
ignored();
+
let _name: ast.NameNode | undefined;
+
if (_name = name()) {
+
return {
+
kind: Kind.FRAGMENT_SPREAD,
+
name: _name,
+
directives: directives(false),
+
};
+
} else {
+
const _typeCondition = typeCondition();
+
const _directives = directives(false);
+
const _selectionSet = selectionSet();
+
if (!_selectionSet)
+
throw error(Kind.INLINE_FRAGMENT);
+
return {
+
kind: Kind.INLINE_FRAGMENT,
+
typeCondition: _typeCondition,
+
directives: _directives,
+
selectionSet: _selectionSet,
+
};
+
}
+
}
+
}
+
+
function selectionSet(): ast.SelectionSetNode | undefined {
+
let match: ast.SelectionNode | undefined;
+
ignored();
+
if (input.charCodeAt(idx) === 123 /*'{'*/) {
+
idx++;
+
ignored();
+
const selections: ast.SelectionNode[] = [];
+
while (match = fragmentSpread() || field())
+
selections.push(match);
+
if (!selections.length || input.charCodeAt(idx++) !== 125 /*'}'*/)
+
throw error(Kind.SELECTION_SET);
+
ignored();
+
return {
+
kind: Kind.SELECTION_SET,
+
selections,
+
};
+
}
+
}
+
+
function variableDefinitions(): ast.VariableDefinitionNode[] {
+
let match: string | undefined;
+
const vars: ast.VariableDefinitionNode[] = [];
+
ignored();
+
if (input.charCodeAt(idx) === 40 /*'('*/) {
+
idx++;
+
ignored();
+
while (match = advance(variableRe)) {
+
ignored();
+
if (input.charCodeAt(idx++) !== 58 /*':'*/)
+
throw error(Kind.VARIABLE_DEFINITION);
+
const _type = type();
+
if (!_type)
+
throw error(Kind.VARIABLE_DEFINITION);
+
let _defaultValue: ast.ValueNode | undefined;
+
if (input.charCodeAt(idx) === 61 /*'='*/) {
+
idx++;
+
ignored();
+
_defaultValue = value(true);
+
if (!_defaultValue)
+
throw error(Kind.VARIABLE_DEFINITION);
+
}
+
ignored();
+
vars.push({
+
kind: Kind.VARIABLE_DEFINITION,
+
variable: {
+
kind: Kind.VARIABLE,
+
name: {
+
kind: Kind.NAME,
+
value: match.slice(1),
+
},
+
},
+
type: _type,
+
defaultValue: _defaultValue as ast.ConstValueNode,
+
directives: directives(true),
+
});
+
}
+
if (input.charCodeAt(idx++) !== 41 /*')'*/)
+
throw error(Kind.VARIABLE_DEFINITION);
+
ignored();
+
}
+
return vars;
+
}
+
+
const fragmentDefinitionRe = /fragment/y;
+
function fragmentDefinition(): ast.FragmentDefinitionNode | undefined {
+
if (advance(fragmentDefinitionRe)) {
+
ignored();
+
const _name = name();
+
if (!_name)
+
throw error(Kind.FRAGMENT_DEFINITION);
+
ignored();
+
const _typeCondition = typeCondition();
+
if (!_typeCondition)
+
throw error(Kind.FRAGMENT_DEFINITION);
+
const _directives = directives(false);
+
const _selectionSet = selectionSet();
+
if (!_selectionSet)
+
throw error(Kind.FRAGMENT_DEFINITION);
+
return {
+
kind: Kind.FRAGMENT_DEFINITION,
+
name: _name,
+
typeCondition: _typeCondition,
+
directives: _directives,
+
selectionSet: _selectionSet,
+
};
+
}
+
}
+
+
const operationDefinitionRe = /query|mutation|subscription/y;
+
function operationDefinition(): ast.OperationDefinitionNode | undefined {
+
let _operation: string | undefined;
+
let _name: ast.NameNode | undefined;
+
let _variableDefinitions: ast.VariableDefinitionNode[] = [];
+
let _directives: ast.DirectiveNode[] = [];
+
if (_operation = advance(operationDefinitionRe)) {
+
ignored();
+
_name = name();
+
_variableDefinitions = variableDefinitions();
+
_directives = directives(false);
+
}
+
const _selectionSet = selectionSet();
+
if (_selectionSet) {
+
return {
+
kind: Kind.OPERATION_DEFINITION,
+
operation: (_operation || 'query') as ast.OperationTypeNode,
+
name: _name,
+
variableDefinitions: _variableDefinitions,
+
directives: _directives,
+
selectionSet: _selectionSet,
+
};
+
}
+
}
+
+
function document(): ast.DocumentNode {
+
let match: ast.DefinitionNode | void;
+
ignored();
+
const definitions: ast.DefinitionNode[] = [];
+
while (match = fragmentDefinition() || operationDefinition())
+
definitions.push(match);
+
if (idx !== input.length)
+
throw error(Kind.DOCUMENT);
+
return {
+
kind: Kind.DOCUMENT,
+
definitions,
+
};
+
}
+
+
export function parse(string: string): ast.DocumentNode {
+
input = string;
+
idx = 0;
+
return document();
+
}
+
+
export function parseValue(string: string): ast.ValueNode | undefined {
+
input = string;
+
idx = 0;
+
ignored();
+
return value(false);
+
}
+
+
export function parseType(string: string): ast.TypeNode | undefined {
+
input = string;
+
idx = 0;
+
return type();
+
}
+132
src/printer.ts
···
+
import { Kind } from './kind';
+
import { ASTNode } from './ast';
+
+
export function printString(string: string) {
+
return JSON.stringify(string);
+
}
+
+
export function printBlockString(string: string) {
+
return '"""\n' + string.replace(/"""/g, '\\"""') + '\n"""';
+
}
+
+
const hasItems = <T>(
+
array: ReadonlyArray<T> | undefined | null
+
): array is ReadonlyArray<T> => !!(array && array.length);
+
+
export function print(node: ASTNode): string {
+
let out: string;
+
switch (node.kind) {
+
case Kind.OPERATION_DEFINITION:
+
if (node.operation === 'query' && !node.name && !hasItems(node.variableDefinitions) && !hasItems(node.directives)) {
+
return print(node.selectionSet);
+
}
+
out = node.operation;
+
if (node.name)
+
out += ' ' + node.name.value;
+
if (hasItems(node.variableDefinitions)) {
+
if (!node.name) out += ' ';
+
out += '(' + node.variableDefinitions.map(print).join(', ') + ')';
+
}
+
if (hasItems(node.directives))
+
out += ' ' + node.directives.map(print).join(' ');
+
return out + ' ' + print(node.selectionSet);
+
+
case Kind.VARIABLE_DEFINITION:
+
out = print(node.variable) +
+
': ' +
+
print(node.type);
+
if (node.defaultValue)
+
out += ' = ' + print(node.defaultValue);
+
if (hasItems(node.directives))
+
out += ' ' + node.directives.map(print).join(' ');
+
return out;
+
+
case Kind.FIELD:
+
out = (node.alias ? print(node.alias) + ': ' : '') + node.name.value
+
if (hasItems(node.arguments))
+
out += '(' + node.arguments.map(print).join(', ') + ')';
+
if (hasItems(node.directives))
+
out += ' ' + node.directives.map(print).join(' ');
+
return node.selectionSet
+
? out + ' ' + print(node.selectionSet)
+
: out;
+
+
case Kind.STRING:
+
return node.block
+
? printBlockString(node.value)
+
: printString(node.value);
+
+
case Kind.BOOLEAN:
+
return '' + node.value;
+
+
case Kind.NULL:
+
return 'null';
+
+
case Kind.INT:
+
case Kind.FLOAT:
+
case Kind.ENUM:
+
case Kind.NAME:
+
return node.value;
+
+
case Kind.LIST:
+
return '[' + node.values.map(print).join(', ') + ']';
+
+
case Kind.OBJECT:
+
return '{' + node.fields.map(print).join(', ') + '}';
+
+
case Kind.OBJECT_FIELD:
+
return node.name.value + ': ' + print(node.value);
+
+
case Kind.VARIABLE:
+
return '$' + node.name.value;
+
+
case Kind.DOCUMENT:
+
return hasItems(node.definitions)
+
? node.definitions.map(print).join('\n\n')
+
: '';
+
+
case Kind.SELECTION_SET:
+
return '{\n ' +
+
node.selections.map(print).join('\n').replace(/\n/g, '\n ') +
+
'\n}';
+
+
case Kind.ARGUMENT:
+
return node.name.value + ': ' + print(node.value);
+
+
case Kind.FRAGMENT_SPREAD:
+
out = '...' + node.name.value;
+
if (hasItems(node.directives))
+
out += ' ' + node.directives.map(print).join(' ');
+
return out;
+
+
case Kind.INLINE_FRAGMENT:
+
out = '...';
+
if (node.typeCondition)
+
out += ' on ' + node.typeCondition.name.value;
+
if (hasItems(node.directives))
+
out += ' ' + node.directives.map(print).join(' ');
+
return out + ' ' + print(node.selectionSet);
+
+
case Kind.FRAGMENT_DEFINITION:
+
out = 'fragment ' + node.name.value;
+
out += ' on ' + node.typeCondition.name.value;
+
if (hasItems(node.directives))
+
out += ' ' + node.directives.map(print).join(' ');
+
return out + ' ' + print(node.selectionSet);
+
+
case Kind.DIRECTIVE:
+
out = '@' + node.name.value;
+
if (hasItems(node.arguments))
+
out += '(' + node.arguments.map(print).join(', ') + ')';
+
return out;
+
+
case Kind.NAMED_TYPE:
+
return node.name.value;
+
+
case Kind.LIST_TYPE:
+
return '[' + print(node.type) + ']';
+
+
case Kind.NON_NULL_TYPE:
+
return print(node.type) + '!';
+
}
+
}
+5
src/types.ts
···
+
export type Maybe<T> = T | undefined | null;
+
+
export interface Extensions {
+
[extension: string]: unknown;
+
}
+136
src/visitor.ts
···
+
import { ASTNode } from './ast';
+
+
export const BREAK = {};
+
+
export function visit<N extends ASTNode>(root: N, visitor: ASTVisitor): N;
+
export function visit<R>(root: ASTNode, visitor: ASTReducer<R>): R;
+
+
export function visit(node: ASTNode, visitor: ASTVisitor | ASTReducer<any>) {
+
const ancestors: Array<ASTNode | ReadonlyArray<ASTNode>>= [];
+
const path: Array<string | number> = [];
+
+
function traverse(
+
node: ASTNode,
+
key?: string | number | undefined,
+
parent?: ASTNode | ReadonlyArray<ASTNode> | undefined,
+
) {
+
let hasEdited = false;
+
+
const enter = visitor[node.kind] && visitor[node.kind].enter || visitor[node.kind];
+
const resultEnter =
+
enter && enter.call(visitor, node, key, parent, path, ancestors);
+
if (resultEnter === false) {
+
return node;
+
} else if (resultEnter === null) {
+
return null;
+
} else if (resultEnter === BREAK) {
+
throw BREAK;
+
} else if (resultEnter && typeof resultEnter.kind === 'string') {
+
hasEdited = resultEnter !== node;
+
node = resultEnter;
+
}
+
+
if (parent) ancestors.push(parent);
+
+
let result: any;
+
const copy = { ...node };
+
for (const nodeKey in node) {
+
path.push(nodeKey);
+
let value = node[nodeKey];
+
if (Array.isArray(value)) {
+
const newValue: any[] = [];
+
for (let index = 0; index < value.length; index++) {
+
if (value[index] != null && typeof value[index].kind === 'string') {
+
ancestors.push(node);
+
path.push(index);
+
result = traverse(value[index], index, value);
+
path.pop();
+
ancestors.pop();
+
if (result === undefined) {
+
newValue.push(value[index]);
+
} else if (result === null) {
+
hasEdited = true;
+
} else {
+
hasEdited = hasEdited || result !== value[index];
+
newValue.push(result);
+
}
+
}
+
}
+
value = newValue;
+
} else if (value != null && typeof value.kind === 'string') {
+
result = traverse(value, nodeKey, node);
+
if (result !== undefined) {
+
hasEdited = hasEdited || value !== result;
+
value = result;
+
}
+
}
+
+
path.pop();
+
if (hasEdited) copy[nodeKey] = value;
+
}
+
+
if (parent) ancestors.pop();
+
const leave = visitor[node.kind] && visitor[node.kind].leave;
+
const resultLeave =
+
leave && leave.call(visitor, node, key, parent, path, ancestors);
+
if (resultLeave === BREAK) {
+
throw BREAK;
+
} else if (resultLeave !== undefined) {
+
return resultLeave;
+
} else if (resultEnter !== undefined) {
+
return hasEdited ? copy : resultEnter;
+
} else {
+
return hasEdited ? copy : node;
+
}
+
}
+
+
try {
+
const result = traverse(node);
+
return result !== undefined && result !== false ? result : node;
+
} catch (error) {
+
if (error !== BREAK) throw error;
+
return node;
+
}
+
}
+
+
export type ASTVisitor = EnterLeaveVisitor<ASTNode> | KindVisitor;
+
+
type KindVisitor = {
+
readonly [NodeT in ASTNode as NodeT['kind']]?:
+
| ASTVisitFn<NodeT>
+
| EnterLeaveVisitor<NodeT>;
+
};
+
+
interface EnterLeaveVisitor<TVisitedNode extends ASTNode> {
+
readonly enter?: ASTVisitFn<TVisitedNode> | undefined;
+
readonly leave?: ASTVisitFn<TVisitedNode> | undefined;
+
}
+
+
export type ASTVisitFn<Node extends ASTNode> = (
+
node: Node,
+
key: string | number | undefined,
+
parent: ASTNode | ReadonlyArray<ASTNode> | undefined,
+
path: ReadonlyArray<string | number>,
+
ancestors: ReadonlyArray<ASTNode | ReadonlyArray<ASTNode>>,
+
) => any;
+
+
export type ASTReducer<R> = {
+
readonly [NodeT in ASTNode as NodeT['kind']]?: {
+
readonly enter?: ASTVisitFn<NodeT>;
+
readonly leave: ASTReducerFn<NodeT, R>;
+
};
+
};
+
+
type ASTReducerFn<TReducedNode extends ASTNode, R> = (
+
node: { [K in keyof TReducedNode]: ReducedField<TReducedNode[K], R> },
+
key: string | number | undefined,
+
parent: ASTNode | ReadonlyArray<ASTNode> | undefined,
+
path: ReadonlyArray<string | number>,
+
ancestors: ReadonlyArray<ASTNode | ReadonlyArray<ASTNode>>,
+
) => R;
+
+
type ReducedField<T, R> = T extends null | undefined
+
? T
+
: T extends ReadonlyArray<any>
+
? ReadonlyArray<R>
+
: R;