mirror of
https://github.com/retailcrm/graphql-php.git
synced 2024-11-25 06:16:05 +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
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $value;
|
public $value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var boolean|null
|
||||||
|
*/
|
||||||
|
public $block;
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ namespace GraphQL\Language;
|
|||||||
|
|
||||||
use GraphQL\Error\SyntaxError;
|
use GraphQL\Error\SyntaxError;
|
||||||
use GraphQL\Utils\Utils;
|
use GraphQL\Utils\Utils;
|
||||||
|
use GraphQL\Utils\BlockString;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Lexer is a stateful stream generator in that every time
|
* A Lexer is a stateful stream generator in that every time
|
||||||
@ -201,7 +202,15 @@ class Lexer
|
|||||||
->readNumber($line, $col, $prev);
|
->readNumber($line, $col, $prev);
|
||||||
// "
|
// "
|
||||||
case 34:
|
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);
|
->readString($line, $col, $prev);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,12 +379,28 @@ class Lexer
|
|||||||
$value = '';
|
$value = '';
|
||||||
|
|
||||||
while (
|
while (
|
||||||
$code &&
|
$code !== null &&
|
||||||
// not LineTerminator
|
// not LineTerminator
|
||||||
$code !== 10 && $code !== 13 &&
|
$code !== 10 && $code !== 13
|
||||||
// not Quote (")
|
|
||||||
$code !== 34
|
|
||||||
) {
|
) {
|
||||||
|
// 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->assertValidStringCharacterCode($code, $this->position);
|
||||||
$this->moveStringCursor(1, $bytes);
|
$this->moveStringCursor(1, $bytes);
|
||||||
|
|
||||||
@ -421,27 +446,83 @@ class Lexer
|
|||||||
list ($char, $code, $bytes) = $this->readChar();
|
list ($char, $code, $bytes) = $this->readChar();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($code !== 34) {
|
throw new SyntaxError(
|
||||||
throw new SyntaxError(
|
$this->source,
|
||||||
$this->source,
|
$this->position,
|
||||||
$this->position,
|
'Unterminated string.'
|
||||||
'Unterminated string.'
|
);
|
||||||
);
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
$this->moveStringCursor(1, 1);
|
||||||
|
|
||||||
|
return new Token(
|
||||||
|
Token::BLOCK_STRING,
|
||||||
|
$start,
|
||||||
|
$this->position,
|
||||||
|
$line,
|
||||||
|
$col,
|
||||||
|
$prev,
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
$value .= $chunk;
|
throw new SyntaxError(
|
||||||
|
$this->source,
|
||||||
// Skip trailing quote:
|
|
||||||
$this->moveStringCursor(1, 1);
|
|
||||||
|
|
||||||
return new Token(
|
|
||||||
Token::STRING,
|
|
||||||
$start,
|
|
||||||
$this->position,
|
$this->position,
|
||||||
$line,
|
'Unterminated string.'
|
||||||
$col,
|
|
||||||
$prev,
|
|
||||||
$value
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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
|
* Reads from body starting at startPosition until it finds a non-whitespace
|
||||||
* or commented character, then places cursor to the position of that character.
|
* or commented character, then places cursor to the position of that character.
|
||||||
@ -537,7 +630,7 @@ class Lexer
|
|||||||
$byteStreamPosition = $this->byteStreamPosition;
|
$byteStreamPosition = $this->byteStreamPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
$code = 0;
|
$code = null;
|
||||||
$utf8char = '';
|
$utf8char = '';
|
||||||
$bytes = 0;
|
$bytes = 0;
|
||||||
$positionOffset = 0;
|
$positionOffset = 0;
|
||||||
|
@ -655,9 +655,11 @@ class Parser
|
|||||||
'loc' => $this->loc($token)
|
'loc' => $this->loc($token)
|
||||||
]);
|
]);
|
||||||
case Token::STRING:
|
case Token::STRING:
|
||||||
|
case Token::BLOCK_STRING:
|
||||||
$this->lexer->advance();
|
$this->lexer->advance();
|
||||||
return new StringValueNode([
|
return new StringValueNode([
|
||||||
'value' => $token->value,
|
'value' => $token->value,
|
||||||
|
'block' => $token->kind === Token::BLOCK_STRING,
|
||||||
'loc' => $this->loc($token)
|
'loc' => $this->loc($token)
|
||||||
]);
|
]);
|
||||||
case Token::NAME:
|
case Token::NAME:
|
||||||
|
@ -139,6 +139,9 @@ class Printer
|
|||||||
return $node->value;
|
return $node->value;
|
||||||
},
|
},
|
||||||
NodeKind::STRING => function(StringValueNode $node) {
|
NodeKind::STRING => function(StringValueNode $node) {
|
||||||
|
if ($node->block) {
|
||||||
|
return "\"\"\"\n" . str_replace('"""', '\\"""', $node->value) . "\n\"\"\"";
|
||||||
|
}
|
||||||
return json_encode($node->value);
|
return json_encode($node->value);
|
||||||
},
|
},
|
||||||
NodeKind::BOOLEAN => function(BooleanValueNode $node) {
|
NodeKind::BOOLEAN => function(BooleanValueNode $node) {
|
||||||
|
@ -27,6 +27,7 @@ class Token
|
|||||||
const INT = 'Int';
|
const INT = 'Int';
|
||||||
const FLOAT = 'Float';
|
const FLOAT = 'Float';
|
||||||
const STRING = 'String';
|
const STRING = 'String';
|
||||||
|
const BLOCK_STRING = 'BlockString';
|
||||||
const COMMENT = 'Comment';
|
const COMMENT = 'Comment';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -57,6 +58,7 @@ class Token
|
|||||||
$description[self::INT] = 'Int';
|
$description[self::INT] = 'Int';
|
||||||
$description[self::FLOAT] = 'Float';
|
$description[self::FLOAT] = 'Float';
|
||||||
$description[self::STRING] = 'String';
|
$description[self::STRING] = 'String';
|
||||||
|
$description[self::BLOCK_STRING] = 'BlockString';
|
||||||
$description[self::COMMENT] = 'Comment';
|
$description[self::COMMENT] = 'Comment';
|
||||||
|
|
||||||
return $description[$kind];
|
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"'));
|
], (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 [
|
return [
|
||||||
['"', "Syntax Error GraphQL (1:2) Unterminated string.\n\n1: \"\n ^\n"],
|
['"', "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"],
|
['"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
|
* @it lex reports useful string errors
|
||||||
*/
|
*/
|
||||||
public function testReportsUsefulErrors($str, $expectedMessage)
|
public function testLexReportsUsefulStringErrors($str, $expectedMessage)
|
||||||
{
|
{
|
||||||
$this->setExpectedException(SyntaxError::class, $expectedMessage);
|
$this->setExpectedException(SyntaxError::class, $expectedMessage);
|
||||||
$this->lexOne($str);
|
$this->lexOne($str);
|
||||||
|
@ -497,7 +497,8 @@ fragment $fragmentName on Type {
|
|||||||
[
|
[
|
||||||
'kind' => NodeKind::STRING,
|
'kind' => NodeKind::STRING,
|
||||||
'loc' => ['start' => 5, 'end' => 10],
|
'loc' => ['start' => 5, 'end' => 10],
|
||||||
'value' => 'abc'
|
'value' => 'abc',
|
||||||
|
'block' => false
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
], $this->nodeToArray(Parser::parseValue('[123 "abc"]')));
|
], $this->nodeToArray(Parser::parseValue('[123 "abc"]')));
|
||||||
|
@ -146,7 +146,9 @@ subscription StoryLikeSubscription($input: StoryLikeSubscribeInput) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fragment frag on Friend {
|
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' ],
|
[ 'enter', 'StringValue', 'value', 'ObjectField' ],
|
||||||
[ 'leave', 'StringValue', 'value', 'ObjectField' ],
|
[ 'leave', 'StringValue', 'value', 'ObjectField' ],
|
||||||
[ 'leave', 'ObjectField', 0, null ],
|
[ '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', 'ObjectValue', 'value', 'Argument' ],
|
||||||
[ 'leave', 'Argument', 2, null ],
|
[ 'leave', 'Argument', 2, null ],
|
||||||
[ 'leave', 'Field', 0, null ],
|
[ 'leave', 'Field', 0, null ],
|
||||||
|
@ -556,7 +556,20 @@
|
|||||||
},
|
},
|
||||||
"value": {
|
"value": {
|
||||||
"kind": "StringValue",
|
"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",
|
"kind": "Document",
|
||||||
"loc": {
|
"loc": {
|
||||||
"start": 0,
|
"start": 0,
|
||||||
"end": 1087
|
"end": 1136
|
||||||
},
|
},
|
||||||
"definitions": [
|
"definitions": [
|
||||||
{
|
{
|
||||||
@ -959,7 +959,7 @@
|
|||||||
"kind": "FragmentDefinition",
|
"kind": "FragmentDefinition",
|
||||||
"loc": {
|
"loc": {
|
||||||
"start": 942,
|
"start": 942,
|
||||||
"end": 1018
|
"end": 1067
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"kind": "Name",
|
"kind": "Name",
|
||||||
@ -989,14 +989,14 @@
|
|||||||
"kind": "SelectionSet",
|
"kind": "SelectionSet",
|
||||||
"loc": {
|
"loc": {
|
||||||
"start": 966,
|
"start": 966,
|
||||||
"end": 1018
|
"end": 1067
|
||||||
},
|
},
|
||||||
"selections": [
|
"selections": [
|
||||||
{
|
{
|
||||||
"kind": "Field",
|
"kind": "Field",
|
||||||
"loc": {
|
"loc": {
|
||||||
"start": 970,
|
"start": 970,
|
||||||
"end": 1016
|
"end": 1065
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"kind": "Name",
|
"kind": "Name",
|
||||||
@ -1071,13 +1071,13 @@
|
|||||||
"kind": "Argument",
|
"kind": "Argument",
|
||||||
"loc": {
|
"loc": {
|
||||||
"start": 996,
|
"start": 996,
|
||||||
"end": 1015
|
"end": 1064
|
||||||
},
|
},
|
||||||
"value": {
|
"value": {
|
||||||
"kind": "ObjectValue",
|
"kind": "ObjectValue",
|
||||||
"loc": {
|
"loc": {
|
||||||
"start": 1001,
|
"start": 1001,
|
||||||
"end": 1015
|
"end": 1064
|
||||||
},
|
},
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@ -1100,7 +1100,32 @@
|
|||||||
"start": 1007,
|
"start": 1007,
|
||||||
"end": 1014
|
"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",
|
"kind": "OperationDefinition",
|
||||||
"loc": {
|
"loc": {
|
||||||
"start": 1020,
|
"start": 1069,
|
||||||
"end": 1086
|
"end": 1135
|
||||||
},
|
},
|
||||||
"operation": "query",
|
"operation": "query",
|
||||||
"variableDefinitions": [],
|
"variableDefinitions": [],
|
||||||
@ -1132,21 +1157,21 @@
|
|||||||
"selectionSet": {
|
"selectionSet": {
|
||||||
"kind": "SelectionSet",
|
"kind": "SelectionSet",
|
||||||
"loc": {
|
"loc": {
|
||||||
"start": 1020,
|
"start": 1069,
|
||||||
"end": 1086
|
"end": 1135
|
||||||
},
|
},
|
||||||
"selections": [
|
"selections": [
|
||||||
{
|
{
|
||||||
"kind": "Field",
|
"kind": "Field",
|
||||||
"loc": {
|
"loc": {
|
||||||
"start": 1024,
|
"start": 1073,
|
||||||
"end": 1075
|
"end": 1124
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"kind": "Name",
|
"kind": "Name",
|
||||||
"loc": {
|
"loc": {
|
||||||
"start": 1024,
|
"start": 1073,
|
||||||
"end": 1031
|
"end": 1080
|
||||||
},
|
},
|
||||||
"value": "unnamed"
|
"value": "unnamed"
|
||||||
},
|
},
|
||||||
@ -1154,22 +1179,22 @@
|
|||||||
{
|
{
|
||||||
"kind": "Argument",
|
"kind": "Argument",
|
||||||
"loc": {
|
"loc": {
|
||||||
"start": 1032,
|
"start": 1081,
|
||||||
"end": 1044
|
"end": 1093
|
||||||
},
|
},
|
||||||
"value": {
|
"value": {
|
||||||
"kind": "BooleanValue",
|
"kind": "BooleanValue",
|
||||||
"loc": {
|
"loc": {
|
||||||
"start": 1040,
|
"start": 1089,
|
||||||
"end": 1044
|
"end": 1093
|
||||||
},
|
},
|
||||||
"value": true
|
"value": true
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"kind": "Name",
|
"kind": "Name",
|
||||||
"loc": {
|
"loc": {
|
||||||
"start": 1032,
|
"start": 1081,
|
||||||
"end": 1038
|
"end": 1087
|
||||||
},
|
},
|
||||||
"value": "truthy"
|
"value": "truthy"
|
||||||
}
|
}
|
||||||
@ -1177,22 +1202,22 @@
|
|||||||
{
|
{
|
||||||
"kind": "Argument",
|
"kind": "Argument",
|
||||||
"loc": {
|
"loc": {
|
||||||
"start": 1046,
|
"start": 1095,
|
||||||
"end": 1059
|
"end": 1108
|
||||||
},
|
},
|
||||||
"value": {
|
"value": {
|
||||||
"kind": "BooleanValue",
|
"kind": "BooleanValue",
|
||||||
"loc": {
|
"loc": {
|
||||||
"start": 1054,
|
"start": 1103,
|
||||||
"end": 1059
|
"end": 1108
|
||||||
},
|
},
|
||||||
"value": false
|
"value": false
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"kind": "Name",
|
"kind": "Name",
|
||||||
"loc": {
|
"loc": {
|
||||||
"start": 1046,
|
"start": 1095,
|
||||||
"end": 1052
|
"end": 1101
|
||||||
},
|
},
|
||||||
"value": "falsey"
|
"value": "falsey"
|
||||||
}
|
}
|
||||||
@ -1200,21 +1225,21 @@
|
|||||||
{
|
{
|
||||||
"kind": "Argument",
|
"kind": "Argument",
|
||||||
"loc": {
|
"loc": {
|
||||||
"start": 1061,
|
"start": 1110,
|
||||||
"end": 1074
|
"end": 1123
|
||||||
},
|
},
|
||||||
"value": {
|
"value": {
|
||||||
"kind": "NullValue",
|
"kind": "NullValue",
|
||||||
"loc": {
|
"loc": {
|
||||||
"start": 1070,
|
"start": 1119,
|
||||||
"end": 1074
|
"end": 1123
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"kind": "Name",
|
"kind": "Name",
|
||||||
"loc": {
|
"loc": {
|
||||||
"start": 1061,
|
"start": 1110,
|
||||||
"end": 1068
|
"end": 1117
|
||||||
},
|
},
|
||||||
"value": "nullish"
|
"value": "nullish"
|
||||||
}
|
}
|
||||||
@ -1225,14 +1250,14 @@
|
|||||||
{
|
{
|
||||||
"kind": "Field",
|
"kind": "Field",
|
||||||
"loc": {
|
"loc": {
|
||||||
"start": 1079,
|
"start": 1128,
|
||||||
"end": 1084
|
"end": 1133
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"kind": "Name",
|
"kind": "Name",
|
||||||
"loc": {
|
"loc": {
|
||||||
"start": 1079,
|
"start": 1128,
|
||||||
"end": 1084
|
"end": 1133
|
||||||
},
|
},
|
||||||
"value": "query"
|
"value": "query"
|
||||||
},
|
},
|
||||||
|
@ -48,7 +48,11 @@ subscription StoryLikeSubscription($input: StoryLikeSubscribeInput) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fragment frag on Friend {
|
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.
|
# Copyright (c) 2015-present, Facebook, Inc.
|
||||||
# All rights reserved.
|
|
||||||
#
|
#
|
||||||
# This source code is licensed under the BSD-style license found in the
|
# This source code is licensed under the MIT license found in the
|
||||||
# LICENSE file in the root directory of this source tree. An additional grant
|
# LICENSE file in the root directory of this source tree.
|
||||||
# of patent rights can be found in the PATENTS file in the same directory.
|
|
||||||
|
|
||||||
schema {
|
schema {
|
||||||
query: QueryType
|
query: QueryType
|
||||||
|
Loading…
Reference in New Issue
Block a user