mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-22 04:46:04 +03:00
RFC: Block String
This RFC adds a new form of `StringValue`, the multi-line string, similar to that found in Python and Scala. A multi-line string starts and ends with a triple-quote: ``` """This is a triple-quoted string and it can contain multiple lines""" ``` Multi-line strings are useful for typing literal bodies of text where new lines should be interpretted literally. In fact, the only escape sequence used is `\"""` and `\` is otherwise allowed unescaped. This is beneficial when writing documentation within strings which may reference the back-slash often: ``` """ In a multi-line string \n and C:\\ are unescaped. """ ``` The primary value of multi-line strings are to write long-form input directly in query text, in tools like GraphiQL, and as a prerequisite to another pending RFC to allow docstring style documentation in the Schema Definition Language. Ref: graphql/graphql-js#926
This commit is contained in:
parent
46816a7cda
commit
8747ff8954
@ -9,4 +9,9 @@ class StringValueNode extends Node implements ValueNode
|
||||
* @var string
|
||||
*/
|
||||
public $value;
|
||||
|
||||
/**
|
||||
* @var boolean|null
|
||||
*/
|
||||
public $block;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ namespace GraphQL\Language;
|
||||
|
||||
use GraphQL\Error\SyntaxError;
|
||||
use GraphQL\Utils\Utils;
|
||||
use GraphQL\Utils\BlockString;
|
||||
|
||||
/**
|
||||
* A Lexer is a stateful stream generator in that every time
|
||||
@ -201,7 +202,15 @@ class Lexer
|
||||
->readNumber($line, $col, $prev);
|
||||
// "
|
||||
case 34:
|
||||
return $this->moveStringCursor(-1, -1 * $bytes)
|
||||
list(,$nextCode) = $this->readChar();
|
||||
list(,$nextNextCode) = $this->moveStringCursor(1, 1)->readChar();
|
||||
|
||||
if ($nextCode === 34 && $nextNextCode === 34) {
|
||||
return $this->moveStringCursor(-2, (-1 * $bytes) - 1)
|
||||
->readBlockString($line, $col, $prev);
|
||||
}
|
||||
|
||||
return $this->moveStringCursor(-2, (-1 * $bytes) - 1)
|
||||
->readString($line, $col, $prev);
|
||||
}
|
||||
|
||||
@ -370,12 +379,28 @@ class Lexer
|
||||
$value = '';
|
||||
|
||||
while (
|
||||
$code &&
|
||||
$code !== null &&
|
||||
// not LineTerminator
|
||||
$code !== 10 && $code !== 13 &&
|
||||
// not Quote (")
|
||||
$code !== 34
|
||||
$code !== 10 && $code !== 13
|
||||
) {
|
||||
// Closing Quote (")
|
||||
if ($code === 34) {
|
||||
$value .= $chunk;
|
||||
|
||||
// Skip quote
|
||||
$this->moveStringCursor(1, 1);
|
||||
|
||||
return new Token(
|
||||
Token::STRING,
|
||||
$start,
|
||||
$this->position,
|
||||
$line,
|
||||
$col,
|
||||
$prev,
|
||||
$value
|
||||
);
|
||||
}
|
||||
|
||||
$this->assertValidStringCharacterCode($code, $this->position);
|
||||
$this->moveStringCursor(1, $bytes);
|
||||
|
||||
@ -421,7 +446,6 @@ class Lexer
|
||||
list ($char, $code, $bytes) = $this->readChar();
|
||||
}
|
||||
|
||||
if ($code !== 34) {
|
||||
throw new SyntaxError(
|
||||
$this->source,
|
||||
$this->position,
|
||||
@ -429,19 +453,76 @@ class Lexer
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a block string token from the source file.
|
||||
*
|
||||
* """("?"?(\\"""|\\(?!=""")|[^"\\]))*"""
|
||||
*/
|
||||
private function readBlockString($line, $col, Token $prev)
|
||||
{
|
||||
$start = $this->position;
|
||||
|
||||
// Skip leading quotes and read first string char:
|
||||
list ($char, $code, $bytes) = $this->moveStringCursor(3, 3)->readChar();
|
||||
|
||||
$chunk = '';
|
||||
$value = '';
|
||||
|
||||
while ($code !== null) {
|
||||
// Closing Triple-Quote (""")
|
||||
if ($code === 34) {
|
||||
// Move 2 quotes
|
||||
list(,$nextCode) = $this->moveStringCursor(1, 1)->readChar();
|
||||
list(,$nextNextCode) = $this->moveStringCursor(1, 1)->readChar();
|
||||
|
||||
if ($nextCode === 34 && $nextNextCode === 34) {
|
||||
$value .= $chunk;
|
||||
|
||||
// Skip trailing quote:
|
||||
$this->moveStringCursor(1, 1);
|
||||
|
||||
return new Token(
|
||||
Token::STRING,
|
||||
Token::BLOCK_STRING,
|
||||
$start,
|
||||
$this->position,
|
||||
$line,
|
||||
$col,
|
||||
$prev,
|
||||
$value
|
||||
BlockString::value($value)
|
||||
);
|
||||
} else {
|
||||
// move cursor back to before the first quote
|
||||
$this->moveStringCursor(-2, -2);
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertValidBlockStringCharacterCode($code, $this->position);
|
||||
$this->moveStringCursor(1, $bytes);
|
||||
|
||||
list(,$nextCode) = $this->readChar();
|
||||
list(,$nextNextCode) = $this->moveStringCursor(1, 1)->readChar();
|
||||
list(,$nextNextNextCode) = $this->moveStringCursor(1, 1)->readChar();
|
||||
|
||||
// Escape Triple-Quote (\""")
|
||||
if ($code === 92 &&
|
||||
$nextCode === 34 &&
|
||||
$nextNextCode === 34 &&
|
||||
$nextNextNextCode === 34
|
||||
) {
|
||||
$this->moveStringCursor(1, 1);
|
||||
$value .= $chunk . '"""';
|
||||
$chunk = '';
|
||||
} else {
|
||||
$this->moveStringCursor(-2, -2);
|
||||
$chunk .= $char;
|
||||
}
|
||||
|
||||
list ($char, $code, $bytes) = $this->readChar();
|
||||
}
|
||||
|
||||
throw new SyntaxError(
|
||||
$this->source,
|
||||
$this->position,
|
||||
'Unterminated string.'
|
||||
);
|
||||
}
|
||||
|
||||
@ -457,6 +538,18 @@ class Lexer
|
||||
}
|
||||
}
|
||||
|
||||
private function assertValidBlockStringCharacterCode($code, $position)
|
||||
{
|
||||
// SourceCharacter
|
||||
if ($code < 0x0020 && $code !== 0x0009 && $code !== 0x000A && $code !== 0x000D) {
|
||||
throw new SyntaxError(
|
||||
$this->source,
|
||||
$position,
|
||||
'Invalid character within String: ' . Utils::printCharCode($code)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads from body starting at startPosition until it finds a non-whitespace
|
||||
* or commented character, then places cursor to the position of that character.
|
||||
@ -537,7 +630,7 @@ class Lexer
|
||||
$byteStreamPosition = $this->byteStreamPosition;
|
||||
}
|
||||
|
||||
$code = 0;
|
||||
$code = null;
|
||||
$utf8char = '';
|
||||
$bytes = 0;
|
||||
$positionOffset = 0;
|
||||
|
@ -655,9 +655,11 @@ class Parser
|
||||
'loc' => $this->loc($token)
|
||||
]);
|
||||
case Token::STRING:
|
||||
case Token::BLOCK_STRING:
|
||||
$this->lexer->advance();
|
||||
return new StringValueNode([
|
||||
'value' => $token->value,
|
||||
'block' => $token->kind === Token::BLOCK_STRING,
|
||||
'loc' => $this->loc($token)
|
||||
]);
|
||||
case Token::NAME:
|
||||
|
@ -139,6 +139,9 @@ class Printer
|
||||
return $node->value;
|
||||
},
|
||||
NodeKind::STRING => function(StringValueNode $node) {
|
||||
if ($node->block) {
|
||||
return "\"\"\"\n" . str_replace('"""', '\\"""', $node->value) . "\n\"\"\"";
|
||||
}
|
||||
return json_encode($node->value);
|
||||
},
|
||||
NodeKind::BOOLEAN => function(BooleanValueNode $node) {
|
||||
|
@ -27,6 +27,7 @@ class Token
|
||||
const INT = 'Int';
|
||||
const FLOAT = 'Float';
|
||||
const STRING = 'String';
|
||||
const BLOCK_STRING = 'BlockString';
|
||||
const COMMENT = 'Comment';
|
||||
|
||||
/**
|
||||
@ -57,6 +58,7 @@ class Token
|
||||
$description[self::INT] = 'Int';
|
||||
$description[self::FLOAT] = 'Float';
|
||||
$description[self::STRING] = 'String';
|
||||
$description[self::BLOCK_STRING] = 'BlockString';
|
||||
$description[self::COMMENT] = 'Comment';
|
||||
|
||||
return $description[$kind];
|
||||
|
61
src/Utils/BlockString.php
Normal file
61
src/Utils/BlockString.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
namespace GraphQL\Utils;
|
||||
|
||||
class BlockString {
|
||||
/**
|
||||
* Produces the value of a block string from its parsed raw value, similar to
|
||||
* Coffeescript's block string, Python's docstring trim or Ruby's strip_heredoc.
|
||||
*
|
||||
* This implements the GraphQL spec's BlockStringValue() static algorithm.
|
||||
*/
|
||||
public static function value($rawString) {
|
||||
// Expand a block string's raw value into independent lines.
|
||||
$lines = preg_split("/\\r\\n|[\\n\\r]/", $rawString);
|
||||
|
||||
// Remove common indentation from all lines but first.
|
||||
$commonIndent = null;
|
||||
$linesLength = count($lines);
|
||||
|
||||
for ($i = 1; $i < $linesLength; $i++) {
|
||||
$line = $lines[$i];
|
||||
$indent = self::leadingWhitespace($line);
|
||||
|
||||
if (
|
||||
$indent < mb_strlen($line) &&
|
||||
($commonIndent === null || $indent < $commonIndent)
|
||||
) {
|
||||
$commonIndent = $indent;
|
||||
if ($commonIndent === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($commonIndent) {
|
||||
for ($i = 1; $i < $linesLength; $i++) {
|
||||
$line = $lines[$i];
|
||||
$lines[$i] = mb_substr($line, $commonIndent);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove leading and trailing blank lines.
|
||||
while (count($lines) > 0 && trim($lines[0], " \t") === '') {
|
||||
array_shift($lines);
|
||||
}
|
||||
while (count($lines) > 0 && trim($lines[count($lines) - 1], " \t") === '') {
|
||||
array_pop($lines);
|
||||
}
|
||||
|
||||
// Return a string of the lines joined with U+000A.
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
|
||||
private static function leadingWhitespace($str) {
|
||||
$i = 0;
|
||||
while ($i < mb_strlen($str) && ($str[$i] === ' ' || $str[$i] === '\t')) {
|
||||
$i++;
|
||||
}
|
||||
|
||||
return $i;
|
||||
}
|
||||
}
|
@ -223,7 +223,101 @@ class LexerTest extends \PHPUnit_Framework_TestCase
|
||||
], (array) $this->lexOne('"\u1234\u5678\u90AB\uCDEF"'));
|
||||
}
|
||||
|
||||
public function reportsUsefulErrors() {
|
||||
/**
|
||||
* @it lexes block strings
|
||||
*/
|
||||
public function testLexesBlockString()
|
||||
{
|
||||
$this->assertArraySubset([
|
||||
'kind' => Token::BLOCK_STRING,
|
||||
'start' => 0,
|
||||
'end' => 12,
|
||||
'value' => 'simple'
|
||||
], (array) $this->lexOne('"""simple"""'));
|
||||
|
||||
$this->assertArraySubset([
|
||||
'kind' => Token::BLOCK_STRING,
|
||||
'start' => 0,
|
||||
'end' => 19,
|
||||
'value' => ' white space '
|
||||
], (array) $this->lexOne('""" white space """'));
|
||||
|
||||
$this->assertArraySubset([
|
||||
'kind' => Token::BLOCK_STRING,
|
||||
'start' => 0,
|
||||
'end' => 22,
|
||||
'value' => 'contains " quote'
|
||||
], (array) $this->lexOne('"""contains " quote"""'));
|
||||
|
||||
$this->assertArraySubset([
|
||||
'kind' => Token::BLOCK_STRING,
|
||||
'start' => 0,
|
||||
'end' => 31,
|
||||
'value' => 'contains """ triplequote'
|
||||
], (array) $this->lexOne('"""contains \\""" triplequote"""'));
|
||||
|
||||
$this->assertArraySubset([
|
||||
'kind' => Token::BLOCK_STRING,
|
||||
'start' => 0,
|
||||
'end' => 16,
|
||||
'value' => "multi\nline"
|
||||
], (array) $this->lexOne("\"\"\"multi\nline\"\"\""));
|
||||
|
||||
$this->assertArraySubset([
|
||||
'kind' => Token::BLOCK_STRING,
|
||||
'start' => 0,
|
||||
'end' => 28,
|
||||
'value' => "multi\nline\nnormalized"
|
||||
], (array) $this->lexOne("\"\"\"multi\rline\r\nnormalized\"\"\""));
|
||||
|
||||
$this->assertArraySubset([
|
||||
'kind' => Token::BLOCK_STRING,
|
||||
'start' => 0,
|
||||
'end' => 32,
|
||||
'value' => 'unescaped \\n\\r\\b\\t\\f\\u1234'
|
||||
], (array) $this->lexOne('"""unescaped \\n\\r\\b\\t\\f\\u1234"""'));
|
||||
|
||||
$this->assertArraySubset([
|
||||
'kind' => Token::BLOCK_STRING,
|
||||
'start' => 0,
|
||||
'end' => 19,
|
||||
'value' => 'slashes \\\\ \\/'
|
||||
], (array) $this->lexOne('"""slashes \\\\ \\/"""'));
|
||||
|
||||
$this->assertArraySubset([
|
||||
'kind' => Token::BLOCK_STRING,
|
||||
'start' => 0,
|
||||
'end' => 68,
|
||||
'value' => "spans\n multiple\n lines"
|
||||
], (array) $this->lexOne("\"\"\"
|
||||
|
||||
spans
|
||||
multiple
|
||||
lines
|
||||
|
||||
\"\"\""));
|
||||
}
|
||||
|
||||
public function reportsUsefulBlockStringErrors() {
|
||||
return [
|
||||
['"""', "Syntax Error GraphQL (1:4) Unterminated string.\n\n1: \"\"\"\n ^\n"],
|
||||
['"""no end quote', "Syntax Error GraphQL (1:16) Unterminated string.\n\n1: \"\"\"no end quote\n ^\n"],
|
||||
['"""contains unescaped ' . json_decode('"\u0007"') . ' control char"""', "Syntax Error GraphQL (1:23) Invalid character within String: \"\\u0007\""],
|
||||
['"""null-byte is not ' . json_decode('"\u0000"') . ' end of file"""', "Syntax Error GraphQL (1:21) Invalid character within String: \"\\u0000\""],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider reportsUsefulBlockStringErrors
|
||||
* @it lex reports useful block string errors
|
||||
*/
|
||||
public function testReportsUsefulBlockStringErrors($str, $expectedMessage)
|
||||
{
|
||||
$this->setExpectedException(SyntaxError::class, $expectedMessage);
|
||||
$this->lexOne($str);
|
||||
}
|
||||
|
||||
public function reportsUsefulStringErrors() {
|
||||
return [
|
||||
['"', "Syntax Error GraphQL (1:2) Unterminated string.\n\n1: \"\n ^\n"],
|
||||
['"no end quote', "Syntax Error GraphQL (1:14) Unterminated string.\n\n1: \"no end quote\n ^\n"],
|
||||
@ -243,10 +337,10 @@ class LexerTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider reportsUsefulErrors
|
||||
* @dataProvider reportsUsefulStringErrors
|
||||
* @it lex reports useful string errors
|
||||
*/
|
||||
public function testReportsUsefulErrors($str, $expectedMessage)
|
||||
public function testLexReportsUsefulStringErrors($str, $expectedMessage)
|
||||
{
|
||||
$this->setExpectedException(SyntaxError::class, $expectedMessage);
|
||||
$this->lexOne($str);
|
||||
|
@ -497,7 +497,8 @@ fragment $fragmentName on Type {
|
||||
[
|
||||
'kind' => NodeKind::STRING,
|
||||
'loc' => ['start' => 5, 'end' => 10],
|
||||
'value' => 'abc'
|
||||
'value' => 'abc',
|
||||
'block' => false
|
||||
]
|
||||
]
|
||||
], $this->nodeToArray(Parser::parseValue('[123 "abc"]')));
|
||||
|
@ -146,7 +146,9 @@ subscription StoryLikeSubscription($input: StoryLikeSubscribeInput) {
|
||||
}
|
||||
|
||||
fragment frag on Friend {
|
||||
foo(size: $size, bar: $b, obj: {key: "value"})
|
||||
foo(size: $size, bar: $b, obj: {key: "value", block: """
|
||||
block string uses \"""
|
||||
"""})
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -615,6 +615,12 @@ class VisitorTest extends \PHPUnit_Framework_TestCase
|
||||
[ 'enter', 'StringValue', 'value', 'ObjectField' ],
|
||||
[ 'leave', 'StringValue', 'value', 'ObjectField' ],
|
||||
[ 'leave', 'ObjectField', 0, null ],
|
||||
[ 'enter', 'ObjectField', 1, null ],
|
||||
[ 'enter', 'Name', 'name', 'ObjectField' ],
|
||||
[ 'leave', 'Name', 'name', 'ObjectField' ],
|
||||
[ 'enter', 'StringValue', 'value', 'ObjectField' ],
|
||||
[ 'leave', 'StringValue', 'value', 'ObjectField' ],
|
||||
[ 'leave', 'ObjectField', 1, null ],
|
||||
[ 'leave', 'ObjectValue', 'value', 'Argument' ],
|
||||
[ 'leave', 'Argument', 2, null ],
|
||||
[ 'leave', 'Field', 0, null ],
|
||||
|
@ -556,7 +556,20 @@
|
||||
},
|
||||
"value": {
|
||||
"kind": "StringValue",
|
||||
"value": "value"
|
||||
"value": "value",
|
||||
"block": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ObjectField",
|
||||
"name": {
|
||||
"kind": "Name",
|
||||
"value": "block"
|
||||
},
|
||||
"value": {
|
||||
"kind": "StringValue",
|
||||
"value": "block string uses \"\"\"",
|
||||
"block": true
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -2,7 +2,7 @@
|
||||
"kind": "Document",
|
||||
"loc": {
|
||||
"start": 0,
|
||||
"end": 1087
|
||||
"end": 1136
|
||||
},
|
||||
"definitions": [
|
||||
{
|
||||
@ -959,7 +959,7 @@
|
||||
"kind": "FragmentDefinition",
|
||||
"loc": {
|
||||
"start": 942,
|
||||
"end": 1018
|
||||
"end": 1067
|
||||
},
|
||||
"name": {
|
||||
"kind": "Name",
|
||||
@ -989,14 +989,14 @@
|
||||
"kind": "SelectionSet",
|
||||
"loc": {
|
||||
"start": 966,
|
||||
"end": 1018
|
||||
"end": 1067
|
||||
},
|
||||
"selections": [
|
||||
{
|
||||
"kind": "Field",
|
||||
"loc": {
|
||||
"start": 970,
|
||||
"end": 1016
|
||||
"end": 1065
|
||||
},
|
||||
"name": {
|
||||
"kind": "Name",
|
||||
@ -1071,13 +1071,13 @@
|
||||
"kind": "Argument",
|
||||
"loc": {
|
||||
"start": 996,
|
||||
"end": 1015
|
||||
"end": 1064
|
||||
},
|
||||
"value": {
|
||||
"kind": "ObjectValue",
|
||||
"loc": {
|
||||
"start": 1001,
|
||||
"end": 1015
|
||||
"end": 1064
|
||||
},
|
||||
"fields": [
|
||||
{
|
||||
@ -1100,7 +1100,32 @@
|
||||
"start": 1007,
|
||||
"end": 1014
|
||||
},
|
||||
"value": "value"
|
||||
"value": "value",
|
||||
"block": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "ObjectField",
|
||||
"loc": {
|
||||
"start": 1016,
|
||||
"end": 1063
|
||||
},
|
||||
"name": {
|
||||
"kind": "Name",
|
||||
"loc": {
|
||||
"start": 1016,
|
||||
"end": 1021
|
||||
},
|
||||
"value": "block"
|
||||
},
|
||||
"value": {
|
||||
"kind": "StringValue",
|
||||
"loc": {
|
||||
"start": 1023,
|
||||
"end": 1063
|
||||
},
|
||||
"value": "block string uses \"\"\"",
|
||||
"block": true
|
||||
}
|
||||
}
|
||||
]
|
||||
@ -1123,8 +1148,8 @@
|
||||
{
|
||||
"kind": "OperationDefinition",
|
||||
"loc": {
|
||||
"start": 1020,
|
||||
"end": 1086
|
||||
"start": 1069,
|
||||
"end": 1135
|
||||
},
|
||||
"operation": "query",
|
||||
"variableDefinitions": [],
|
||||
@ -1132,21 +1157,21 @@
|
||||
"selectionSet": {
|
||||
"kind": "SelectionSet",
|
||||
"loc": {
|
||||
"start": 1020,
|
||||
"end": 1086
|
||||
"start": 1069,
|
||||
"end": 1135
|
||||
},
|
||||
"selections": [
|
||||
{
|
||||
"kind": "Field",
|
||||
"loc": {
|
||||
"start": 1024,
|
||||
"end": 1075
|
||||
"start": 1073,
|
||||
"end": 1124
|
||||
},
|
||||
"name": {
|
||||
"kind": "Name",
|
||||
"loc": {
|
||||
"start": 1024,
|
||||
"end": 1031
|
||||
"start": 1073,
|
||||
"end": 1080
|
||||
},
|
||||
"value": "unnamed"
|
||||
},
|
||||
@ -1154,22 +1179,22 @@
|
||||
{
|
||||
"kind": "Argument",
|
||||
"loc": {
|
||||
"start": 1032,
|
||||
"end": 1044
|
||||
"start": 1081,
|
||||
"end": 1093
|
||||
},
|
||||
"value": {
|
||||
"kind": "BooleanValue",
|
||||
"loc": {
|
||||
"start": 1040,
|
||||
"end": 1044
|
||||
"start": 1089,
|
||||
"end": 1093
|
||||
},
|
||||
"value": true
|
||||
},
|
||||
"name": {
|
||||
"kind": "Name",
|
||||
"loc": {
|
||||
"start": 1032,
|
||||
"end": 1038
|
||||
"start": 1081,
|
||||
"end": 1087
|
||||
},
|
||||
"value": "truthy"
|
||||
}
|
||||
@ -1177,22 +1202,22 @@
|
||||
{
|
||||
"kind": "Argument",
|
||||
"loc": {
|
||||
"start": 1046,
|
||||
"end": 1059
|
||||
"start": 1095,
|
||||
"end": 1108
|
||||
},
|
||||
"value": {
|
||||
"kind": "BooleanValue",
|
||||
"loc": {
|
||||
"start": 1054,
|
||||
"end": 1059
|
||||
"start": 1103,
|
||||
"end": 1108
|
||||
},
|
||||
"value": false
|
||||
},
|
||||
"name": {
|
||||
"kind": "Name",
|
||||
"loc": {
|
||||
"start": 1046,
|
||||
"end": 1052
|
||||
"start": 1095,
|
||||
"end": 1101
|
||||
},
|
||||
"value": "falsey"
|
||||
}
|
||||
@ -1200,21 +1225,21 @@
|
||||
{
|
||||
"kind": "Argument",
|
||||
"loc": {
|
||||
"start": 1061,
|
||||
"end": 1074
|
||||
"start": 1110,
|
||||
"end": 1123
|
||||
},
|
||||
"value": {
|
||||
"kind": "NullValue",
|
||||
"loc": {
|
||||
"start": 1070,
|
||||
"end": 1074
|
||||
"start": 1119,
|
||||
"end": 1123
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"kind": "Name",
|
||||
"loc": {
|
||||
"start": 1061,
|
||||
"end": 1068
|
||||
"start": 1110,
|
||||
"end": 1117
|
||||
},
|
||||
"value": "nullish"
|
||||
}
|
||||
@ -1225,14 +1250,14 @@
|
||||
{
|
||||
"kind": "Field",
|
||||
"loc": {
|
||||
"start": 1079,
|
||||
"end": 1084
|
||||
"start": 1128,
|
||||
"end": 1133
|
||||
},
|
||||
"name": {
|
||||
"kind": "Name",
|
||||
"loc": {
|
||||
"start": 1079,
|
||||
"end": 1084
|
||||
"start": 1128,
|
||||
"end": 1133
|
||||
},
|
||||
"value": "query"
|
||||
},
|
||||
|
@ -48,7 +48,11 @@ subscription StoryLikeSubscription($input: StoryLikeSubscribeInput) {
|
||||
}
|
||||
|
||||
fragment frag on Friend {
|
||||
foo(size: $size, bar: $b, obj: {key: "value"})
|
||||
foo(size: $size, bar: $b, obj: {key: "value", block: """
|
||||
|
||||
block string uses \"""
|
||||
|
||||
"""})
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -1,9 +1,7 @@
|
||||
# Copyright (c) 2015, Facebook, Inc.
|
||||
# All rights reserved.
|
||||
# Copyright (c) 2015-present, Facebook, Inc.
|
||||
#
|
||||
# This source code is licensed under the BSD-style license found in the
|
||||
# LICENSE file in the root directory of this source tree. An additional grant
|
||||
# of patent rights can be found in the PATENTS file in the same directory.
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
schema {
|
||||
query: QueryType
|
||||
|
Loading…
Reference in New Issue
Block a user