Add RootResolverSignatureRector to auto-fix root resolver signatures#2779
Open
spawnia wants to merge 42 commits into
Open
Add RootResolverSignatureRector to auto-fix root resolver signatures#2779spawnia wants to merge 42 commits into
spawnia wants to merge 42 commits into
Conversation
…ces() 🤖 Generated with Claude Code
🤖 Generated with Claude Code
🤖 Generated with Claude Code
The rule calls RootType::namespaces() which requires config() to be available. Instead of silently falling back to defaults when config() is unavailable, fail loudly as the spec requires. The test bootstrap loads the Larastan bootstrap and registers the Lighthouse config. 🤖 Generated with Claude Code
Follow the project convention of protected over private for extensibility. Also remove redundant $objectType->equals() check (isSuperTypeOf covers it). 🤖 Generated with Claude Code
🤖 Generated with Claude Code
When a resolver had multiple params with the first typed array (e.g. __invoke(array $root, array $args)), or a single untyped param, the rule would incorrectly prepend a new $root, creating duplicate variable names. Now only considers root as missing when there is exactly one param that is either untyped or typed array. 🤖 Generated with Claude Code
Assert the value is an array (satisfies PHPStan level-8 type narrowing) and throw InvalidConfigurationException for non-string, non-null elements. 🤖 Generated with Claude Code
Instead of silently doing nothing when config() has no Lighthouse namespaces loaded, throw a RuntimeException guiding the user to add the required bootstrapFiles entry. 🤖 Generated with Claude Code
- Move fixParamType(0, ...) into else branch (no-op after prependRootParam) - Split isset() && fix() into nested ifs (one-thing-per-line) - Replace ensureMinParams while-loop with direct ensureArgsParam - Extract fixObjectParam from duplicate fixContextParam/fixResolveInfoParam 🤖 Generated with Claude Code
🤖 Generated with Claude Code
Cover wrong ResolveInfo type and untyped context/resolveInfo params. 🤖 Generated with Claude Code
🤖 Generated with Claude Code
🤖 Generated with Claude Code
There was a problem hiding this comment.
Pull request overview
Adds a new Rector rule to automatically fix Lighthouse root resolver __invoke method signatures so they match Lighthouse’s positional calling convention and avoid runtime TypeErrors. It also centralizes resolver namespace lookup in RootType and documents the new behavior and tooling.
Changes:
- Add
RootResolverSignatureRectorto detect/fix root resolver__invokeparameter order, presence, types, and (optionally) names. - Extract
RootType::namespaces()and reuse it fromFieldValue. - Add unit tests/fixtures plus user docs and changelog entry.
Reviewed changes
Copilot reviewed 31 out of 32 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Unit/Rector/RootResolverSignatureRector/Source/CustomContext.php | Test-only custom context implementing GraphQLContext. |
| tests/Unit/Rector/RootResolverSignatureRector/RootResolverSignatureRectorWithNamesTest.php | Test runner for fixtures with name normalization config. |
| tests/Unit/Rector/RootResolverSignatureRector/RootResolverSignatureRectorTest.php | Main rule test runner. |
| tests/Unit/Rector/RootResolverSignatureRector/RootResolverSignatureRectorPartialNamesTest.php | Test runner for partial name normalization config. |
| tests/Unit/Rector/RootResolverSignatureRector/RootResolverSignatureRectorConfigTest.php | Tests for invalid configuration rejection. |
| tests/Unit/Rector/RootResolverSignatureRector/FixtureWithNames/normalize_names.php.inc | Fixture validating parameter name normalization behavior. |
| tests/Unit/Rector/RootResolverSignatureRector/FixturePartialNames/normalize_partial.php.inc | Fixture validating partial parameter renaming. |
| tests/Unit/Rector/RootResolverSignatureRector/Fixture/wrong_second_param.php.inc | Fixture for correcting $args type. |
| tests/Unit/Rector/RootResolverSignatureRector/Fixture/wrong_root_type.php.inc | Fixture for correcting $root type. |
| tests/Unit/Rector/RootResolverSignatureRector/Fixture/wrong_resolve_info_type.php.inc | Fixture for correcting ResolveInfo type. |
| tests/Unit/Rector/RootResolverSignatureRector/Fixture/wrong_context_type.php.inc | Fixture for correcting GraphQLContext type. |
| tests/Unit/Rector/RootResolverSignatureRector/Fixture/untyped_first_param.php.inc | Fixture for prepending missing $root. |
| tests/Unit/Rector/RootResolverSignatureRector/Fixture/untyped_context_and_resolve_info.php.inc | Fixture for typing $context/$resolveInfo. |
| tests/Unit/Rector/RootResolverSignatureRector/Fixture/skip_zero_params.php.inc | Fixture ensuring 0-param __invoke is skipped. |
| tests/Unit/Rector/RootResolverSignatureRector/Fixture/skip_subdirectory.php.inc | Fixture ensuring nested namespaces are skipped. |
| tests/Unit/Rector/RootResolverSignatureRector/Fixture/skip_non_resolver_namespace.php.inc | Fixture ensuring non-resolver namespaces are skipped. |
| tests/Unit/Rector/RootResolverSignatureRector/Fixture/skip_custom_context.php.inc | Fixture ensuring custom context types are not rewritten. |
| tests/Unit/Rector/RootResolverSignatureRector/Fixture/skip_correct_null.php.inc | Fixture ensuring already-correct signature is skipped. |
| tests/Unit/Rector/RootResolverSignatureRector/Fixture/single_wrong_root_param.php.inc | Fixture for single-param root fix + args insertion. |
| tests/Unit/Rector/RootResolverSignatureRector/Fixture/narrow_mixed_root.php.inc | Fixture for narrowing mixed $root to null on PHP 8.2+. |
| tests/Unit/Rector/RootResolverSignatureRector/Fixture/missing_root_param.php.inc | Fixture for detecting missing $root when first param looks like $args. |
| tests/Unit/Rector/RootResolverSignatureRector/Fixture/array_root_with_args.php.inc | Fixture for correcting invalid $root types. |
| tests/Unit/Rector/RootResolverSignatureRector/config/configured_rule.php | Rector test config enabling rule (and importNames()). |
| tests/Unit/Rector/RootResolverSignatureRector/config/configured_rule_with_names.php | Rector test config enabling rule with paramNames. |
| tests/Unit/Rector/RootResolverSignatureRector/config/configured_rule_partial_names.php | Rector test config enabling partial paramNames. |
| tests/Unit/Rector/RootResolverSignatureRector/bootstrap.php | Test bootstrap to load Larastan + Lighthouse config. |
| src/Schema/Values/FieldValue.php | Switch to shared RootType::namespaces() for root parent namespaces. |
| src/Schema/RootType.php | Add RootType::namespaces() helper to resolve configured namespaces. |
| src/Rector/RootResolverSignatureRector.php | New Rector rule implementing signature/type/name corrections. |
| docs/master/api-reference/resolvers.md | Document root resolver convention + Rector rule usage/config. |
| CHANGELOG.md | Note new Rector rule in Unreleased section. |
| .gitignore | Ignore docs/superpowers. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Use Rector's VariableRenamer to update all references to the old parameter name within the method body, not just the signature. #2779 (comment) 🤖 Generated with Claude Code
The standalone null type requires PHP 8.2+, but the package supports ^8. Use mixed as the safe default and note that PHP 8.2+ can use null. #2779 (comment) 🤖 Generated with Claude Code
Assertions can be disabled at runtime. Use a proper exception to guarantee validation regardless of PHP assert settings. #2779 (comment) 🤖 Generated with Claude Code
#2779 (comment) 🤖 Generated with Claude Code
🤖 Generated with Claude Code
🤖 Generated with Claude Code
PHPUnit 9 (Laravel 9) does not support PHP 8 attributes for data providers. Dual-annotating with both PHPDoc and attributes ensures tests run across the full CI matrix. 🤖 Generated with Claude Code
AbstractRectorTestCase sets up error/exception handlers that are not removed, triggering PHPUnit's risky test detection. Since configure() does not use constructor dependencies, use newInstanceWithoutConstructor to sidestep mocking final classes. 🤖 Generated with Claude Code
Remove rector from PHPStan CI job (it's only needed for PHPUnit tests). Rector's presence causes phpstan to resolve to incompatible versions: - rector/rector 1.x pulls phpstan to versions where Composer\InstalledVersions class discovery breaks - rector/rector 2.x requires phpstan ^2.2.2 which conflicts with larastan <3.10 Bump larastan minimum to ^3.10 (first version compatible with phpstan ^2.2). Revert phpstan minimum to ^1.12.18 || ^2 since removing rector from the PHPStan job eliminates the version conflict. 🤖 Generated with Claude Code
rector/rector is removed in the PHPStan CI job to avoid dependency conflicts, so its classes are unavailable for static analysis. 🤖 Generated with Claude Code
spawnia
commented
Jul 15, 2026
Throw RuntimeException when configured namespaces are empty or contain non-string values, return type is now non-empty-array<int, string>. Remove redundant empty check in RootResolverSignatureRector since RootType::namespaces() now handles this. #2779 (comment) 🤖 Generated with Claude Code
- Use named args for CodeSample and Param constructors - Break compound conditionals into multiple lines - Extract variables for readability - Use mixed in CodeSample (valid on all PHP ^8) - Make test fixtures more realistic (no pre-existing imports) - Use config() helper in bootstrap #2779 🤖 Generated with Claude Code
Fixtures expect `null $root` which is only valid on PHP 8.2+. Without pinning, tests would fail on PHP 8.0/8.1 where Rector produces `mixed $root` instead. #2779 🤖 Generated with Claude Code
spawnia
commented
Jul 15, 2026
#2779 🤖 Generated with Claude Code
Config may have string keys when defined as an associative array. #2779 🤖 Generated with Claude Code
When a resolver intentionally declares only a root param (e.g. `null $_` or `mixed $root`) with no args, the rule was incorrectly appending `array \$args`. ensureArgsParam now only fires when the root param was prepended by the rule itself (i.e. the resolver had array/untyped args as its only param), not when the root param already existed and was merely type-fixed. Add skip_root_only_param.php.inc fixture to cover this case, and update single_wrong_root_param.php.inc to expect only a type fix. 🤖 Generated with Claude Code
Single-param resolvers that don't match the canonical args signature (array $args) are now stripped entirely rather than having an args param incorrectly added. Only `(array $args)` triggers root prepending. 🤖 Generated with Claude Code
#2779 (comment) 🤖 Generated with Claude Code
Associative arrays from config would preserve string keys through array spread, potentially causing collisions that drop namespaces. #2779 (comment) 🤖 Generated with Claude Code
… are root resolvers https://mlllab.atlassian.net/browse/PFE-995 🤖 Generated with Claude Code
https://mlllab.atlassian.net/browse/PFE-995 🤖 Generated with Claude Code
…ref from resolvers https://mlllab.atlassian.net/browse/PFE-995 🤖 Generated with Claude Code
🤖 Generated with Claude Code
Comment on lines
+83
to
+90
| foreach ($paramNames as $name) { | ||
| if ($name !== null && ! is_string($name)) { | ||
| throw new InvalidConfigurationException('Each paramNames element must be a string or null.'); | ||
| } | ||
| } | ||
|
|
||
| $this->paramNames = $paramNames; | ||
| } |
Comment on lines
+192
to
+195
| $type = $method->params[0]->type; | ||
|
|
||
| return $type instanceof Identifier && $type->name === 'array'; | ||
| } |
Comment on lines
+205
to
+207
| if ($type instanceof Identifier && $type->name === 'array') { | ||
| return false; | ||
| } |
Comment on lines
+124
to
+126
| if ($rootWasPrepended && $this->ensureArgsParam($invokeMethod)) { | ||
| $changed = true; | ||
| } |
| public function __invoke(array $args): mixed {} | ||
|
|
||
| // After | ||
| public function __invoke(null $root, array $args): mixed {} |
| public function __invoke(User $root, array $args): mixed {} | ||
|
|
||
| // After | ||
| public function __invoke(null $root, array $args): mixed {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a configurable Rector rule (
RootResolverSignatureRector) that normalizes root resolver method signatures to match the canonical($root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo)pattern. Supports custom parameter names, custom context class, and PHP version-aware typing (mixedfor 8.0+,nullfor 8.2+).Single-param handling:
(array $args)— prepends root:(null $_, array $args). This is the only single-param case that triggers prepending.null $_,mixed $root, untyped $whatever) — strips the param entirely:(). These are useless root params or wrong signatures that static analysis will surface after removal.Known limitation: the rule assumes every
__invokeclass directly under the configured resolver namespaces is a root resolver. Non-root-resolver classes (e.g. nested field resolvers receiving a parent model as first argument) should be moved out of those directories or added to the Rector skip list. See docs/master/testing/rector.md.CI configuration
rector/rectorfrom PHPStan CI job (dependency conflicts with phpstan)src/Rectorandtests/Unit/Rectorfrom PHPStan paths (rector unavailable after removal)larastan/larastanto^3.10(compatible with phpstan ^2.2 that rector requires)null $root)rector/rectorin PHPUnit job (needed for Rector rule tests)Key files
src/Rector/RootResolverSignatureRector.php— the Rector rulesrc/Schema/RootType.php— added non-empty validation for namespaces, reindex to guarantee listtests/Unit/Rector/RootResolverSignatureRector/— test suitetests/Unit/Schema/RootTypeTest.php— RootType::namespaces() tests.github/workflows/validate.yml— CI matrix (modified)docs/master/testing/rector.md— documentation (moved from api-reference/resolvers.md)