Skip to content
This repository was archived by the owner on Nov 12, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Codegen/Builders/Fields/ConnectionFieldBuilder.hack
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ final class ConnectionFieldBuilder extends MethodFieldBuilder {

<<__Override>>
protected function getArgumentDefinitions(): vec<Parameter> {
return Vec\concat($this->data['parameters'], $this->getConnectionParameters());
return Vec\concat($this->data['parameters'] ?? vec[], $this->getConnectionParameters());
}

/**
Expand Down
32 changes: 30 additions & 2 deletions src/Codegen/Builders/Fields/FieldBuilder.hack
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,16 @@ use type Facebook\HackCodegen\{HackBuilder, HackBuilderValues};
*/
abstract class FieldBuilder {

abstract const type TField as shape(
const type TField = shape(
'name' => string,
'output_type' => shape('type' => string, ?'needs_await' => bool),
...
?'description' => string,
?'deprecation_reason' => string,
?'method_name' => string,
?'parameters' => vec<Parameter>,
?'root_field_for_type' => string,
?'is_static' => bool,
?'is_optional' => bool,
);

public function getName(): string {
Expand All @@ -35,6 +41,7 @@ abstract class FieldBuilder {
): FieldBuilder {
$data = shape(
'name' => $field->getName(),
'description' => $field->getDescription(),
'method_name' => $rm->getName(),
'output_type' => output_type(
$rm->getReturnTypeText(),
Expand All @@ -56,6 +63,11 @@ abstract class FieldBuilder {
),
);

$deprecated = $rm->getAttribute('__Deprecated');
if ($deprecated) {
$data['deprecation_reason'] = C\firstx($deprecated) as string;
}

if ($is_root_field) {
$data['root_field_for_type'] = $rm->getDeclaringClass()->getName();
}
Expand Down Expand Up @@ -126,6 +138,22 @@ abstract class FieldBuilder {
$this->generateResolverBody($hb);
$hb->addLine(',');

// Field description
if ($this->data['description'] ?? '') {
$description_literal = \var_export($this->data['description'] ?? '', true);
$hb->addLinef('%s,', $description_literal);
} else {
// Fields built from shape members don't have descriptions as of yet
$hb->addLine('null,');
}

// Deprecation reason
if (Shapes::keyExists($this->data, 'deprecation_reason')) {
$hb->addLine(\var_export($this->data['deprecation_reason'], true).',');
} else {
$hb->addLine('null,');
}

// End of new GraphQL\FieldDefinition(
$hb->unindent()->addLine(');');
$hb->unindent();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,6 @@ namespace Slack\GraphQL\Codegen;
use type Facebook\HackCodegen\{HackBuilder};

final class IntrospectSchemaFieldBuilder extends FieldBuilder {
const type TField = shape(
'name' => string,
'output_type' => shape('type' => string, ?'needs_await' => bool),
...
);

public function __construct() {
parent::__construct(shape(
'name' => '__schema',
Expand Down
19 changes: 7 additions & 12 deletions src/Codegen/Builders/Fields/MethodFieldBuilder.hack
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,11 @@ type Parameter = shape(
* we can tell `MethodFieldBuilder` exactly what we want, and the runtime will successfully resolve the generic method.
*/
class MethodFieldBuilder extends FieldBuilder {
const type TField = shape(
'name' => string,
'method_name' => string,
'output_type' => shape('type' => string, ?'needs_await' => bool),
'parameters' => vec<Parameter>,
?'root_field_for_type' => string,
?'is_static' => bool,
);

<<__Override>>
protected function generateResolverBody(HackBuilder $hb): void {
if ($this->data['deprecation_reason'] ?? null) {
$hb->add('/* HH_FIXME[4128] Deprecated */ ');
}
$type_info = $this->data['output_type'];
$method_name = Shapes::at($this->data, 'method_name');
$hb->addf(
Expand All @@ -54,9 +48,10 @@ class MethodFieldBuilder extends FieldBuilder {
}

protected function generateParametersInvocation(HackBuilder $hb): void {
if (!C\is_empty($this->data['parameters'])) {
$parameters = $this->data['parameters'] ?? vec[];
if (!C\is_empty($parameters)) {
$hb->newLine()->indent();
foreach ($this->data['parameters'] as $param) {
foreach ($parameters as $param) {
$arg = $this->getArgumentInvocationString($param);
$hb->addLinef('%s,', $arg);
}
Expand All @@ -66,7 +61,7 @@ class MethodFieldBuilder extends FieldBuilder {

<<__Override>>
protected function getArgumentDefinitions(): vec<Parameter> {
return $this->data['parameters'];
return $this->data['parameters'] ?? vec[];
}

protected function getMethodCallPrefix(): string {
Expand Down
8 changes: 1 addition & 7 deletions src/Codegen/Builders/Fields/ShapeFieldBuilder.hack
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,6 @@ use type Facebook\HackCodegen\{HackBuilder, HackBuilderValues};
* In general, call `FieldBuilder::fromShapeField` instead of instantiating this directly.
*/
final class ShapeFieldBuilder extends FieldBuilder {
const type TField = shape(
'name' => string,
'output_type' => shape('type' => string, ?'needs_await' => bool),
'is_optional' => bool,
);

<<__Override>>
protected function getArgumentDefinitions(): vec<Parameter> {
return vec[];
Expand All @@ -26,6 +20,6 @@ final class ShapeFieldBuilder extends FieldBuilder {
<<__Override>>
protected function generateResolverBody(HackBuilder $hb): void {
$name_literal = \var_export($this->data['name'], true);
$hb->addf('$parent[%s]%s', $name_literal, $this->data['is_optional'] ? ' ?? null' : '');
$hb->addf('$parent[%s]%s', $name_literal, Shapes::idx($this->data, 'is_optional', false) ? ' ?? null' : '');
}
}
4 changes: 0 additions & 4 deletions src/Codegen/Builders/ObjectBuilder.hack
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,11 @@ class ObjectBuilder extends CompositeBuilder {
'type' => $edge_name.'::nonNullable()->nullableOutputListOf()',
'needs_await' => true,
),
'parameters' => vec[],
)),
new MethodFieldBuilder(shape(
'name' => 'pageInfo',
'method_name' => 'getPageInfo',
'output_type' => shape('type' => 'PageInfo::nullableOutput()', 'needs_await' => true),
'parameters' => vec[],
)),
],
dict[], // Connections do not implement any interfaces
Expand All @@ -98,13 +96,11 @@ class ObjectBuilder extends CompositeBuilder {
'name' => 'node',
'method_name' => 'getNode',
'output_type' => shape('type' => $output_type.'::nullableOutput()'),
'parameters' => vec[],
)),
new MethodFieldBuilder(shape(
'name' => 'cursor',
'method_name' => 'getCursor',
'output_type' => shape('type' => 'Types\StringType::nullableOutput()'),
'parameters' => vec[],
)),
],
dict[],
Expand Down
8 changes: 5 additions & 3 deletions src/Field.hack
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ class Field implements \HH\MethodAttribute {
public function getName(): string {
return $this->name;
}

public function getDescription(): string {
return $this->description;
}
}

/**
Expand All @@ -30,6 +34,4 @@ class Field implements \HH\MethodAttribute {
*
* @see https://spec.graphql.org/draft/#sec-Handling-Field-Errors
*/
final class KillsParentOnException implements \HH\MethodAttribute {
public function __construct() {}
}
final class KillsParentOnException implements \HH\MethodAttribute {}
11 changes: 5 additions & 6 deletions src/FieldDefinition.hack
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ final class FieldDefinition<TParent, TRet, TResolved> implements IResolvableFiel
dict<string, \Graphpinator\Parser\Value\Value>,
Variables,
): Awaitable<TRet>) $resolver,
private ?string $description,
private ?string $deprecation_reason = null,
) {}

public async function resolveAsync(
Expand Down Expand Up @@ -67,18 +69,15 @@ final class FieldDefinition<TParent, TRet, TResolved> implements IResolvableFiel
}

public function getDeprecationReason(): ?string {
// TODO
return null;
return $this->deprecation_reason;
}

public function isDeprecated(): bool {
// TODO
return false;
return $this->getDeprecationReason() is nonnull;
}

public function getDescription(): ?string {
// TODO
return null;
return $this->description;
}

public function getArgs(): vec<Introspection\__InputValue> {
Expand Down
7 changes: 6 additions & 1 deletion tests/Fixtures/IntrospectionTestObjects.hack
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ final class IntrospectionTestObject {
return null;
}

<<GraphQL\Field('non_null_int', 'Nullable string'), GraphQL\KillsParentOnException>>
<<GraphQL\Field('non_null_int', 'Non nullable int'), GraphQL\KillsParentOnException>>
public function getNonNullInt(): int {
return 1;
}
Expand All @@ -87,4 +87,9 @@ final class IntrospectionTestObject {
return vec[null];
}

<<GraphQL\Field('deprecated_field', 'Deprecated field'), __Deprecated('Deprecated for testing')>>
public function getDeprecated(): string {
return 'deprecated';
}

}
35 changes: 35 additions & 0 deletions tests/IntrospectionTest.hack
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ final class IntrospectionTest extends FixtureTest {
name
fields {
name
description
type {
kind
name
Expand All @@ -285,6 +286,8 @@ final class IntrospectionTest extends FixtureTest {
}
}
}
isDeprecated
deprecationReason
}
}
}',
Expand All @@ -295,6 +298,7 @@ final class IntrospectionTest extends FixtureTest {
'fields' => vec[
dict[
'name' => 'default_list_of_non_nullable_int',
'description' => 'Default list of non nullable int',
'type' => dict[
'kind' => 'LIST',
'name' => null,
Expand All @@ -308,9 +312,12 @@ final class IntrospectionTest extends FixtureTest {
],
],
],
'isDeprecated' => false,
'deprecationReason' => null,
],
dict[
'name' => 'default_list_of_nullable_int',
'description' => 'Default list of nullable int',
'type' => dict[
'kind' => 'LIST',
'name' => null,
Expand All @@ -320,17 +327,34 @@ final class IntrospectionTest extends FixtureTest {
'ofType' => null,
],
],
'isDeprecated' => false,
'deprecationReason' => null,
],
dict[
'name' => 'default_nullable_string',
'description' => 'Default nullable string',
'type' => dict[
'kind' => 'SCALAR',
'name' => 'String',
'ofType' => null,
],
'isDeprecated' => false,
'deprecationReason' => null,
],
dict[
'name' => 'deprecated_field',
'description' => 'Deprecated field',
'type' => dict[
'kind' => 'SCALAR',
'name' => 'String',
'ofType' => null,
],
'isDeprecated' => true,
'deprecationReason' => 'Deprecated for testing',
],
dict[
'name' => 'non_null_int',
'description' => 'Non nullable int',
'type' => dict[
'kind' => 'NON_NULL',
'name' => null,
Expand All @@ -340,9 +364,12 @@ final class IntrospectionTest extends FixtureTest {
'ofType' => null,
],
],
'isDeprecated' => false,
'deprecationReason' => null,
],
dict[
'name' => 'non_null_list_of_non_null',
'description' => 'Non nullable list of non nullables',
'type' => dict[
'kind' => 'NON_NULL',
'name' => null,
Expand All @@ -359,9 +386,12 @@ final class IntrospectionTest extends FixtureTest {
],
],
],
'isDeprecated' => false,
'deprecationReason' => null,
],
dict[
'name' => 'non_null_string',
'description' => 'Non nullable string',
'type' => dict[
'kind' => 'NON_NULL',
'name' => null,
Expand All @@ -371,14 +401,19 @@ final class IntrospectionTest extends FixtureTest {
'ofType' => null,
],
],
'isDeprecated' => false,
'deprecationReason' => null,
],
dict[
'name' => 'nullable_string',
'description' => 'Nullable string',
'type' => dict[
'kind' => 'SCALAR',
'name' => 'String',
'ofType' => null,
],
'isDeprecated' => false,
'deprecationReason' => null,
],
],
],
Expand Down
6 changes: 5 additions & 1 deletion tests/gen/AlphabetConnection.hack
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* To re-generate this file run vendor/bin/hacktest
*
*
* @generated SignedSource<<62edee806dca502f5b400aefff59f52a>>
* @generated SignedSource<<28a23bb2fbfd1ea78d4867cfa9f92f20>>
*/
namespace Slack\GraphQL\Test\Generated;
use namespace Slack\GraphQL;
Expand Down Expand Up @@ -32,13 +32,17 @@ final class AlphabetConnection extends \Slack\GraphQL\Types\ObjectType {
StringTypeEdge::nonNullable()->nullableOutputListOf(),
dict[],
async ($parent, $args, $vars) ==> await $parent->getEdges(),
null,
null,
);
case 'pageInfo':
return new GraphQL\FieldDefinition(
'pageInfo',
PageInfo::nullableOutput(),
dict[],
async ($parent, $args, $vars) ==> await $parent->getPageInfo(),
null,
null,
);
default:
return null;
Expand Down
Loading