diff --git a/src/language/__tests__/lexer-test.js b/src/language/__tests__/lexer-test.js index 7897495e15e..d6a70a47c20 100644 --- a/src/language/__tests__/lexer-test.js +++ b/src/language/__tests__/lexer-test.js @@ -27,13 +27,6 @@ function expectSyntaxError(text: string) { } describe('Lexer', () => { - it('disallows uncommon control characters', () => { - expectSyntaxError('\u0007').to.deep.equal({ - message: 'Syntax Error: Invalid character: U+0007.', - locations: [{ line: 1, column: 1 }], - }); - }); - it('ignores BOM header', () => { expect(lexOne('\uFEFF foo')).to.contain({ kind: TokenKind.NAME, @@ -263,12 +256,98 @@ describe('Lexer', () => { value: 'slashes \\ /', }); + expect(lexOne('"unescaped unicode outside BMP \u{1f600}"')).to.contain({ + kind: TokenKind.STRING, + start: 0, + end: 34, + value: 'unescaped unicode outside BMP \u{1f600}', + }); + + expect( + lexOne('"unescaped maximal unicode outside BMP \u{10ffff}"'), + ).to.contain({ + kind: TokenKind.STRING, + start: 0, + end: 42, + value: 'unescaped maximal unicode outside BMP \u{10ffff}', + }); + expect(lexOne('"unicode \\u1234\\u5678\\u90AB\\uCDEF"')).to.contain({ kind: TokenKind.STRING, start: 0, end: 34, value: 'unicode \u1234\u5678\u90AB\uCDEF', }); + + expect(lexOne('"unicode \\u{1234}\\u{5678}\\u{90AB}\\u{CDEF}"')).to.contain( + { + kind: TokenKind.STRING, + start: 0, + end: 42, + value: 'unicode \u1234\u5678\u90AB\uCDEF', + }, + ); + + expect( + lexOne('"string with unicode escape outside BMP \\u{1F600}"'), + ).to.contain({ + kind: TokenKind.STRING, + start: 0, + end: 50, + value: 'string with unicode escape outside BMP \u{1f600}', + }); + + expect(lexOne('"string with minimal unicode escape \\u{0}"')).to.contain({ + kind: TokenKind.STRING, + start: 0, + end: 42, + value: 'string with minimal unicode escape \u{0}', + }); + + expect( + lexOne('"string with maximal unicode escape \\u{10FFFF}"'), + ).to.contain({ + kind: TokenKind.STRING, + start: 0, + end: 47, + value: 'string with maximal unicode escape \u{10FFFF}', + }); + + expect( + lexOne('"string with maximal minimal unicode escape \\u{00000000}"'), + ).to.contain({ + kind: TokenKind.STRING, + start: 0, + end: 57, + value: 'string with maximal minimal unicode escape \u{0}', + }); + + expect( + lexOne('"string with unicode surrogate pair escape \\uD83D\\uDE00"'), + ).to.contain({ + kind: TokenKind.STRING, + start: 0, + end: 56, + value: 'string with unicode surrogate pair escape \u{1f600}', + }); + + expect( + lexOne('"string with minimal surrogate pair escape \\uD800\\uDC00"'), + ).to.contain({ + kind: TokenKind.STRING, + start: 0, + end: 56, + value: 'string with minimal surrogate pair escape \u{10000}', + }); + + expect( + lexOne('"string with maximal surrogate pair escape \\uDBFF\\uDFFF"'), + ).to.contain({ + kind: TokenKind.STRING, + start: 0, + end: 56, + value: 'string with maximal surrogate pair escape \u{10FFFF}', + }); }); it('lex reports useful string errors', () => { @@ -298,16 +377,19 @@ describe('Lexer', () => { locations: [{ line: 1, column: 1 }], }); - expectSyntaxError('"contains unescaped \u0007 control char"').to.deep.equal( - { - message: 'Syntax Error: Invalid character within String: U+0007.', - locations: [{ line: 1, column: 21 }], - }, - ); + expectSyntaxError('"bad surrogate \uDEAD"').to.deep.equal({ + message: 'Syntax Error: Invalid character within String: U+DEAD.', + locations: [{ line: 1, column: 16 }], + }); + + expectSyntaxError('"bad high surrogate pair \uDEAD\uDEAD"').to.deep.equal({ + message: 'Syntax Error: Invalid character within String: U+DEAD.', + locations: [{ line: 1, column: 26 }], + }); - expectSyntaxError('"null-byte is not \u0000 end of file"').to.deep.equal({ - message: 'Syntax Error: Invalid character within String: U+0000.', - locations: [{ line: 1, column: 19 }], + expectSyntaxError('"bad low surrogate pair \uD800\uD800"').to.deep.equal({ + message: 'Syntax Error: Invalid character within String: U+D800.', + locations: [{ line: 1, column: 25 }], }); expectSyntaxError('"multi\nline"').to.deep.equal({ @@ -354,6 +436,93 @@ describe('Lexer', () => { message: 'Syntax Error: Invalid Unicode escape sequence: "\\uXXXF".', locations: [{ line: 1, column: 6 }], }); + + expectSyntaxError('"bad \\u{} esc"').to.deep.equal({ + message: 'Syntax Error: Invalid Unicode escape sequence: "\\u{}".', + locations: [{ line: 1, column: 6 }], + }); + + expectSyntaxError('"bad \\u{FXXX} esc"').to.deep.equal({ + message: 'Syntax Error: Invalid Unicode escape sequence: "\\u{FX".', + locations: [{ line: 1, column: 6 }], + }); + + expectSyntaxError('"bad \\u{FFFF esc"').to.deep.equal({ + message: 'Syntax Error: Invalid Unicode escape sequence: "\\u{FFFF ".', + locations: [{ line: 1, column: 6 }], + }); + + expectSyntaxError('"bad \\u{FFFF"').to.deep.equal({ + message: 'Syntax Error: Invalid Unicode escape sequence: "\\u{FFFF"".', + locations: [{ line: 1, column: 6 }], + }); + + expectSyntaxError('"too high \\u{110000} esc"').to.deep.equal({ + message: 'Syntax Error: Invalid Unicode escape sequence: "\\u{110000}".', + locations: [{ line: 1, column: 11 }], + }); + + expectSyntaxError('"way too high \\u{12345678} esc"').to.deep.equal({ + message: + 'Syntax Error: Invalid Unicode escape sequence: "\\u{12345678}".', + locations: [{ line: 1, column: 15 }], + }); + + expectSyntaxError('"too long \\u{000000000} esc"').to.deep.equal({ + message: + 'Syntax Error: Invalid Unicode escape sequence: "\\u{000000000".', + locations: [{ line: 1, column: 11 }], + }); + + expectSyntaxError('"bad surrogate \\uDEAD esc"').to.deep.equal({ + message: 'Syntax Error: Invalid Unicode escape sequence: "\\uDEAD".', + locations: [{ line: 1, column: 16 }], + }); + + expectSyntaxError('"bad surrogate \\u{DEAD} esc"').to.deep.equal({ + message: 'Syntax Error: Invalid Unicode escape sequence: "\\u{DEAD}".', + locations: [{ line: 1, column: 16 }], + }); + + expectSyntaxError( + '"cannot use braces for surrogate pair \\u{D83D}\\u{DE00} esc"', + ).to.deep.equal({ + message: 'Syntax Error: Invalid Unicode escape sequence: "\\u{D83D}".', + locations: [{ line: 1, column: 39 }], + }); + + expectSyntaxError( + '"bad high surrogate pair \\uDEAD\\uDEAD esc"', + ).to.deep.equal({ + message: 'Syntax Error: Invalid Unicode escape sequence: "\\uDEAD".', + locations: [{ line: 1, column: 26 }], + }); + + expectSyntaxError( + '"bad low surrogate pair \\uD800\\uD800 esc"', + ).to.deep.equal({ + message: 'Syntax Error: Invalid Unicode escape sequence: "\\uD800".', + locations: [{ line: 1, column: 25 }], + }); + + expectSyntaxError( + '"cannot escape half a pair \uD83D\\uDE00 esc"', + ).to.deep.equal({ + message: 'Syntax Error: Invalid character within String: U+D83D.', + locations: [{ line: 1, column: 28 }], + }); + + expectSyntaxError( + '"cannot escape half a pair \\uD83D\uDE00 esc"', + ).to.deep.equal({ + message: 'Syntax Error: Invalid Unicode escape sequence: "\\uD83D".', + locations: [{ line: 1, column: 28 }], + }); + + expectSyntaxError('"bad \\uD83D\\not an escape"').to.deep.equal({ + message: 'Syntax Error: Invalid Unicode escape sequence: "\\uD83D".', + locations: [{ line: 1, column: 6 }], + }); }); it('lexes block strings', () => { @@ -413,6 +582,13 @@ describe('Lexer', () => { value: 'unescaped \\n\\r\\b\\t\\f\\u1234', }); + expect(lexOne('"""unescaped unicode outside BMP \u{1f600}"""')).to.contain({ + kind: TokenKind.BLOCK_STRING, + start: 0, + end: 38, + value: 'unescaped unicode outside BMP \u{1f600}', + }); + expect(lexOne('"""slashes \\\\ \\/"""')).to.contain({ kind: TokenKind.BLOCK_STRING, start: 0, @@ -485,18 +661,9 @@ describe('Lexer', () => { locations: [{ line: 1, column: 16 }], }); - expectSyntaxError( - '"""contains unescaped \u0007 control char"""', - ).to.deep.equal({ - message: 'Syntax Error: Invalid character within String: U+0007.', - locations: [{ line: 1, column: 23 }], - }); - - expectSyntaxError( - '"""null-byte is not \u0000 end of file"""', - ).to.deep.equal({ - message: 'Syntax Error: Invalid character within String: U+0000.', - locations: [{ line: 1, column: 21 }], + expectSyntaxError('"""contains invalid surrogate \uDEAD"""').to.deep.equal({ + message: 'Syntax Error: Invalid character within String: U+DEAD.', + locations: [{ line: 1, column: 31 }], }); }); @@ -836,10 +1003,30 @@ describe('Lexer', () => { locations: [{ line: 1, column: 1 }], }); + expectSyntaxError('\x00').to.deep.equal({ + message: 'Syntax Error: Unexpected character: U+0000.', + locations: [{ line: 1, column: 1 }], + }); + + expectSyntaxError('\b').to.deep.equal({ + message: 'Syntax Error: Unexpected character: U+0008.', + locations: [{ line: 1, column: 1 }], + }); + expectSyntaxError('\u203B').to.deep.equal({ message: 'Syntax Error: Unexpected character: U+203B.', locations: [{ line: 1, column: 1 }], }); + + expectSyntaxError('\u{1f600}').to.deep.equal({ + message: 'Syntax Error: Unexpected character: U+1F600.', + locations: [{ line: 1, column: 1 }], + }); + + expectSyntaxError('\uDEAD').to.deep.equal({ + message: 'Syntax Error: Invalid character: U+DEAD.', + locations: [{ line: 1, column: 1 }], + }); }); it('lex reports useful information for dashes in names', () => { @@ -920,9 +1107,15 @@ describe('Lexer', () => { end: 9, value: ' Comment', }); - expectSyntaxError('# \u0007').to.deep.equal({ - message: 'Syntax Error: Invalid character: U+0007.', - locations: [{ line: 1, column: 3 }], + expect(lexOne('# Comment \u{1f600}').prev).to.contain({ + kind: TokenKind.COMMENT, + start: 0, + end: 12, + value: ' Comment \u{1f600}', + }); + expectSyntaxError('# Invalid surrogate \uDEAD').to.deep.equal({ + message: 'Syntax Error: Invalid character: U+DEAD.', + locations: [{ line: 1, column: 21 }], }); }); }); diff --git a/src/language/__tests__/printString-test.js b/src/language/__tests__/printString-test.js new file mode 100644 index 00000000000..e7056aac7f0 --- /dev/null +++ b/src/language/__tests__/printString-test.js @@ -0,0 +1,54 @@ +import { expect } from 'chai'; +import { describe, it } from 'mocha'; + +import { printString } from '../printString'; + +describe('printString', () => { + it('prints a simple string', () => { + expect(printString('hello world')).to.equal('"hello world"'); + }); + + it('escapes quotes', () => { + expect(printString('"hello world"')).to.equal('"\\"hello world\\""'); + }); + + it('does not escape single quote', () => { + expect(printString("who's test")).to.equal('"who\'s test"'); + }); + + it('escapes backslashes', () => { + expect(printString('escape: \\n')).to.equal('"escape: \\\\n"'); + }); + + it('escapes well-known control chars', () => { + expect(printString('\b\f\n\r\t')).to.equal('"\\b\\f\\n\\r\\t"'); + }); + + it('escapes zero byte', () => { + expect(printString('\x00')).to.equal('"\\u0000"'); + }); + + it('does not escape space', () => { + expect(printString(' ')).to.equal('" "'); + }); + + it('escapes all other control chars', () => { + for (let i = 1; i <= 0x9f; i++) { + const source = String.fromCharCode(i); + if (/[\b\f\n\r\t]/.test(source) || (i >= 0x0020 && i <= 0x007e)) { + continue; + } + expect(printString(source)).to.equal( + `"\\u00${i <= 0x000f ? '0' : ''}${i.toString(16).toUpperCase()}"`, + ); + } + }); + + it('does not escape non-ascii character', () => { + expect(printString('\u21BB')).to.equal('"\u21BB"'); + }); + + it('does not escape supplementary character', () => { + expect(printString('\u{1f600}')).to.equal('"\u{1f600}"'); + }); +}); diff --git a/src/language/lexer.js b/src/language/lexer.js index 386d91fa6fa..05cf3d674f6 100644 --- a/src/language/lexer.js +++ b/src/language/lexer.js @@ -104,18 +104,53 @@ export function isPunctuatorTokenKind(kind: TokenKindEnum): boolean { } /** + * A Unicode scalar value is any Unicode code point except surrogate code + * points. In other words, the inclusive ranges of values 0x0000 to 0xD7FF and + * 0xE000 to 0x10FFFF. + * * SourceCharacter :: - * - U+0009 (Horizontal Tab) - * - U+000A (New Line) - * - U+000D (Carriage Return) - * - U+0020-U+FFFF + * - "Any Unicode scalar value" */ -function isSourceCharacter(code: number): boolean { +function isUnicodeScalarValue(code: number): boolean { return ( - code >= 0x0020 || code === 0x0009 || code === 0x000a || code === 0x000d + (code >= 0x0000 && code <= 0xd7ff) || (code >= 0xe000 && code <= 0x10ffff) ); } +/** + * The GraphQL specification defines source text as a sequence of unicode scalar + * values (which Unicode defines to exclude surrogate code points). However + * JavaScript defines strings as a sequence of UTF-16 code units which may + * include surrogates. A surrogate pair is a valid source character as it + * encodes a supplementary code point (above U+FFFF), but unpaired surrogate + * code points are not valid source characters. + */ +function isSupplementaryCodePoint(body: string, location: number): boolean { + return ( + isLeadingSurrogate(body.charCodeAt(location)) && + isTrailingSurrogate(body.charCodeAt(location + 1)) + ); +} + +function isLeadingSurrogate(code: number): boolean { + return code >= 0xd800 && code <= 0xdbff; +} + +function isTrailingSurrogate(code: number): boolean { + return code >= 0xdc00 && code <= 0xdfff; +} + +function encodeSurrogatePair(point: number): string { + return String.fromCharCode( + 0xd800 | ((point - 0x10000) >> 10), // Leading Surrogate + 0xdc00 | ((point - 0x10000) & 0x3ff), // Trailing Surrogate + ); +} + +function decodeSurrogatePair(leading: number, trailing: number): number { + return 0x10000 | ((leading & 0x03ff) << 10) | (trailing & 0x03ff); +} + /** * Prints the code point (or end of file reference) at a given location in a * source for use in error messages. @@ -134,7 +169,10 @@ function printCodePointAt(lexer: Lexer, location: number): string { return code === 0x0022 ? "'\"'" : `"${body[location]}"`; } // Unicode code point - const hexCode = code.toString(16).toUpperCase(); + const point = isSupplementaryCodePoint(body, location) + ? decodeSurrogatePair(code, body.charCodeAt(location + 1)) + : code; + const hexCode = point.toString(16).toUpperCase(); const zeroPad = hexCode.length < 4 ? Array(5 - hexCode.length).join('0') : ''; return `U+${zeroPad}${hexCode}`; } @@ -281,7 +319,7 @@ function readNextToken(lexer: Lexer, prev: Token): Token { position, code === 0x0027 ? 'Unexpected single quote character (\'), did you mean to use a double quote (")?' - : isSourceCharacter(code) + : isUnicodeScalarValue(code) || isSupplementaryCodePoint(body, position) ? `Unexpected character: ${printCodePointAt(lexer, position)}.` : `Invalid character: ${printCodePointAt(lexer, position)}.`, ); @@ -311,8 +349,10 @@ function readComment(lexer: Lexer, start: number): Token { } // SourceCharacter - if (isSourceCharacter(code)) { + if (isUnicodeScalarValue(code)) { ++position; + } else if (isSupplementaryCodePoint(body, position)) { + position += 2; } else { break; } @@ -467,7 +507,14 @@ function readDigits(lexer: Lexer, start: number, firstCode: number): number { * - `\u` EscapedUnicode * - `\` EscapedCharacter * - * EscapedUnicode :: /[0-9A-Fa-f]{4}/ + * EscapedUnicode :: + * - `{` HexDigit+ `}` + * - HexDigit HexDigit HexDigit HexDigit + * + * HexDigit :: one of + * - `0` `1` `2` `3` `4` `5` `6` `7` `8` `9` + * - `A` `B` `C` `D` `E` `F` + * - `a` `b` `c` `d` `e` `f` * * EscapedCharacter :: one of `"` `\` `/` `b` `f` `n` `r` `t` */ @@ -492,7 +539,9 @@ function readString(lexer: Lexer, start: number): Token { value += body.slice(chunkStart, position); const escape = body.charCodeAt(position + 1) === 0x0075 // u - ? readEscapedUnicode(lexer, position) + ? body.charCodeAt(position + 2) === 0x007b // { + ? readEscapedUnicodeVariableWidth(lexer, position) + : readEscapedUnicodeFixedWidth(lexer, position) : readEscapedCharacter(lexer, position); value += escape.value; position += escape.size; @@ -506,8 +555,10 @@ function readString(lexer: Lexer, start: number): Token { } // SourceCharacter - if (isSourceCharacter(code)) { + if (isUnicodeScalarValue(code)) { ++position; + } else if (isSupplementaryCodePoint(body, position)) { + position += 2; } else { throw syntaxError( lexer.source, @@ -526,14 +577,81 @@ function readString(lexer: Lexer, start: number): Token { // The string value and lexed size of an escape sequence. type EscapeSequence = { value: string, size: number }; -function readEscapedUnicode(lexer: Lexer, position: number): EscapeSequence { +function readEscapedUnicodeVariableWidth( + lexer: Lexer, + position: number, +): EscapeSequence { + const body = lexer.source.body; + let point = 0; + let size = 3; + // Cannot be larger than 12 chars (\u{00000000}). + while (size < 12) { + const code = body.charCodeAt(position + size++); + // Closing Brace (}) + if (code === 125) { + // Must be at least 5 chars (\u{0}) and encode a Unicode scalar value. + if (size < 5 || !isUnicodeScalarValue(point)) { + break; + } + // JavaScript defines strings as a sequence of UTF-16 code units and + // encodes Unicode code points above U+FFFF using a surrogate pair. + return { + value: + point <= 0xffff + ? String.fromCharCode(point) + : encodeSurrogatePair(point), + size, + }; + } + // Append this hex digit to the code point. + point = (point << 4) | hexValue(code); + if (point < 0) { + break; + } + } + + throw syntaxError( + lexer.source, + position, + `Invalid Unicode escape sequence: "${body.slice( + position, + position + size, + )}".`, + ); +} + +function readEscapedUnicodeFixedWidth( + lexer: Lexer, + position: number, +): EscapeSequence { const body = lexer.source.body; const code = read16BitHexCode(body, position + 2); - if (code >= 0) { + if (isUnicodeScalarValue(code)) { return { value: String.fromCharCode(code), size: 6 }; } + // GraphQL allows JSON-style surrogate pair escape sequences, but only when + // a valid pair is formed. + if (isLeadingSurrogate(code)) { + // \u + if ( + body.charCodeAt(position + 6) === 92 && + body.charCodeAt(position + 7) === 117 + ) { + const trailingCode = read16BitHexCode(body, position + 8); + if (isTrailingSurrogate(trailingCode)) { + // JavaScript defines strings as a sequence of UTF-16 code units and + // encodes Unicode code points above U+FFFF using a surrogate pair of + // code units. Since this is a surrogate pair escape sequence, just + // include both codes into the JavaScript string value. Had JavaScript + // not been internally based on UTF-16, then this surrogate pair would + // be decoded to retrieve the supplementary code point. + return { value: String.fromCharCode(code, trailingCode), size: 12 }; + } + } + } + throw syntaxError( lexer.source, position, @@ -684,8 +802,10 @@ function readBlockString(lexer: Lexer, start: number): Token { } // SourceCharacter - if (isSourceCharacter(code)) { + if (isUnicodeScalarValue(code)) { ++position; + } else if (isSupplementaryCodePoint(body, position)) { + position += 2; } else { throw syntaxError( lexer.source, diff --git a/src/language/printString.js b/src/language/printString.js new file mode 100644 index 00000000000..1940b2902bd --- /dev/null +++ b/src/language/printString.js @@ -0,0 +1,38 @@ +/** + * Prints a string as a GraphQL StringValue literal. Replaces control characters + * and excluded characters (" U+0022 and \ U+005C) with escape sequences. + */ +export function printString(str: string): string { + return `"${str.replace(escapedRegExp, escapedReplacer)}"`; +} + +// eslint-disable-next-line no-control-regex +const escapedRegExp = /[\x00-\x1f\x22\x5c\x7f-\x9f]/g; + +function escapedReplacer(str: string): string { + return escapeSequences[str.charCodeAt(0)]; +} + +// prettier-ignore +const escapeSequences = [ + '\\u0000', '\\u0001', '\\u0002', '\\u0003', '\\u0004', '\\u0005', '\\u0006', '\\u0007', + '\\b', '\\t', '\\n', '\\u000B', '\\f', '\\r', '\\u000E', '\\u000F', + '\\u0010', '\\u0011', '\\u0012', '\\u0013', '\\u0014', '\\u0015', '\\u0016', '\\u0017', + '\\u0018', '\\u0019', '\\u001A', '\\u001B', '\\u001C', '\\u001D', '\\u001E', '\\u001F', + '', '', '\\"', '', '', '', '', '', + '', '', '', '', '', '', '', '', // 2F + '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', // 3F + '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', // 4F + '', '', '', '', '', '', '', '', + '', '', '', '', '\\\\', '', '', '', // 5F + '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', // 6F + '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '\\u007F', + '\\u0080', '\\u0081', '\\u0082', '\\u0083', '\\u0084', '\\u0085', '\\u0086', '\\u0087', + '\\u0088', '\\u0089', '\\u008A', '\\u008B', '\\u008C', '\\u008D', '\\u008E', '\\u008F', + '\\u0090', '\\u0091', '\\u0092', '\\u0093', '\\u0094', '\\u0095', '\\u0096', '\\u0097', + '\\u0098', '\\u0099', '\\u009A', '\\u009B', '\\u009C', '\\u009D', '\\u009E', '\\u009F', +]; diff --git a/src/language/printer.js b/src/language/printer.js index 43bd627195d..884540620b1 100644 --- a/src/language/printer.js +++ b/src/language/printer.js @@ -2,6 +2,7 @@ import type { ASTNode } from './ast'; import { visit } from './visitor'; import { printBlockString } from './blockString'; +import { printString } from './printString'; /** * Converts an AST into a string, using one set of reasonable @@ -108,7 +109,7 @@ const printDocASTReducer: any = { FloatValue: { leave: ({ value }) => value }, StringValue: { leave: ({ value, block: isBlockString }) => - isBlockString ? printBlockString(value) : JSON.stringify(value), + isBlockString ? printBlockString(value) : printString(value), }, BooleanValue: { leave: ({ value }) => (value ? 'true' : 'false') }, NullValue: { leave: () => 'null' }, diff --git a/src/type/__tests__/introspection-test.js b/src/type/__tests__/introspection-test.js index 9db9470e975..5da2ba425f8 100644 --- a/src/type/__tests__/introspection-test.js +++ b/src/type/__tests__/introspection-test.js @@ -1068,6 +1068,52 @@ describe('Introspection', () => { }); }); + it('introspects any default value', () => { + const schema = buildSchema(` + input InputObjectWithDefaultValues { + a: String = "Emoji: \\u{1F600}" + b: Complex = {x: ["abc"], y: 123} + } + + input Complex { + x: [String] + y: Int + } + + type Query { + someField(someArg: InputObjectWithDefaultValues): String + } + `); + + const source = ` + { + __type(name: "InputObjectWithDefaultValues") { + inputFields { + name + defaultValue + } + } + } + `; + + expect(graphqlSync({ schema, source })).to.deep.equal({ + data: { + __type: { + inputFields: [ + { + name: 'a', + defaultValue: "\"Emoji: \u{1F600}\"", + }, + { + name: 'b', + defaultValue: "{x: [\"abc\"], y: 123}", + }, + ], + }, + }, + }); + }); + it('supports the __type root field', () => { const schema = buildSchema(` type Query {