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

Fix up divergences from graphql.js

+2 -2
package.json
···
{
"name": "@0no-co/graphql.web",
"description": "A spec-compliant client-side GraphQL implementation",
-
"version": "0.1.1",
"author": "0no.co <hi@0no.co>",
"source": "./src/index.ts",
"main": "./dist/graphql.web",
···
"client-side graphql"
],
"scripts": {
-
"test": "vitest run",
"check": "tsc",
"lint": "eslint --ext=js,ts .",
"build": "rollup -c scripts/rollup.config.mjs",
···
{
"name": "@0no-co/graphql.web",
"description": "A spec-compliant client-side GraphQL implementation",
+
"version": "0.1.2",
"author": "0no.co <hi@0no.co>",
"source": "./src/index.ts",
"main": "./dist/graphql.web",
···
"client-side graphql"
],
"scripts": {
+
"test": "vitest",
"check": "tsc",
"lint": "eslint --ext=js,ts .",
"build": "rollup -c scripts/rollup.config.mjs",
+15 -22
src/__tests__/__snapshots__/parser.test.ts.snap
···
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-
exports[`parse > parses the kitchen sink query 1`] = `
{
"definitions": [
{
···
"kind": "ListValue",
"values": [
{
-
"kind": "FloatValue",
"value": "123",
},
{
-
"kind": "FloatValue",
"value": "456",
},
],
···
"selectionSet": undefined,
},
{
-
"directives": [],
-
"kind": "FragmentSpread",
-
"name": {
-
"kind": "Name",
-
"value": "on",
-
},
-
},
-
{
-
"alias": undefined,
-
"arguments": [],
"directives": [
{
"arguments": [],
···
},
},
],
-
"kind": "Field",
-
"name": {
-
"kind": "Name",
-
"value": "User",
-
},
"selectionSet": {
"kind": "SelectionSet",
"selections": [
···
"value": "first",
},
"value": {
-
"kind": "FloatValue",
"value": "10",
},
},
···
},
],
},
},
{
"directives": [
···
"value": "story",
},
"value": {
-
"kind": "FloatValue",
"value": "123",
},
},
···
"value": "bar",
},
"value": {
-
"kind": "FloatValue",
"value": "12",
},
},
···
"value": {
"block": true,
"kind": "StringValue",
-
"value": "block string uses \\\\\\"\\"\\"",
},
},
],
···
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
exports[`print > prints the kitchen sink document like graphql.js does 1`] = `
{
"definitions": [
{
···
"kind": "ListValue",
"values": [
{
+
"kind": "IntValue",
"value": "123",
},
{
+
"kind": "IntValue",
"value": "456",
},
],
···
"selectionSet": undefined,
},
{
"directives": [
{
"arguments": [],
···
},
},
],
+
"kind": "InlineFragment",
"selectionSet": {
"kind": "SelectionSet",
"selections": [
···
"value": "first",
},
"value": {
+
"kind": "IntValue",
"value": "10",
},
},
···
},
],
},
+
"typeCondition": {
+
"kind": "NamedType",
+
"name": {
+
"kind": "Name",
+
"value": "User",
+
},
+
},
},
{
"directives": [
···
"value": "story",
},
"value": {
+
"kind": "IntValue",
"value": "123",
},
},
···
"value": "bar",
},
"value": {
+
"kind": "IntValue",
"value": "12",
},
},
···
"value": {
"block": true,
"kind": "StringValue",
+
"value": "block string uses \\"\\"\\"",
},
},
],
+64
src/__tests__/__snapshots__/printer.test.ts.snap
···
···
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+
exports[`print > prints the kitchen sink document like graphql.js does 1`] = `
+
"query queryName($foo: ComplexType, $site: Site = MOBILE) @onQuery {
+
whoever123is: node(id: [123, 456]) {
+
id
+
... on User @onInlineFragment {
+
field2 {
+
id
+
alias: field1(first: 10, after: $foo) @include(if: $foo) {
+
id
+
...frag @onFragmentSpread
+
}
+
}
+
}
+
... @skip(unless: $foo) {
+
id
+
}
+
... {
+
id
+
}
+
}
+
}
+
+
mutation likeStory @onMutation {
+
like(story: 123) @onField {
+
story {
+
id @onField
+
}
+
}
+
}
+
+
subscription StoryLikeSubscription($input: StoryLikeSubscribeInput) @onSubscription {
+
storyLikeSubscribe(input: $input) {
+
story {
+
likers {
+
count
+
}
+
likeSentence {
+
text
+
}
+
}
+
}
+
}
+
+
fragment frag on Friend @onFragmentDefinition {
+
foo(
+
size: $site
+
bar: 12
+
obj: {key: \\"value\\", block: \\"\\"\\"
+
block string uses \\\\\\"\\"\\"
+
\\"\\"\\"}
+
)
+
}
+
+
query teeny {
+
unnamed(truthy: true, falsey: false, nullish: null)
+
query
+
}
+
+
query tiny {
+
__typename
+
}"
+
`;
+69
src/__tests__/kitchen_sink.graphql
···
···
+
# Copyright (c) 2015-present, Facebook, Inc.
+
#
+
# This source code is licensed under the MIT license found in the
+
# LICENSE file in the root directory of this source tree.
+
+
query queryName($foo: ComplexType, $site: Site = MOBILE) @onQuery {
+
whoever123is: node(id: [123, 456]) {
+
id
+
... on User @onInlineFragment {
+
field2 {
+
id
+
alias: field1(first: 10, after: $foo) @include(if: $foo) {
+
id
+
...frag @onFragmentSpread
+
}
+
}
+
}
+
... @skip(unless: $foo) {
+
id
+
}
+
... {
+
id
+
}
+
}
+
}
+
+
mutation likeStory @onMutation {
+
like(story: 123) @onField {
+
story {
+
id @onField
+
}
+
}
+
}
+
+
subscription StoryLikeSubscription($input: StoryLikeSubscribeInput)
+
@onSubscription {
+
storyLikeSubscribe(input: $input) {
+
story {
+
likers {
+
count
+
}
+
likeSentence {
+
text
+
}
+
}
+
}
+
}
+
+
fragment frag on Friend @onFragmentDefinition {
+
foo(
+
size: $site
+
bar: 12
+
obj: {
+
key: "value"
+
block: """
+
block string uses \"""
+
"""
+
}
+
)
+
}
+
+
query teeny {
+
unnamed(truthy: true, falsey: false, nullish: null)
+
query
+
}
+
+
query tiny {
+
__typename
+
}
+733
src/__tests__/kitchen_sink.json
···
···
+
{
+
"kind": "Document",
+
"definitions": [
+
{
+
"kind": "OperationDefinition",
+
"operation": "query",
+
"name": {
+
"kind": "Name",
+
"value": "queryName"
+
},
+
"variableDefinitions": [
+
{
+
"kind": "VariableDefinition",
+
"variable": {
+
"kind": "Variable",
+
"name": {
+
"kind": "Name",
+
"value": "foo"
+
}
+
},
+
"type": {
+
"kind": "NamedType",
+
"name": {
+
"kind": "Name",
+
"value": "ComplexType"
+
}
+
},
+
"directives": []
+
},
+
{
+
"kind": "VariableDefinition",
+
"variable": {
+
"kind": "Variable",
+
"name": {
+
"kind": "Name",
+
"value": "site"
+
}
+
},
+
"type": {
+
"kind": "NamedType",
+
"name": {
+
"kind": "Name",
+
"value": "Site"
+
}
+
},
+
"defaultValue": {
+
"kind": "EnumValue",
+
"value": "MOBILE"
+
},
+
"directives": []
+
}
+
],
+
"directives": [
+
{
+
"kind": "Directive",
+
"name": {
+
"kind": "Name",
+
"value": "onQuery"
+
},
+
"arguments": []
+
}
+
],
+
"selectionSet": {
+
"kind": "SelectionSet",
+
"selections": [
+
{
+
"kind": "Field",
+
"alias": {
+
"kind": "Name",
+
"value": "whoever123is"
+
},
+
"name": {
+
"kind": "Name",
+
"value": "node"
+
},
+
"arguments": [
+
{
+
"kind": "Argument",
+
"name": {
+
"kind": "Name",
+
"value": "id"
+
},
+
"value": {
+
"kind": "ListValue",
+
"values": [
+
{
+
"kind": "IntValue",
+
"value": "123"
+
},
+
{
+
"kind": "IntValue",
+
"value": "456"
+
}
+
]
+
}
+
}
+
],
+
"directives": [],
+
"selectionSet": {
+
"kind": "SelectionSet",
+
"selections": [
+
{
+
"kind": "Field",
+
"name": {
+
"kind": "Name",
+
"value": "id"
+
},
+
"arguments": [],
+
"directives": []
+
},
+
{
+
"kind": "InlineFragment",
+
"typeCondition": {
+
"kind": "NamedType",
+
"name": {
+
"kind": "Name",
+
"value": "User"
+
}
+
},
+
"directives": [
+
{
+
"kind": "Directive",
+
"name": {
+
"kind": "Name",
+
"value": "onInlineFragment"
+
},
+
"arguments": []
+
}
+
],
+
"selectionSet": {
+
"kind": "SelectionSet",
+
"selections": [
+
{
+
"kind": "Field",
+
"name": {
+
"kind": "Name",
+
"value": "field2"
+
},
+
"arguments": [],
+
"directives": [],
+
"selectionSet": {
+
"kind": "SelectionSet",
+
"selections": [
+
{
+
"kind": "Field",
+
"name": {
+
"kind": "Name",
+
"value": "id"
+
},
+
"arguments": [],
+
"directives": []
+
},
+
{
+
"kind": "Field",
+
"alias": {
+
"kind": "Name",
+
"value": "alias"
+
},
+
"name": {
+
"kind": "Name",
+
"value": "field1"
+
},
+
"arguments": [
+
{
+
"kind": "Argument",
+
"name": {
+
"kind": "Name",
+
"value": "first"
+
},
+
"value": {
+
"kind": "IntValue",
+
"value": "10"
+
}
+
},
+
{
+
"kind": "Argument",
+
"name": {
+
"kind": "Name",
+
"value": "after"
+
},
+
"value": {
+
"kind": "Variable",
+
"name": {
+
"kind": "Name",
+
"value": "foo"
+
}
+
}
+
}
+
],
+
"directives": [
+
{
+
"kind": "Directive",
+
"name": {
+
"kind": "Name",
+
"value": "include"
+
},
+
"arguments": [
+
{
+
"kind": "Argument",
+
"name": {
+
"kind": "Name",
+
"value": "if"
+
},
+
"value": {
+
"kind": "Variable",
+
"name": {
+
"kind": "Name",
+
"value": "foo"
+
}
+
}
+
}
+
]
+
}
+
],
+
"selectionSet": {
+
"kind": "SelectionSet",
+
"selections": [
+
{
+
"kind": "Field",
+
"name": {
+
"kind": "Name",
+
"value": "id"
+
},
+
"arguments": [],
+
"directives": []
+
},
+
{
+
"kind": "FragmentSpread",
+
"name": {
+
"kind": "Name",
+
"value": "frag"
+
},
+
"directives": [
+
{
+
"kind": "Directive",
+
"name": {
+
"kind": "Name",
+
"value": "onFragmentSpread"
+
},
+
"arguments": []
+
}
+
]
+
}
+
]
+
}
+
}
+
]
+
}
+
}
+
]
+
}
+
},
+
{
+
"kind": "InlineFragment",
+
"directives": [
+
{
+
"kind": "Directive",
+
"name": {
+
"kind": "Name",
+
"value": "skip"
+
},
+
"arguments": [
+
{
+
"kind": "Argument",
+
"name": {
+
"kind": "Name",
+
"value": "unless"
+
},
+
"value": {
+
"kind": "Variable",
+
"name": {
+
"kind": "Name",
+
"value": "foo"
+
}
+
}
+
}
+
]
+
}
+
],
+
"selectionSet": {
+
"kind": "SelectionSet",
+
"selections": [
+
{
+
"kind": "Field",
+
"name": {
+
"kind": "Name",
+
"value": "id"
+
},
+
"arguments": [],
+
"directives": []
+
}
+
]
+
}
+
},
+
{
+
"kind": "InlineFragment",
+
"directives": [],
+
"selectionSet": {
+
"kind": "SelectionSet",
+
"selections": [
+
{
+
"kind": "Field",
+
"name": {
+
"kind": "Name",
+
"value": "id"
+
},
+
"arguments": [],
+
"directives": []
+
}
+
]
+
}
+
}
+
]
+
}
+
}
+
]
+
}
+
},
+
{
+
"kind": "OperationDefinition",
+
"operation": "mutation",
+
"name": {
+
"kind": "Name",
+
"value": "likeStory"
+
},
+
"variableDefinitions": [],
+
"directives": [
+
{
+
"kind": "Directive",
+
"name": {
+
"kind": "Name",
+
"value": "onMutation"
+
},
+
"arguments": []
+
}
+
],
+
"selectionSet": {
+
"kind": "SelectionSet",
+
"selections": [
+
{
+
"kind": "Field",
+
"name": {
+
"kind": "Name",
+
"value": "like"
+
},
+
"arguments": [
+
{
+
"kind": "Argument",
+
"name": {
+
"kind": "Name",
+
"value": "story"
+
},
+
"value": {
+
"kind": "IntValue",
+
"value": "123"
+
}
+
}
+
],
+
"directives": [
+
{
+
"kind": "Directive",
+
"name": {
+
"kind": "Name",
+
"value": "onField"
+
},
+
"arguments": []
+
}
+
],
+
"selectionSet": {
+
"kind": "SelectionSet",
+
"selections": [
+
{
+
"kind": "Field",
+
"name": {
+
"kind": "Name",
+
"value": "story"
+
},
+
"arguments": [],
+
"directives": [],
+
"selectionSet": {
+
"kind": "SelectionSet",
+
"selections": [
+
{
+
"kind": "Field",
+
"name": {
+
"kind": "Name",
+
"value": "id"
+
},
+
"arguments": [],
+
"directives": [
+
{
+
"kind": "Directive",
+
"name": {
+
"kind": "Name",
+
"value": "onField"
+
},
+
"arguments": []
+
}
+
]
+
}
+
]
+
}
+
}
+
]
+
}
+
}
+
]
+
}
+
},
+
{
+
"kind": "OperationDefinition",
+
"operation": "subscription",
+
"name": {
+
"kind": "Name",
+
"value": "StoryLikeSubscription"
+
},
+
"variableDefinitions": [
+
{
+
"kind": "VariableDefinition",
+
"variable": {
+
"kind": "Variable",
+
"name": {
+
"kind": "Name",
+
"value": "input"
+
}
+
},
+
"type": {
+
"kind": "NamedType",
+
"name": {
+
"kind": "Name",
+
"value": "StoryLikeSubscribeInput"
+
}
+
},
+
"directives": []
+
}
+
],
+
"directives": [
+
{
+
"kind": "Directive",
+
"name": {
+
"kind": "Name",
+
"value": "onSubscription"
+
},
+
"arguments": []
+
}
+
],
+
"selectionSet": {
+
"kind": "SelectionSet",
+
"selections": [
+
{
+
"kind": "Field",
+
"name": {
+
"kind": "Name",
+
"value": "storyLikeSubscribe"
+
},
+
"arguments": [
+
{
+
"kind": "Argument",
+
"name": {
+
"kind": "Name",
+
"value": "input"
+
},
+
"value": {
+
"kind": "Variable",
+
"name": {
+
"kind": "Name",
+
"value": "input"
+
}
+
}
+
}
+
],
+
"directives": [],
+
"selectionSet": {
+
"kind": "SelectionSet",
+
"selections": [
+
{
+
"kind": "Field",
+
"name": {
+
"kind": "Name",
+
"value": "story"
+
},
+
"arguments": [],
+
"directives": [],
+
"selectionSet": {
+
"kind": "SelectionSet",
+
"selections": [
+
{
+
"kind": "Field",
+
"name": {
+
"kind": "Name",
+
"value": "likers"
+
},
+
"arguments": [],
+
"directives": [],
+
"selectionSet": {
+
"kind": "SelectionSet",
+
"selections": [
+
{
+
"kind": "Field",
+
"name": {
+
"kind": "Name",
+
"value": "count"
+
},
+
"arguments": [],
+
"directives": []
+
}
+
]
+
}
+
},
+
{
+
"kind": "Field",
+
"name": {
+
"kind": "Name",
+
"value": "likeSentence"
+
},
+
"arguments": [],
+
"directives": [],
+
"selectionSet": {
+
"kind": "SelectionSet",
+
"selections": [
+
{
+
"kind": "Field",
+
"name": {
+
"kind": "Name",
+
"value": "text"
+
},
+
"arguments": [],
+
"directives": []
+
}
+
]
+
}
+
}
+
]
+
}
+
}
+
]
+
}
+
}
+
]
+
}
+
},
+
{
+
"kind": "FragmentDefinition",
+
"name": {
+
"kind": "Name",
+
"value": "frag"
+
},
+
"typeCondition": {
+
"kind": "NamedType",
+
"name": {
+
"kind": "Name",
+
"value": "Friend"
+
}
+
},
+
"directives": [
+
{
+
"kind": "Directive",
+
"name": {
+
"kind": "Name",
+
"value": "onFragmentDefinition"
+
},
+
"arguments": []
+
}
+
],
+
"selectionSet": {
+
"kind": "SelectionSet",
+
"selections": [
+
{
+
"kind": "Field",
+
"name": {
+
"kind": "Name",
+
"value": "foo"
+
},
+
"arguments": [
+
{
+
"kind": "Argument",
+
"name": {
+
"kind": "Name",
+
"value": "size"
+
},
+
"value": {
+
"kind": "Variable",
+
"name": {
+
"kind": "Name",
+
"value": "site"
+
}
+
}
+
},
+
{
+
"kind": "Argument",
+
"name": {
+
"kind": "Name",
+
"value": "bar"
+
},
+
"value": {
+
"kind": "IntValue",
+
"value": "12"
+
}
+
},
+
{
+
"kind": "Argument",
+
"name": {
+
"kind": "Name",
+
"value": "obj"
+
},
+
"value": {
+
"kind": "ObjectValue",
+
"fields": [
+
{
+
"kind": "ObjectField",
+
"name": {
+
"kind": "Name",
+
"value": "key"
+
},
+
"value": {
+
"kind": "StringValue",
+
"value": "value",
+
"block": false
+
}
+
},
+
{
+
"kind": "ObjectField",
+
"name": {
+
"kind": "Name",
+
"value": "block"
+
},
+
"value": {
+
"kind": "StringValue",
+
"value": "block string uses \"\"\"",
+
"block": true
+
}
+
}
+
]
+
}
+
}
+
],
+
"directives": []
+
}
+
]
+
}
+
},
+
{
+
"kind": "OperationDefinition",
+
"operation": "query",
+
"name": {
+
"kind": "Name",
+
"value": "teeny"
+
},
+
"variableDefinitions": [],
+
"directives": [],
+
"selectionSet": {
+
"kind": "SelectionSet",
+
"selections": [
+
{
+
"kind": "Field",
+
"name": {
+
"kind": "Name",
+
"value": "unnamed"
+
},
+
"arguments": [
+
{
+
"kind": "Argument",
+
"name": {
+
"kind": "Name",
+
"value": "truthy"
+
},
+
"value": {
+
"kind": "BooleanValue",
+
"value": true
+
}
+
},
+
{
+
"kind": "Argument",
+
"name": {
+
"kind": "Name",
+
"value": "falsey"
+
},
+
"value": {
+
"kind": "BooleanValue",
+
"value": false
+
}
+
},
+
{
+
"kind": "Argument",
+
"name": {
+
"kind": "Name",
+
"value": "nullish"
+
},
+
"value": {
+
"kind": "NullValue"
+
}
+
}
+
],
+
"directives": []
+
},
+
{
+
"kind": "Field",
+
"name": {
+
"kind": "Name",
+
"value": "query"
+
},
+
"arguments": [],
+
"directives": []
+
}
+
]
+
}
+
},
+
{
+
"kind": "OperationDefinition",
+
"operation": "query",
+
"name": {
+
"kind": "Name",
+
"value": "tiny"
+
},
+
"variableDefinitions": [],
+
"directives": [],
+
"selectionSet": {
+
"kind": "SelectionSet",
+
"selections": [
+
{
+
"kind": "Field",
+
"name": {
+
"kind": "Name",
+
"value": "__typename"
+
},
+
"arguments": [],
+
"directives": []
+
}
+
]
+
}
+
}
+
]
+
}
+7 -4
src/__tests__/parser.test.ts
···
import { describe, it, expect } from 'vitest';
import { readFileSync } from 'fs';
import { parse } from '../parser';
-
describe('parse', () => {
-
it('parses the kitchen sink query', () => {
const sink = readFileSync(__dirname + '/../../benchmark/kitchen_sink.graphql', { encoding: 'utf8' });
-
expect(parse(sink)).toMatchSnapshot();
-
})
});
···
import { describe, it, expect } from 'vitest';
import { readFileSync } from 'fs';
+
import { parse as graphql_parse } from 'graphql';
import { parse } from '../parser';
+
describe('print', () => {
+
it('prints the kitchen sink document like graphql.js does', () => {
const sink = readFileSync(__dirname + '/../../benchmark/kitchen_sink.graphql', { encoding: 'utf8' });
+
const doc = parse(sink);
+
expect(doc).toMatchSnapshot();
+
expect(doc).toEqual(graphql_parse(sink, { noLocation: true }));
+
});
});
+14
src/__tests__/printer.test.ts
···
···
+
import { describe, it, expect } from 'vitest';
+
import { readFileSync } from 'fs';
+
+
import { print as graphql_print } from 'graphql';
+
import { print } from '../printer';
+
+
describe('print', () => {
+
it('prints the kitchen sink document like graphql.js does', () => {
+
const sink = JSON.parse(readFileSync(__dirname + '/kitchen_sink.json', { encoding: 'utf8' }));
+
const doc = print(sink);
+
expect(doc).toMatchSnapshot();
+
expect(doc).toEqual(graphql_print(sink));
+
});
+
});
+5 -3
src/parser.ts
···
}
for (let i = firstNonEmptyLine; i <= lastNonEmptyLine; i++) {
if (i !== firstNonEmptyLine) out += '\n';
-
out += lines[i].slice(commonIndent);
}
return out;
}
···
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;
···
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();
···
}
for (let i = firstNonEmptyLine; i <= lastNonEmptyLine; i++) {
if (i !== firstNonEmptyLine) out += '\n';
+
out += lines[i].slice(commonIndent).replace(/\\"""/g, '"""');
}
return out;
}
···
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;
···
ignored();
if (advance(fragmentSpreadRe)) {
ignored();
+
const _idx = idx;
let _name: ast.NameNode | undefined;
+
if ((_name = name()) && _name.value !== 'on') {
return {
kind: Kind.FRAGMENT_SPREAD,
name: _name,
directives: directives(false),
};
} else {
+
idx = _idx;
const _typeCondition = typeCondition();
const _directives = directives(false);
const _selectionSet = selectionSet();
+9 -2
src/printer.ts
···
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.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
···
array: ReadonlyArray<T> | undefined | null
): array is ReadonlyArray<T> => !!(array && array.length);
+
const MAX_LINE_LENGTH = 80;
+
export function print(node: ASTNode): string {
let out: string;
switch (node.kind) {
···
case Kind.FIELD:
out = (node.alias ? print(node.alias) + ': ' : '') + node.name.value
+
if (hasItems(node.arguments)) {
+
const args = node.arguments.map(print);
+
const argsLine = out + '(' + args.join(', ') + ')';
+
out = argsLine.length > MAX_LINE_LENGTH
+
? out + '(\n ' + args.join('\n').replace(/\n/g, '\n ') + '\n)'
+
: argsLine;
+
}
if (hasItems(node.directives))
out += ' ' + node.directives.map(print).join(' ');
return node.selectionSet