AST
2 months ago
BlockString.php
2 months ago
DirectiveLocation.php
2 months ago
Lexer.php
2 months ago
Parser.php
2 months ago
Printer.php
2 months ago
Source.php
2 months ago
SourceLocation.php
2 months ago
Token.php
2 months ago
Visitor.php
2 months ago
VisitorOperation.php
2 months ago
VisitorRemoveNode.php
2 months ago
VisitorSkipNode.php
2 months ago
VisitorStop.php
2 months ago
Printer.php
526 lines
| 1 | <?php declare(strict_types=1); |
| 2 | |
| 3 | namespace Automattic\WooCommerce\Vendor\GraphQL\Language; |
| 4 | |
| 5 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\ArgumentNode; |
| 6 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\BooleanValueNode; |
| 7 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\DirectiveDefinitionNode; |
| 8 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\DirectiveNode; |
| 9 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\DocumentNode; |
| 10 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\EnumTypeDefinitionNode; |
| 11 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\EnumTypeExtensionNode; |
| 12 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\EnumValueDefinitionNode; |
| 13 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\EnumValueNode; |
| 14 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\FieldDefinitionNode; |
| 15 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\FieldNode; |
| 16 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\FloatValueNode; |
| 17 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\FragmentDefinitionNode; |
| 18 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\FragmentSpreadNode; |
| 19 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\InlineFragmentNode; |
| 20 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\InputObjectTypeDefinitionNode; |
| 21 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\InputObjectTypeExtensionNode; |
| 22 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\InputValueDefinitionNode; |
| 23 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\InterfaceTypeDefinitionNode; |
| 24 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\InterfaceTypeExtensionNode; |
| 25 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\IntValueNode; |
| 26 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\ListTypeNode; |
| 27 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\ListValueNode; |
| 28 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\NamedTypeNode; |
| 29 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\NameNode; |
| 30 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\Node; |
| 31 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\NodeList; |
| 32 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\NonNullTypeNode; |
| 33 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\NullValueNode; |
| 34 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\ObjectFieldNode; |
| 35 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\ObjectTypeDefinitionNode; |
| 36 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\ObjectTypeExtensionNode; |
| 37 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\ObjectValueNode; |
| 38 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\OperationDefinitionNode; |
| 39 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\OperationTypeDefinitionNode; |
| 40 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\ScalarTypeDefinitionNode; |
| 41 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\ScalarTypeExtensionNode; |
| 42 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\SchemaDefinitionNode; |
| 43 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\SchemaExtensionNode; |
| 44 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\SelectionSetNode; |
| 45 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\StringValueNode; |
| 46 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\UnionTypeDefinitionNode; |
| 47 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\UnionTypeExtensionNode; |
| 48 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\VariableDefinitionNode; |
| 49 | use Automattic\WooCommerce\Vendor\GraphQL\Language\AST\VariableNode; |
| 50 | |
| 51 | /** |
| 52 | * Prints AST to string. Capable of printing Automattic\WooCommerce\Vendor\GraphQL queries and Type definition language. |
| 53 | * Useful for pretty-printing queries or printing back AST for logging, documentation, etc. |
| 54 | * |
| 55 | * Usage example: |
| 56 | * |
| 57 | * ```php |
| 58 | * $query = 'query myQuery {someField}'; |
| 59 | * $ast = Automattic\WooCommerce\Vendor\GraphQL\Language\Parser::parse($query); |
| 60 | * $printed = Automattic\WooCommerce\Vendor\GraphQL\Language\Printer::doPrint($ast); |
| 61 | * ``` |
| 62 | * |
| 63 | * @see \Automattic\WooCommerce\Vendor\GraphQL\Tests\Language\PrinterTest |
| 64 | */ |
| 65 | class Printer |
| 66 | { |
| 67 | /** |
| 68 | * Converts the AST of a Automattic\WooCommerce\Vendor\GraphQL node to a string. |
| 69 | * |
| 70 | * Handles both executable definitions and schema definitions. |
| 71 | * |
| 72 | * @throws \JsonException |
| 73 | * |
| 74 | * @api |
| 75 | */ |
| 76 | public static function doPrint(Node $ast): string |
| 77 | { |
| 78 | return static::p($ast); |
| 79 | } |
| 80 | |
| 81 | /** @throws \JsonException */ |
| 82 | protected static function p(?Node $node): string |
| 83 | { |
| 84 | if ($node === null) { |
| 85 | return ''; |
| 86 | } |
| 87 | |
| 88 | switch (true) { |
| 89 | case $node instanceof ArgumentNode: |
| 90 | case $node instanceof ObjectFieldNode: |
| 91 | return static::p($node->name) . ': ' . static::p($node->value); |
| 92 | |
| 93 | case $node instanceof BooleanValueNode: |
| 94 | return $node->value |
| 95 | ? 'true' |
| 96 | : 'false'; |
| 97 | |
| 98 | case $node instanceof DirectiveDefinitionNode: |
| 99 | $argStrings = []; |
| 100 | foreach ($node->arguments as $arg) { |
| 101 | $argStrings[] = static::p($arg); |
| 102 | } |
| 103 | |
| 104 | $noIndent = true; |
| 105 | foreach ($argStrings as $argString) { |
| 106 | if (strpos($argString, "\n") !== false) { |
| 107 | $noIndent = false; |
| 108 | break; |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | return static::addDescription($node->description, 'directive @' |
| 113 | . static::p($node->name) |
| 114 | . ($noIndent |
| 115 | ? static::wrap('(', static::join($argStrings, ', '), ')') |
| 116 | : static::wrap("(\n", static::indent(static::join($argStrings, "\n")), "\n")) |
| 117 | . ($node->repeatable |
| 118 | ? ' repeatable' |
| 119 | : '') |
| 120 | . ' on ' . static::printList($node->locations, ' | ')); |
| 121 | |
| 122 | case $node instanceof DirectiveNode: |
| 123 | return '@' . static::p($node->name) . static::wrap('(', static::printList($node->arguments, ', '), ')'); |
| 124 | |
| 125 | case $node instanceof DocumentNode: |
| 126 | return static::printList($node->definitions, "\n\n") . "\n"; |
| 127 | |
| 128 | case $node instanceof EnumTypeDefinitionNode: |
| 129 | return static::addDescription($node->description, static::join( |
| 130 | [ |
| 131 | 'enum', |
| 132 | static::p($node->name), |
| 133 | static::printList($node->directives, ' '), |
| 134 | static::printListBlock($node->values), |
| 135 | ], |
| 136 | ' ' |
| 137 | )); |
| 138 | |
| 139 | case $node instanceof EnumTypeExtensionNode: |
| 140 | return static::join( |
| 141 | [ |
| 142 | 'extend enum', |
| 143 | static::p($node->name), |
| 144 | static::printList($node->directives, ' '), |
| 145 | static::printListBlock($node->values), |
| 146 | ], |
| 147 | ' ' |
| 148 | ); |
| 149 | |
| 150 | case $node instanceof EnumValueDefinitionNode: |
| 151 | return static::addDescription( |
| 152 | $node->description, |
| 153 | static::join([static::p($node->name), static::printList($node->directives, ' ')], ' ') |
| 154 | ); |
| 155 | |
| 156 | case $node instanceof EnumValueNode: |
| 157 | case $node instanceof FloatValueNode: |
| 158 | case $node instanceof IntValueNode: |
| 159 | case $node instanceof NameNode: |
| 160 | return $node->value; |
| 161 | |
| 162 | case $node instanceof FieldDefinitionNode: |
| 163 | $argStrings = []; |
| 164 | foreach ($node->arguments as $item) { |
| 165 | $argStrings[] = static::p($item); |
| 166 | } |
| 167 | |
| 168 | $noIndent = true; |
| 169 | foreach ($argStrings as $argString) { |
| 170 | if (strpos($argString, "\n") !== false) { |
| 171 | $noIndent = false; |
| 172 | break; |
| 173 | } |
| 174 | } |
| 175 | |
| 176 | return static::addDescription( |
| 177 | $node->description, |
| 178 | static::p($node->name) |
| 179 | . ($noIndent |
| 180 | ? static::wrap('(', static::join($argStrings, ', '), ')') |
| 181 | : static::wrap("(\n", static::indent(static::join($argStrings, "\n")), "\n)")) |
| 182 | . ': ' . static::p($node->type) |
| 183 | . static::wrap(' ', static::printList($node->directives, ' ')) |
| 184 | ); |
| 185 | |
| 186 | case $node instanceof FieldNode: |
| 187 | $prefix = static::wrap('', $node->alias->value ?? null, ': ') . static::p($node->name); |
| 188 | |
| 189 | $argsLine = $prefix . static::wrap( |
| 190 | '(', |
| 191 | static::printList($node->arguments, ', '), |
| 192 | ')' |
| 193 | ); |
| 194 | if (strlen($argsLine) > 80) { |
| 195 | $argsLine = $prefix . static::wrap( |
| 196 | "(\n", |
| 197 | static::indent( |
| 198 | static::printList($node->arguments, "\n") |
| 199 | ), |
| 200 | "\n)" |
| 201 | ); |
| 202 | } |
| 203 | |
| 204 | return static::join( |
| 205 | [ |
| 206 | $argsLine, |
| 207 | static::printList($node->directives, ' '), |
| 208 | static::p($node->selectionSet), |
| 209 | ], |
| 210 | ' ' |
| 211 | ); |
| 212 | |
| 213 | case $node instanceof FragmentDefinitionNode: |
| 214 | // Note: fragment variable definitions are experimental and may be changed or removed in the future. |
| 215 | return 'fragment ' . static::p($node->name) |
| 216 | . static::wrap( |
| 217 | '(', |
| 218 | static::printList($node->variableDefinitions ?? new NodeList([]), ', '), |
| 219 | ')' |
| 220 | ) |
| 221 | . ' on ' . static::p($node->typeCondition->name) . ' ' |
| 222 | . static::wrap( |
| 223 | '', |
| 224 | static::printList($node->directives, ' '), |
| 225 | ' ' |
| 226 | ) |
| 227 | . static::p($node->selectionSet); |
| 228 | |
| 229 | case $node instanceof FragmentSpreadNode: |
| 230 | return '...' |
| 231 | . static::p($node->name) |
| 232 | . static::wrap(' ', static::printList($node->directives, ' ')); |
| 233 | |
| 234 | case $node instanceof InlineFragmentNode: |
| 235 | return static::join( |
| 236 | [ |
| 237 | '...', |
| 238 | static::wrap('on ', static::p($node->typeCondition->name ?? null)), |
| 239 | static::printList($node->directives, ' '), |
| 240 | static::p($node->selectionSet), |
| 241 | ], |
| 242 | ' ' |
| 243 | ); |
| 244 | |
| 245 | case $node instanceof InputObjectTypeDefinitionNode: |
| 246 | return static::addDescription($node->description, static::join( |
| 247 | [ |
| 248 | 'input', |
| 249 | static::p($node->name), |
| 250 | static::printList($node->directives, ' '), |
| 251 | static::printListBlock($node->fields), |
| 252 | ], |
| 253 | ' ' |
| 254 | )); |
| 255 | |
| 256 | case $node instanceof InputObjectTypeExtensionNode: |
| 257 | return static::join( |
| 258 | [ |
| 259 | 'extend input', |
| 260 | static::p($node->name), |
| 261 | static::printList($node->directives, ' '), |
| 262 | static::printListBlock($node->fields), |
| 263 | ], |
| 264 | ' ' |
| 265 | ); |
| 266 | |
| 267 | case $node instanceof InputValueDefinitionNode: |
| 268 | return static::addDescription($node->description, static::join( |
| 269 | [ |
| 270 | static::p($node->name) . ': ' . static::p($node->type), |
| 271 | static::wrap('= ', static::p($node->defaultValue)), |
| 272 | static::printList($node->directives, ' '), |
| 273 | ], |
| 274 | ' ' |
| 275 | )); |
| 276 | |
| 277 | case $node instanceof InterfaceTypeDefinitionNode: |
| 278 | return static::addDescription($node->description, static::join( |
| 279 | [ |
| 280 | 'interface', |
| 281 | static::p($node->name), |
| 282 | static::wrap('implements ', static::printList($node->interfaces, ' & ')), |
| 283 | static::printList($node->directives, ' '), |
| 284 | static::printListBlock($node->fields), |
| 285 | ], |
| 286 | ' ' |
| 287 | )); |
| 288 | |
| 289 | case $node instanceof InterfaceTypeExtensionNode: |
| 290 | return static::join( |
| 291 | [ |
| 292 | 'extend interface', |
| 293 | static::p($node->name), |
| 294 | static::wrap('implements ', static::printList($node->interfaces, ' & ')), |
| 295 | static::printList($node->directives, ' '), |
| 296 | static::printListBlock($node->fields), |
| 297 | ], |
| 298 | ' ' |
| 299 | ); |
| 300 | |
| 301 | case $node instanceof ListTypeNode: |
| 302 | return '[' . static::p($node->type) . ']'; |
| 303 | |
| 304 | case $node instanceof ListValueNode: |
| 305 | return '[' . static::printList($node->values, ', ') . ']'; |
| 306 | |
| 307 | case $node instanceof NamedTypeNode: |
| 308 | return static::p($node->name); |
| 309 | |
| 310 | case $node instanceof NonNullTypeNode: |
| 311 | return static::p($node->type) . '!'; |
| 312 | |
| 313 | case $node instanceof NullValueNode: |
| 314 | return 'null'; |
| 315 | |
| 316 | case $node instanceof ObjectTypeDefinitionNode: |
| 317 | return static::addDescription($node->description, static::join( |
| 318 | [ |
| 319 | 'type', |
| 320 | static::p($node->name), |
| 321 | static::wrap('implements ', static::printList($node->interfaces, ' & ')), |
| 322 | static::printList($node->directives, ' '), |
| 323 | static::printListBlock($node->fields), |
| 324 | ], |
| 325 | ' ' |
| 326 | )); |
| 327 | |
| 328 | case $node instanceof ObjectTypeExtensionNode: |
| 329 | return static::join( |
| 330 | [ |
| 331 | 'extend type', |
| 332 | static::p($node->name), |
| 333 | static::wrap('implements ', static::printList($node->interfaces, ' & ')), |
| 334 | static::printList($node->directives, ' '), |
| 335 | static::printListBlock($node->fields), |
| 336 | ], |
| 337 | ' ' |
| 338 | ); |
| 339 | |
| 340 | case $node instanceof ObjectValueNode: |
| 341 | return '{ ' |
| 342 | . static::printList($node->fields, ', ') |
| 343 | . ' }'; |
| 344 | |
| 345 | case $node instanceof OperationDefinitionNode: |
| 346 | $op = $node->operation; |
| 347 | $name = static::p($node->name); |
| 348 | $varDefs = static::wrap('(', static::printList($node->variableDefinitions, ', '), ')'); |
| 349 | $directives = static::printList($node->directives, ' '); |
| 350 | $selectionSet = static::p($node->selectionSet); |
| 351 | |
| 352 | // Anonymous queries with no directives or variable definitions can use |
| 353 | // the query short form. |
| 354 | return $name === '' && $directives === '' && $varDefs === '' && $op === 'query' |
| 355 | ? $selectionSet |
| 356 | : static::join([$op, static::join([$name, $varDefs]), $directives, $selectionSet], ' '); |
| 357 | |
| 358 | case $node instanceof OperationTypeDefinitionNode: |
| 359 | return $node->operation . ': ' . static::p($node->type); |
| 360 | |
| 361 | case $node instanceof ScalarTypeDefinitionNode: |
| 362 | return static::addDescription($node->description, static::join([ |
| 363 | 'scalar', |
| 364 | static::p($node->name), |
| 365 | static::printList($node->directives, ' '), |
| 366 | ], ' ')); |
| 367 | |
| 368 | case $node instanceof ScalarTypeExtensionNode: |
| 369 | return static::join( |
| 370 | [ |
| 371 | 'extend scalar', |
| 372 | static::p($node->name), |
| 373 | static::printList($node->directives, ' '), |
| 374 | ], |
| 375 | ' ' |
| 376 | ); |
| 377 | |
| 378 | case $node instanceof SchemaDefinitionNode: |
| 379 | return static::addDescription($node->description, static::join( |
| 380 | [ |
| 381 | 'schema', |
| 382 | static::printList($node->directives, ' '), |
| 383 | static::printListBlock($node->operationTypes), |
| 384 | ], |
| 385 | ' ' |
| 386 | )); |
| 387 | |
| 388 | case $node instanceof SchemaExtensionNode: |
| 389 | return static::join( |
| 390 | [ |
| 391 | 'extend schema', |
| 392 | static::printList($node->directives, ' '), |
| 393 | static::printListBlock($node->operationTypes), |
| 394 | ], |
| 395 | ' ' |
| 396 | ); |
| 397 | |
| 398 | case $node instanceof SelectionSetNode: |
| 399 | return static::printListBlock($node->selections); |
| 400 | |
| 401 | case $node instanceof StringValueNode: |
| 402 | if ($node->block) { |
| 403 | return BlockString::print($node->value); |
| 404 | } |
| 405 | |
| 406 | return json_encode($node->value, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES); |
| 407 | |
| 408 | case $node instanceof UnionTypeDefinitionNode: |
| 409 | $typesStr = static::printList($node->types, ' | '); |
| 410 | |
| 411 | return static::addDescription($node->description, static::join( |
| 412 | [ |
| 413 | 'union', |
| 414 | static::p($node->name), |
| 415 | static::printList($node->directives, ' '), |
| 416 | $typesStr !== '' |
| 417 | ? "= {$typesStr}" |
| 418 | : '', |
| 419 | ], |
| 420 | ' ' |
| 421 | )); |
| 422 | |
| 423 | case $node instanceof UnionTypeExtensionNode: |
| 424 | $typesStr = static::printList($node->types, ' | '); |
| 425 | |
| 426 | return static::join( |
| 427 | [ |
| 428 | 'extend union', |
| 429 | static::p($node->name), |
| 430 | static::printList($node->directives, ' '), |
| 431 | $typesStr !== '' |
| 432 | ? "= {$typesStr}" |
| 433 | : '', |
| 434 | ], |
| 435 | ' ' |
| 436 | ); |
| 437 | |
| 438 | case $node instanceof VariableDefinitionNode: |
| 439 | return '$' . static::p($node->variable->name) |
| 440 | . ': ' |
| 441 | . static::p($node->type) |
| 442 | . static::wrap(' = ', static::p($node->defaultValue)) |
| 443 | . static::wrap(' ', static::printList($node->directives, ' ')); |
| 444 | |
| 445 | case $node instanceof VariableNode: |
| 446 | return '$' . static::p($node->name); |
| 447 | } |
| 448 | |
| 449 | return ''; |
| 450 | } |
| 451 | |
| 452 | /** |
| 453 | * @template TNode of Node |
| 454 | * |
| 455 | * @param NodeList<TNode> $list |
| 456 | * |
| 457 | * @throws \JsonException |
| 458 | */ |
| 459 | protected static function printList(NodeList $list, string $separator = ''): string |
| 460 | { |
| 461 | $parts = []; |
| 462 | foreach ($list as $item) { |
| 463 | $parts[] = static::p($item); |
| 464 | } |
| 465 | |
| 466 | return static::join($parts, $separator); |
| 467 | } |
| 468 | |
| 469 | /** |
| 470 | * Print each item on its own line, wrapped in an indented "{ }" block. |
| 471 | * |
| 472 | * @template TNode of Node |
| 473 | * |
| 474 | * @param NodeList<TNode> $list |
| 475 | * |
| 476 | * @throws \JsonException |
| 477 | */ |
| 478 | protected static function printListBlock(NodeList $list): string |
| 479 | { |
| 480 | if (count($list) === 0) { |
| 481 | return ''; |
| 482 | } |
| 483 | |
| 484 | $parts = []; |
| 485 | foreach ($list as $item) { |
| 486 | $parts[] = static::p($item); |
| 487 | } |
| 488 | |
| 489 | return "{\n" . static::indent(static::join($parts, "\n")) . "\n}"; |
| 490 | } |
| 491 | |
| 492 | /** @throws \JsonException */ |
| 493 | protected static function addDescription(?StringValueNode $description, string $body): string |
| 494 | { |
| 495 | return static::join([static::p($description), $body], "\n"); |
| 496 | } |
| 497 | |
| 498 | /** |
| 499 | * If maybeString is not null or empty, then wrap with start and end, otherwise |
| 500 | * print an empty string. |
| 501 | */ |
| 502 | protected static function wrap(string $start, ?string $maybeString, string $end = ''): string |
| 503 | { |
| 504 | if ($maybeString === null || $maybeString === '') { |
| 505 | return ''; |
| 506 | } |
| 507 | |
| 508 | return $start . $maybeString . $end; |
| 509 | } |
| 510 | |
| 511 | protected static function indent(string $string): string |
| 512 | { |
| 513 | if ($string === '') { |
| 514 | return ''; |
| 515 | } |
| 516 | |
| 517 | return ' ' . str_replace("\n", "\n ", $string); |
| 518 | } |
| 519 | |
| 520 | /** @param array<string|null> $parts */ |
| 521 | protected static function join(array $parts, string $separator = ''): string |
| 522 | { |
| 523 | return implode($separator, array_filter($parts, static fn (?string $part) => $part !== '' && $part !== null)); |
| 524 | } |
| 525 | } |
| 526 |