Repository: webonyx/graphql-php (php)

Issue:
Allow deprecation of input values (arguments and input fields)
I haven't find anything about it in gql specs. Am I missing something?

```php
private static function printInputObject(InputObjectType $type)
    {
        $fields = array_values($type->getFields());
        return self::printDescription($type) . 
            "input {$type->name} {\n" .
                implode("\n", array_map(function($f, $i) {
                    return self::printDescription($f, '  ', !$i) . '  ' . self::printInputValue($f);
                }, $fields, array_keys($fields))) . "\n" .
            "}";
    }
```
doesn't print deprecation reason vs
```php
private static function printObject(ObjectType $type)
    {
        $interfaces = $type->getInterfaces();
        $implementedInterfaces = !empty($interfaces) ?
            ' implements ' . implode(', ', array_map(function($i) {
                return $i->name;
            }, $interfaces)) : '';
        return self::printDescription($type) .
            "type {$type->name}$implementedInterfaces {\n" .
                self::printFields($type) . "\n" .
            "}";
    }

private static function printFields($type)
    {
        $fields = array_values($type->getFields());
        return implode("\n", array_map(function($f, $i) {
                return self::printDescription($f, '  ', !$i) . '  ' .
                    $f->name . self::printArgs($f->args, '  ') . ': ' .
                    (string) $f->getType() . self::printDeprecated($f);
            }, $fields, array_keys($fields)));
    }
```

Thanks!

Pull request description:
Allow deprecating input fields and arguments
Resolves #110 

Patch:
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index 377cba07..2479e37d 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -26,7 +26,7 @@ parameters:
 			path: src/Language/Visitor.php
 
 		-
-			message: "#^Parameter \\#1 \\$config of class GraphQL\\\\Type\\\\Definition\\\\Argument constructor expects array\\{name\\: string, type\\: \\(callable\\(\\)\\: GraphQL\\\\Type\\\\Definition\\\\InputType&GraphQL\\\\Type\\\\Definition\\\\Type\\)\\|\\(GraphQL\\\\Type\\\\Definition\\\\InputType&GraphQL\\\\Type\\\\Definition\\\\Type\\), defaultValue\\?\\: mixed, description\\?\\: string\\|null, astNode\\?\\: GraphQL\\\\Language\\\\AST\\\\InputValueDefinitionNode\\|null\\}, non\\-empty\\-array given\\.$#"
+			message: "#^Parameter \\#1 \\$config of class GraphQL\\\\Type\\\\Definition\\\\Argument constructor expects array\\{name\\: string, type\\: \\(callable\\(\\)\\: GraphQL\\\\Type\\\\Definition\\\\InputType&GraphQL\\\\Type\\\\Definition\\\\Type\\)\\|\\(GraphQL\\\\Type\\\\Definition\\\\InputType&GraphQL\\\\Type\\\\Definition\\\\Type\\), defaultValue\\?\\: mixed, description\\?\\: string\\|null, deprecationReason\\?\\: string\\|null, astNode\\?\\: GraphQL\\\\Language\\\\AST\\\\InputValueDefinitionNode\\|null\\}, non\\-empty\\-array given\\.$#"
 			count: 1
 			path: src/Type/Definition/Argument.php
 
diff --git a/src/Type/Definition/Argument.php b/src/Type/Definition/Argument.php
index a1da4b83..ce874b41 100644
--- a/src/Type/Definition/Argument.php
+++ b/src/Type/Definition/Argument.php
@@ -14,6 +14,7 @@ use GraphQL\Utils\Utils;
  *     type: ArgumentType,
  *     defaultValue?: mixed,
  *     description?: string|null,
+ *     deprecationReason?: string|null,
  *     astNode?: InputValueDefinitionNode|null
  * }
  * @phpstan-type ArgumentConfig array{
@@ -21,6 +22,7 @@ use GraphQL\Utils\Utils;
  *     type: ArgumentType,
  *     defaultValue?: mixed,
  *     description?: string|null,
+ *     deprecationReason?: string|null,
  *     astNode?: InputValueDefinitionNode|null
  * }
  * @phpstan-type ArgumentListConfig iterable<ArgumentConfig|ArgumentType>|iterable<UnnamedArgumentConfig>
@@ -34,6 +36,8 @@ class Argument
 
     public ?string $description;
 
+    public ?string $deprecationReason;
+
     /** @var Type&InputType */
     private Type $type;
 
@@ -48,6 +52,7 @@ class Argument
         $this->name = $config['name'];
         $this->defaultValue = $config['defaultValue'] ?? null;
         $this->description = $config['description'] ?? null;
+        $this->deprecationReason = $config['deprecationReason'] ?? null;
         // Do nothing for type, it is lazy loaded in getType()
         $this->astNode = $config['astNode'] ?? null;
 
@@ -95,6 +100,11 @@ class Argument
             && ! $this->defaultValueExists();
     }
 
+    public function isDeprecated(): bool
+    {
+        return (bool) $this->deprecationReason;
+    }
+
     /**
      * @param Type&NamedType $parentType
      *
@@ -113,5 +123,9 @@ class Argument
             $notInputType = Utils::printSafe($this->type);
             throw new InvariantViolation("{$parentType->name}.{$parentField->name}({$this->name}): argument type must be Input Type but got: {$notInputType}");
         }
+
+        if ($this->isRequired() && $this->isDeprecated()) {
+            throw new InvariantViolation("Required argument {$parentType->name}.{$parentField->name}({$this->name}:) cannot be deprecated.");
+        }
     }
 }
diff --git a/src/Type/Definition/Directive.php b/src/Type/Definition/Directive.php
index 00ce3ab8..0a6db42e 100644
--- a/src/Type/Definition/Directive.php
+++ b/src/Type/Definition/Directive.php
@@ -127,6 +127,8 @@ class Directive
                 'locations' => [
                     DirectiveLocation::FIELD_DEFINITION,
                     DirectiveLocation::ENUM_VALUE,
+                    DirectiveLocation::ARGUMENT_DEFINITION,
+                    DirectiveLocation::INPUT_FIELD_DEFINITION,
                 ],
                 'args' => [
                     self::REASON_ARGUMENT_NAME => [
diff --git a/src/Type/Definition/InputObjectField.php b/src/Type/Definition/InputObjectField.php
index 99f6bfc4..604ab308 100644
--- a/src/Type/Definition/InputObjectField.php
+++ b/src/Type/Definition/InputObjectField.php
@@ -14,6 +14,7 @@ use GraphQL\Utils\Utils;
  *   type: ArgumentType,
  *   defaultValue?: mixed,
  *   description?: string|null,
+ *   deprecationReason?: string|null,
  *   astNode?: InputValueDefinitionNode|null
  * }
  * @phpstan-type UnnamedInputObjectFieldConfig array{
@@ -21,6 +22,7 @@ use GraphQL\Utils\Utils;
  *   type: ArgumentType,
  *   defaultValue?: mixed,
  *   description?: string|null,
+ *   deprecationReason?: string|null,
  *   astNode?: InputValueDefinitionNode|null
  * }
  */
@@ -33,6 +35,8 @@ class InputObjectField
 
     public ?string $description;
 
+    public ?string $deprecationReason;
+
     /** @var Type&InputType */
     private Type $type;
 
@@ -47,6 +51,7 @@ class InputObjectField
         $this->name = $config['name'];
         $this->defaultValue = $config['defaultValue'] ?? null;
         $this->description = $config['description'] ?? null;
+        $this->deprecationReason = $config['deprecationReason'] ?? null;
         // Do nothing for type, it is lazy loaded in getType()
         $this->astNode = $config['astNode'] ?? null;
 
@@ -74,6 +79,11 @@ class InputObjectField
             && ! $this->defaultValueExists();
     }
 
+    public function isDeprecated(): bool
+    {
+        return (bool) $this->deprecationReason;
+    }
+
     /**
      * @param Type&NamedType $parentType
      *
@@ -97,5 +107,9 @@ class InputObjectField
         if (\array_key_exists('resolve', $this->config)) {
             throw new InvariantViolation("{$parentType->name}.{$this->name} field has a resolve property, but Input Types cannot define resolvers.");
         }
+
+        if ($this->isRequired() && $this->isDeprecated()) {
+            throw new InvariantViolation("Required input field {$parentType->name}.{$this->name} cannot be deprecated.");
+        }
     }
 }
diff --git a/src/Type/Introspection.php b/src/Type/Introspection.php
index c3d4a55b..7fb70ec0 100644
--- a/src/Type/Introspection.php
+++ b/src/Type/Introspection.php
@@ -96,7 +96,7 @@ class Introspection
     fields(includeDeprecated: true) {
       name
       {$descriptions}
-      args {
+      args(includeDeprecated: true) {
         ...InputValue
       }
       type {
@@ -105,7 +105,7 @@ class Introspection
       isDeprecated
       deprecationReason
     }
-    inputFields {
+    inputFields(includeDeprecated: true) {
       ...InputValue
     }
     interfaces {
@@ -127,6 +127,8 @@ class Introspection
     {$descriptions}
     type { ...TypeRef }
     defaultValue
+    isDeprecated
+    deprecationReason
   }
 
   fragment TypeRef on __Type {
@@ -392,9 +394,30 @@ GRAPHQL;
                 ],
                 'inputFields' => [
                     'type' => Type::listOf(Type::nonNull(self::_inputValue())),
-                    'resolve' => static fn ($type): ?array => $type instanceof InputObjectType
-                        ? $type->getFields()
-                        : null,
+                    'args' => [
+                        'includeDeprecated' => [
+                            'type' => Type::boolean(),
+                            'defaultValue' => false,
+                        ],
+                    ],
+                    'resolve' => static function ($type, $args): ?array {
+                        if ($type instanceof InputObjectType) {
+                            $fields = $type->getFields();
+
+                            if (! ($args['includeDeprecated'] ?? false)) {
+                                return \array_filter(
+                                    $fields,
+                                    static fn (InputObjectField $field): bool =>
+                                        $field->deprecationReason === null
+                                        || $field->deprecationReason === '',
+                                );
+                            }
+
+                            return $fields;
+                        }
+
+                        return null;
+                    },
                 ],
                 'ofType' => [
                     'type' => self::_type(),
@@ -469,7 +492,26 @@ GRAPHQL;
                 ],
                 'args' => [
                     'type' => Type::nonNull(Type::listOf(Type::nonNull(self::_inputValue()))),
-                    'resolve' => static fn (FieldDefinition $field): array => $field->args,
+                    'args' => [
+                        'includeDeprecated' => [
+                            'type' => Type::boolean(),
+                            'defaultValue' => false,
+                        ],
+                    ],
+                    'resolve' => static function (FieldDefinition $field, $args): array {
+                        $values = $field->args;
+
+                        if (! ($args['includeDeprecated'] ?? false)) {
+                            return \array_filter(
+                                $values,
+                                static fn (Argument $value): bool =>
+                                    $value->deprecationReason === null
+                                    || $value->deprecationReason === '',
+                            );
+                        }
+
+                        return $values;
+                    },
                 ],
                 'type' => [
                     'type' => Type::nonNull(self::_type()),
@@ -532,6 +574,17 @@ GRAPHQL;
                         return null;
                     },
                 ],
+                'isDeprecated' => [
+                    'type' => Type::nonNull(Type::boolean()),
+                    /** @param Argument|InputObjectField $inputValue */
+                    'resolve' => static fn ($inputValue): bool => $inputValue->deprecationReason !== null
+                        && $inputValue->deprecationReason !== '',
+                ],
+                'deprecationReason' => [
+                    'type' => Type::string(),
+                    /** @param Argument|InputObjectField $inputValue */
+                    'resolve' => static fn ($inputValue): ?string => $inputValue->deprecationReason,
+                ],
             ],
         ]);
     }
diff --git a/src/Utils/ASTDefinitionBuilder.php b/src/Utils/ASTDefinitionBuilder.php
index 30a7e852..1e5aee27 100644
--- a/src/Utils/ASTDefinitionBuilder.php
+++ b/src/Utils/ASTDefinitionBuilder.php
@@ -141,6 +141,7 @@ class ASTDefinitionBuilder
                 'name' => $value->name->value,
                 'type' => $type,
                 'description' => $value->description->value ?? null,
+                'deprecationReason' => $this->getDeprecationReason($value),
                 'astNode' => $value,
             ];
 
@@ -388,7 +389,7 @@ class ASTDefinitionBuilder
      * Given a collection of directives, returns the string value for the
      * deprecation reason.
      *
-     * @param EnumValueDefinitionNode|FieldDefinitionNode $node
+     * @param EnumValueDefinitionNode|FieldDefinitionNode|InputValueDefinitionNode $node
      *
      * @throws \Exception
      * @throws \ReflectionException
diff --git a/src/Utils/SchemaExtender.php b/src/Utils/SchemaExtender.php
index 41836e8e..bab501b8 100644
--- a/src/Utils/SchemaExtender.php
+++ b/src/Utils/SchemaExtender.php
@@ -300,6 +300,7 @@ class SchemaExtender
             $newFieldConfig = [
                 'description' => $field->description,
                 'type' => $extendedType,
+                'deprecationReason' => $field->deprecationReason,
                 'astNode' => $field->astNode,
             ];
 
@@ -459,6 +460,7 @@ class SchemaExtender
             $def = [
                 'type' => $extendedType,
                 'description' => $arg->description,
+                'deprecationReason' => $arg->deprecationReason,
                 'astNode' => $arg->astNode,
             ];
 
diff --git a/src/Utils/SchemaPrinter.php b/src/Utils/SchemaPrinter.php
index 3eabcd1e..07279a48 100644
--- a/src/Utils/SchemaPrinter.php
+++ b/src/Utils/SchemaPrinter.php
@@ -336,7 +336,7 @@ class SchemaPrinter
      */
     protected static function printInputValue($arg): string
     {
-        $argDecl = "{$arg->name}: {$arg->getType()->toString()}";
+        $argDecl = "{$arg->name}: {$arg->getType()->toString()}" . static::printDeprecated($arg);
 
         if ($arg->defaultValueExists()) {
             $defaultValueAST = AST::astFromValue($arg->defaultValue, $arg->getType());
@@ -424,15 +424,15 @@ class SchemaPrinter
     }
 
     /**
-     * @param FieldDefinition|EnumValueDefinition $fieldOrEnumVal
+     * @param FieldDefinition|EnumValueDefinition|InputObjectField|Argument $deprecation
      *
      * @throws \JsonException
      * @throws InvariantViolation
      * @throws SerializationError
      */
-    protected static function printDeprecated($fieldOrEnumVal): string
+    protected static function printDeprecated($deprecation): string
     {
-        $reason = $fieldOrEnumVal->deprecationReason;
+        $reason = $deprecation->deprecationReason;
         if ($reason === null) {
             return '';
         }


Test patch:
diff --git a/tests/Type/DefinitionTest.php b/tests/Type/DefinitionTest.php
index 93da39b8..625fe104 100644
--- a/tests/Type/DefinitionTest.php
+++ b/tests/Type/DefinitionTest.php
@@ -920,13 +920,19 @@ final class DefinitionTest extends TestCaseBase
                 'goodField' => [
                     'type' => Type::string(),
                     'args' => [
-                        'goodArg' => ['type' => Type::string()],
+                        'goodArg' => [
+                            'type' => Type::string(),
+                            'deprecationReason' => 'Just because',
+                        ],
                     ],
                 ],
             ],
         ]);
         $objType->assertValid();
-        self::assertDidNotCrash();
+        $argument = $objType->getField('goodField')->getArg('goodArg');
+        self::assertInstanceOf(Argument::class, $argument);
+        self::assertTrue($argument->isDeprecated());
+        self::assertSame('Just because', $argument->deprecationReason);
     }
 
     // Object interfaces must be array
@@ -1436,12 +1442,16 @@ final class DefinitionTest extends TestCaseBase
             'fields' => [
                 $fieldName => [
                     'type' => Type::string(),
+                    'deprecationReason' => 'Just because',
                 ],
             ],
         ]);
 
         $inputObjType->assertValid();
-        self::assertSame(Type::string(), $inputObjType->getField($fieldName)->getType());
+        $field = $inputObjType->getField($fieldName);
+        self::assertSame(Type::string(), $field->getType());
+        self::assertTrue($field->isDeprecated());
+        self::assertSame('Just because', $field->deprecationReason);
     }
 
     /** @see it('accepts an Input Object type with a field function') */
@@ -1453,12 +1463,16 @@ final class DefinitionTest extends TestCaseBase
             'fields' => static fn (): array => [
                 $fieldName => [
                     'type' => Type::string(),
+                    'deprecationReason' => 'Just because',
                 ],
             ],
         ]);
 
         $inputObjType->assertValid();
+        $field = $inputObjType->getField($fieldName);
         self::assertSame(Type::string(), $inputObjType->getField($fieldName)->getType());
+        self::assertTrue($field->isDeprecated());
+        self::assertSame('Just because', $field->deprecationReason);
     }
 
     /** @see it('accepts an Input Object type with a field type function') */
diff --git a/tests/Type/DirectiveTest.php b/tests/Type/DirectiveTest.php
index dbeb7353..fae3c7c6 100644
--- a/tests/Type/DirectiveTest.php
+++ b/tests/Type/DirectiveTest.php
@@ -2,8 +2,10 @@
 
 namespace GraphQL\Tests\Type;
 
+use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
 use GraphQL\Language\DirectiveLocation;
 use GraphQL\Type\Definition\Directive;
+use GraphQL\Type\Definition\Type;
 use PHPUnit\Framework\TestCase;
 
 /**
@@ -11,6 +13,8 @@ use PHPUnit\Framework\TestCase;
  */
 final class DirectiveTest extends TestCase
 {
+    use ArraySubsetAsserts;
+
     /** @see it('defines a directive with no args', () => { */
     public function testDefinesADirectiveWithNoArgs(): void
     {
@@ -28,5 +32,56 @@ final class DirectiveTest extends TestCase
         self::assertSame($locations, $directive->locations);
     }
 
+    /** @see it('defines a directive with multiple args' */
+    public function testDefinesADirectiveWithMultipleArgs(): void
+    {
+        $name = 'Foo';
+        $locations = [DirectiveLocation::QUERY];
+
+        $directive = new Directive([
+            'name' => $name,
+            'args' => [
+                'foo' => [
+                    'type' => Type::string(),
+                    'deprecationReason' => 'Just because',
+                ],
+                'bar' => [
+                    'type' => Type::int(),
+                ],
+            ],
+            'locations' => $locations,
+        ]);
+
+        self::assertSame($name, $directive->name);
+        self::assertFalse($directive->isRepeatable);
+        self::assertSame($locations, $directive->locations);
+
+        $argumentFoo = $directive->args[0];
+        self::assertArraySubset(
+            [
+                'name' => 'foo',
+                'description' => null,
+                'deprecationReason' => 'Just because',
+                'defaultValue' => null,
+                'astNode' => null,
+            ],
+            (array) $argumentFoo
+        );
+        self::assertTrue($argumentFoo->isDeprecated());
+
+        $argumentBar = $directive->args[1];
+        self::assertArraySubset(
+            [
+                'name' => 'bar',
+                'description' => null,
+                'deprecationReason' => null,
+                'defaultValue' => null,
+                'astNode' => null,
+            ],
+            (array) $argumentBar
+        );
+        self::assertFalse($argumentBar->isDeprecated());
+    }
+
     // TODO implement all of https://github.com/graphql/graphql-js/blob/master/src/type/__tests__/directive-test.js
 }
diff --git a/tests/Type/IntrospectionTest.php b/tests/Type/IntrospectionTest.php
index 9a528d23..a6402d9d 100644
--- a/tests/Type/IntrospectionTest.php
+++ b/tests/Type/IntrospectionTest.php
@@ -232,6 +232,8 @@ final class IntrospectionTest extends TestCase
                                                 'ofType' => null,
                                             ],
                                             'defaultValue' => 'false',
+                                            'isDeprecated' => false,
+                                            'deprecationReason' => null,
                                         ],
                                     ],
                                     'type' => [
@@ -299,6 +301,8 @@ final class IntrospectionTest extends TestCase
                                                 'ofType' => null,
                                             ],
                                             'defaultValue' => 'false',
+                                            'isDeprecated' => false,
+                                            'deprecationReason' => null,
                                         ],
                                     ],
                                     'type' => [
@@ -319,7 +323,19 @@ final class IntrospectionTest extends TestCase
                                 ],
                                 7 => [
                                     'name' => 'inputFields',
-                                    'args' => [],
+                                    'args' => [
+                                        0 => [
+                                            'name' => 'includeDeprecated',
+                                            'type' => [
+                                                'kind' => 'SCALAR',
+                                                'name' => 'Boolean',
+                                                'ofType' => null,
+                                            ],
+                                            'defaultValue' => 'false',
+                                            'isDeprecated' => false,
+                                            'deprecationReason' => null,
+                                        ],
+                                    ],
                                     'type' => [
                                         'kind' => 'LIST',
                                         'name' => null,
@@ -435,7 +451,19 @@ final class IntrospectionTest extends TestCase
                                 ],
                                 2 => [
                                     'name' => 'args',
-                                    'args' => [],
+                                    'args' => [
+                                        0 => [
+                                            'name' => 'includeDeprecated',
+                                            'type' => [
+                                                'kind' => 'SCALAR',
+                                                'name' => 'Boolean',
+                                                'ofType' => null,
+                                            ],
+                                            'defaultValue' => 'false',
+                                            'isDeprecated' => false,
+                                            'deprecationReason' => null,
+                                        ],
+                                    ],
                                     'type' => [
                                         'kind' => 'NON_NULL',
                                         'name' => null,
@@ -559,6 +587,32 @@ final class IntrospectionTest extends TestCase
                                     'isDeprecated' => false,
                                     'deprecationReason' => null,
                                 ],
+                                4 => [
+                                    'name' => 'isDeprecated',
+                                    'args' => [],
+                                    'type' => [
+                                        'kind' => 'NON_NULL',
+                                        'name' => null,
+                                        'ofType' => [
+                                            'kind' => 'SCALAR',
+                                            'name' => 'Boolean',
+                                            'ofType' => null,
+                                        ],
+                                    ],
+                                    'isDeprecated' => false,
+                                    'deprecationReason' => null,
+                                ],
+                                5 => [
+                                    'name' => 'deprecationReason',
+                                    'args' => [],
+                                    'type' => [
+                                        'kind' => 'SCALAR',
+                                        'name' => 'String',
+                                        'ofType' => null,
+                                    ],
+                                    'isDeprecated' => false,
+                                    'deprecationReason' => null,
+                                ],
                             ],
                             'inputFields' => null,
                             'interfaces' => [],
@@ -846,6 +900,8 @@ final class IntrospectionTest extends TestCase
                                         ],
                                     ],
                                     'defaultValue' => null,
+                                    'isDeprecated' => false,
+                                    'deprecationReason' => null,
                                 ],
                             ],
                             'isRepeatable' => false,
@@ -870,6 +926,8 @@ final class IntrospectionTest extends TestCase
                                         ],
                                     ],
                                     'defaultValue' => null,
+                                    'isDeprecated' => false,
+                                    'deprecationReason' => null,
                                 ],
                             ],
                             'isRepeatable' => false,
@@ -890,12 +948,16 @@ final class IntrospectionTest extends TestCase
                                         'ofType' => null,
                                     ],
                                     'defaultValue' => '"No longer supported"',
+                                    'isDeprecated' => false,
+                                    'deprecationReason' => null,
                                 ],
                             ],
                             'isRepeatable' => false,
                             'locations' => [
                                 0 => 'FIELD_DEFINITION',
                                 1 => 'ENUM_VALUE',
+                                2 => 'ARGUMENT_DEFINITION',
+                                3 => 'INPUT_FIELD_DEFINITION',
                             ],
                         ],
                     ],
@@ -1142,6 +1204,115 @@ final class IntrospectionTest extends TestCase
         self::assertSame($expected, GraphQL::executeQuery($schema, $request)->toArray());
     }
 
+    /** @see it('identifies deprecated args' */
+    public function testIdentifiesDeprecatedArgs(): void
+    {
+        $TestType = new ObjectType([
+            'name' => 'TestType',
+            'fields' => [
+                'testField' => [
+                    'type' => Type::string(),
+                    'args' => [
+                        'nonDeprecated' => [
+                            'type' => Type::string(),
+                        ],
+                        'deprecated' => [
+                            'type' => Type::string(),
+                            'deprecationReason' => 'Removed in 1.0',
+                        ],
+                    ],
+                ],
+            ],
+        ]);
+
+        $schema = new Schema(['query' => $TestType]);
+        $request = '
+          {
+            __type(name: "TestType") {
+              fields {
+                args(includeDeprecated: true) {
+                  name
+                  isDeprecated
+                  deprecationReason
+                }
+              }
+            }
+          }
+        ';
+
+        $expected = [
+            [
+                'args' => [
+                    [
+                        'name' => 'nonDeprecated',
+                        'isDeprecated' => false,
+                        'deprecationReason' => null,
+                    ],
+                    [
+                        'name' => 'deprecated',
+                        'isDeprecated' => true,
+                        'deprecationReason' => 'Removed in 1.0',
+                    ],
+                ],
+            ],
+        ];
+
+        $result = GraphQL::executeQuery($schema, $request)->toArray();
+        self::assertEquals($expected, $result['data']['__type']['fields'] ?? null);
+    }
+
+    /** @see it('respects the includeDeprecated parameter for args' */
+    public function testRespectsTheIncludeDeprecatedParameterForArgs(): void
+    {
+        $TestType = new ObjectType([
+            'name' => 'TestType',
+            'fields' => [
+                'testField' => [
+                    'type' => Type::string(),
+                    'args' => [
+                        'nonDeprecated' => [
+                            'type' => Type::string(),
+                        ],
+                        'deprecated' => [
+                            'type' => Type::string(),
+                            'deprecationReason' => 'Removed in 1.0',
+                        ],
+                    ],
+                ],
+            ],
+        ]);
+
+        $schema = new Schema(['query' => $TestType]);
+        $request = '
+          {
+            __type(name: "TestType") {
+              fields {
+                trueArgs: args(includeDeprecated: true) {
+                  name
+                }
+                falseArgs: args(includeDeprecated: false) {
+                  name
+                }
+                omittedArgs: args {
+                  name
+                }
+              }
+            }
+          }
+        ';
+
+        $expected = [
+            [
+                'trueArgs' => [['name' => 'nonDeprecated'], ['name' => 'deprecated']],
+                'falseArgs' => [['name' => 'nonDeprecated']],
+                'omittedArgs' => [['name' => 'nonDeprecated']],
+            ],
+        ];
+
+        $result = GraphQL::executeQuery($schema, $request)->toArray();
+        self::assertSame($expected, $result['data']['__type']['fields'] ?? null);
+    }
+
     /** @see it('identifies deprecated enum values') */
     public function testIdentifiesDeprecatedEnumValues(): void
     {
@@ -1261,6 +1432,121 @@ final class IntrospectionTest extends TestCase
         self::assertSame($expected, GraphQL::executeQuery($schema, $request)->toArray());
     }
 
+    /** @see it('identifies deprecated for input fields' */
+    public function testIdentifiesDeprecatedForInputFields(): void
+    {
+        $TestInputObject = new InputObjectType([
+            'name' => 'TestInputObject',
+            'fields' => [
+                'nonDeprecated' => [
+                    'type' => Type::string(),
+                ],
+                'deprecated' => [
+                    'type' => Type::listOf(Type::string()),
+                    'deprecationReason' => 'Very important fake reason',
+                ],
+            ],
+        ]);
+
+        $TestType = new ObjectType([
+            'name' => 'TestType',
+            'fields' => [
+                'field' => [
+                    'type' => Type::string(),
+                    'args' => ['complex' => ['type' => $TestInputObject]],
+                ],
+            ],
+        ]);
+
+        $schema = new Schema(['query' => $TestType]);
+        $request = '
+          {
+            __type(name: "TestInputObject") {
+              name
+              trueFields: inputFields(includeDeprecated: true) {
+                name
+              }
+              falseFields: inputFields(includeDeprecated: false) {
+                name
+              }
+              omittedFields: inputFields {
+                name
+              }
+            }
+          }
+        ';
+
+        $expected = [
+            'name' => 'TestInputObject',
+            'trueFields' => [['name' => 'nonDeprecated'], ['name' => 'deprecated']],
+            'falseFields' => [['name' => 'nonDeprecated']],
+            'omittedFields' => [['name' => 'nonDeprecated']],
+        ];
+
+        $result = GraphQL::executeQuery($schema, $request)->toArray();
+        self::assertSame($expected, $result['data']['__type'] ?? null);
+    }
+
+    /** @see it('respects the includeDeprecated parameter for input fields' */
+    public function testRespectsTheIncludeDeprecatedParameterForInputFields(): void
+    {
+        $TestInputObject = new InputObjectType([
+            'name' => 'TestInputObject',
+            'fields' => [
+                'nonDeprecated' => [
+                    'type' => Type::string(),
+                ],
+                'deprecated' => [
+                    'type' => Type::listOf(Type::string()),
+                    'deprecationReason' => 'Very important fake reason',
+                ],
+            ],
+        ]);
+
+        $TestType = new ObjectType([
+            'name' => 'TestType',
+            'fields' => [
+                'field' => [
+                    'type' => Type::string(),
+                    'args' => ['complex' => ['type' => $TestInputObject]],
+                ],
+            ],
+        ]);
+
+        $schema = new Schema(['query' => $TestType]);
+        $request = '
+          {
+            __type(name: "TestInputObject") {
+              name
+              inputFields(includeDeprecated: true) {
+                name
+                isDeprecated
+                deprecationReason
+              }
+            }
+          }
+        ';
+
+        $expected = [
+            'name' => 'TestInputObject',
+            'inputFields' => [
+                [
+                    'name' => 'nonDeprecated',
+                    'isDeprecated' => false,
+                    'deprecationReason' => null,
+                ],
+                [
+                    'name' => 'deprecated',
+                    'isDeprecated' => true,
+                    'deprecationReason' => 'Very important fake reason',
+                ],
+            ],
+        ];
+
+        $result = GraphQL::executeQuery($schema, $request)->toArray();
+        self::assertEquals($expected, $result['data']['__type'] ?? null);
+    }
+
     /** @see it('fails as expected on the __type root field without an arg') */
     public function testFailsAsExpectedOnTheTypeRootFieldWithoutAnArg(): void
     {
diff --git a/tests/Type/ValidationTest.php b/tests/Type/ValidationTest.php
index c254fc0f..eacd42e1 100644
--- a/tests/Type/ValidationTest.php
+++ b/tests/Type/ValidationTest.php
@@ -1005,6 +1005,26 @@ final class ValidationTest extends TestCaseBase
         );
     }
 
+    /** @see it('rejects an Input Object type with required argument that is deprecated' */
+    public function testRejectsAnInputObjectTypeWithRequiredArgumentThatIsDeprecated(): void
+    {
+        $schema = BuildSchema::build('
+      type Query {
+        field(arg: SomeInputObject): String
+      }
+
+      input SomeInputObject {
+        optionalField: String @deprecated
+        anotherOptionalField: String! = "" @deprecated
+        badField: String! @deprecated
+      }
+        ');
+
+        $this->expectException(InvariantViolation::class);
+        $this->expectExceptionMessage('Required input field SomeInputObject.badField cannot be deprecated.');
+        $schema->assertValid();
+    }
+
     /** @see it('rejects an Enum type without values') */
     public function testRejectsAnEnumTypeWithoutValues(): void
     {
@@ -1544,6 +1564,29 @@ final class ValidationTest extends TestCaseBase
         }
     }
 
+    /** @see it('rejects an required argument that is deprecated' */
+    public function testRejectsARequiredArgumentThatIsDeprecated(): void
+    {
+        $schema = BuildSchema::build('
+      directive @BadDirective(
+        badArg: String! @deprecated
+        optionalArg: String @deprecated
+        anotherOptionalArg: String! = "" @deprecated
+      ) on FIELD
+      type Query {
+        test(
+          badArg: String! @deprecated
+          optionalArg: String @deprecated
+          anotherOptionalArg: String! = "" @deprecated
+        ): String
+      }
+        ');
+
+        $this->expectException(InvariantViolation::class);
+        $this->expectExceptionMessage('Required argument String.test(badArg:) cannot be deprecated.');
+        $schema->assertValid();
+    }
+
     /** @see it('rejects a non-input type as a field arg with locations') */
     public function testANonInputTypeAsAFieldArgWithLocations(): void
     {
diff --git a/tests/Utils/BuildSchemaTest.php b/tests/Utils/BuildSchemaTest.php
index a1d7477c..1f87b5dc 100644
--- a/tests/Utils/BuildSchemaTest.php
+++ b/tests/Utils/BuildSchemaTest.php
@@ -745,7 +745,6 @@ final class BuildSchemaTest extends TestCaseBase
     /** @see it('Supports @deprecated') */
     public function testSupportsDeprecated(): void
     {
-        // TODO restore @deprecated on inputs - see https://github.com/webonyx/graphql-php/issues/110
         $sdl = <<<GRAPHQL
             enum MyEnum {
               VALUE
@@ -754,8 +753,8 @@ final class BuildSchemaTest extends TestCaseBase
             }
 
             input MyInput {
-              oldInput: String
-              otherInput: String
+              oldInput: String @deprecated
+              otherInput: String @deprecated(reason: "Use newInput")
               newInput: String
             }
             
@@ -763,8 +762,8 @@ final class BuildSchemaTest extends TestCaseBase
               field1: String @deprecated
               field2: Int @deprecated(reason: "Because I said so")
               enum: MyEnum
-              field3(oldArg: String, arg: String): String
-              field4(oldArg: String, arg: String): String
+              field3(oldArg: String @deprecated, arg: String): String
+              field4(oldArg: String @deprecated(reason: "Why not?"), arg: String): String
               field5(arg: MyInput): String
             }
             
@@ -802,7 +801,6 @@ final class BuildSchemaTest extends TestCaseBase
         self::assertTrue($rootFields['field2']->isDeprecated());
         self::assertSame('Because I said so', $rootFields['field2']->deprecationReason);
 
-        self::markTestIncomplete('See https://github.com/webonyx/graphql-php/issues/110');
         $type = $schema->getType('MyInput');
         self::assertInstanceOf(InputObjectType::class, $type);
         $inputFields = $type->getFields();
diff --git a/tests/Utils/SchemaExtenderTest.php b/tests/Utils/SchemaExtenderTest.php
index ae2b2ff3..4e10d5bd 100644
--- a/tests/Utils/SchemaExtenderTest.php
+++ b/tests/Utils/SchemaExtenderTest.php
@@ -19,6 +19,7 @@ use GraphQL\Tests\Utils\SchemaExtenderTest\SomeInterfaceClassType;
 use GraphQL\Tests\Utils\SchemaExtenderTest\SomeObjectClassType;
 use GraphQL\Tests\Utils\SchemaExtenderTest\SomeScalarClassType;
 use GraphQL\Tests\Utils\SchemaExtenderTest\SomeUnionClassType;
+use GraphQL\Type\Definition\Argument;
 use GraphQL\Type\Definition\CustomScalarType;
 use GraphQL\Type\Definition\Directive;
 use GraphQL\Type\Definition\EnumType;
@@ -662,12 +663,16 @@ GRAPHQL,
         $schema = new Schema([]);
         $extendAST = Parser::parse('
           type SomeObject {
-            deprecatedField: String @deprecated(reason: "not used anymore")
+            deprecatedField(deprecatedArg: String @deprecated(reason: "unusable")): String @deprecated(reason: "not used anymore")
           }
 
           enum SomeEnum {
             DEPRECATED_VALUE @deprecated(reason: "do not use")
           }
+
+          input SomeInputObject {
+            deprecatedField: String @deprecated(reason: "redundant")
+          }
         ');
         $extendedSchema = SchemaExtender::extend($schema, $extendAST);
 
@@ -679,6 +684,12 @@ GRAPHQL,
         self::assertTrue($deprecatedFieldDef->isDeprecated());
         self::assertSame('not used anymore', $deprecatedFieldDef->deprecationReason);
 
+        $deprecatedArgument = $deprecatedFieldDef->getArg('deprecatedArg');
+
+        self::assertInstanceOf(Argument::class, $deprecatedArgument);
+        self::assertTrue($deprecatedArgument->isDeprecated());
+        self::assertSame('unusable', $deprecatedArgument->deprecationReason);
+
         $someEnum = $extendedSchema->getType('SomeEnum');
         assert($someEnum instanceof EnumType);
 
@@ -687,6 +698,14 @@ GRAPHQL,
 
         self::assertTrue($deprecatedEnumDef->isDeprecated());
         self::assertSame('do not use', $deprecatedEnumDef->deprecationReason);
+
+        $someInput = $extendedSchema->getType('SomeInputObject');
+        assert($someInput instanceof InputObjectType);
+
+        $deprecatedInputField = $someInput->getField('deprecatedField');
+
+        self::assertTrue($deprecatedInputField->isDeprecated());
+        self::assertSame('redundant', $deprecatedInputField->deprecationReason);
     }
 
     /** @see it('extends objects with deprecated fields') */
diff --git a/tests/Utils/SchemaPrinterTest.php b/tests/Utils/SchemaPrinterTest.php
index b559696e..7b2cb1d2 100644
--- a/tests/Utils/SchemaPrinterTest.php
+++ b/tests/Utils/SchemaPrinterTest.php
@@ -968,7 +968,7 @@ final class SchemaPrinterTest extends TestCase
       directive @deprecated(
         "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted using the Markdown syntax, as specified by [CommonMark](https:\/\/commonmark.org\/)."
         reason: String = "No longer supported"
-      ) on FIELD_DEFINITION | ENUM_VALUE
+      ) on FIELD_DEFINITION | ENUM_VALUE | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION
 
       "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations."
       type __Schema {
@@ -1001,7 +1001,7 @@ final class SchemaPrinterTest extends TestCase
         interfaces: [__Type!]
         possibleTypes: [__Type!]
         enumValues(includeDeprecated: Boolean = false): [__EnumValue!]
-        inputFields: [__InputValue!]
+        inputFields(includeDeprecated: Boolean = false): [__InputValue!]
         ofType: __Type
       }
 
@@ -1036,7 +1036,7 @@ final class SchemaPrinterTest extends TestCase
       type __Field {
         name: String!
         description: String
-        args: [__InputValue!]!
+        args(includeDeprecated: Boolean = false): [__InputValue!]!
         type: __Type!
         isDeprecated: Boolean!
         deprecationReason: String
@@ -1050,6 +1050,9 @@ final class SchemaPrinterTest extends TestCase
 
         "A GraphQL-formatted string representing the default value for this input value."
         defaultValue: String
+
+        isDeprecated: Boolean!
+        deprecationReason: String
       }
 
       "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string."
diff --git a/tests/Validator/QueryComplexityTest.php b/tests/Validator/QueryComplexityTest.php
index adbf718a..8ea6da01 100644
--- a/tests/Validator/QueryComplexityTest.php
+++ b/tests/Validator/QueryComplexityTest.php
@@ -164,7 +164,7 @@ final class QueryComplexityTest extends QuerySecurityTestCase
 
     public function testComplexityIntrospectionQuery(): void
     {
-        $this->assertIntrospectionQuery(181);
+        $this->assertIntrospectionQuery(187);
     }
 
     public function testIntrospectionTypeMetaFieldQuery(): void