Skip to content

Add RootResolverSignatureRector to auto-fix root resolver signatures#2779

Draft
spawnia wants to merge 21 commits into
masterfrom
root-resolver-signature-rector-rule
Draft

Add RootResolverSignatureRector to auto-fix root resolver signatures#2779
spawnia wants to merge 21 commits into
masterfrom
root-resolver-signature-rector-rule

Conversation

@spawnia

@spawnia spawnia commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

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 (mixed for 8.0+, null fallback).

CI Experiment Status

Ran experiments to determine which PHP/Laravel matrix combinations break when keeping rector/rector and larastan/larastan installed in CI (previously both were removed from all jobs).

Findings

Rector itself installs and passes PHPStan cleanly across the entire matrix. No Rector-related PHPStan failures.

Remaining CI failures are all pre-existing or dependency-interaction issues:

Category Root Cause Jobs Affected Rector-related?
Composer\InstalledVersions unknown class PHPStan lowest deps can't resolve runtime Composer class PHPStan --prefer-lowest No
CanDirectiveTestBase::query() visibility conflict Newest orchestra/testbench added public query() method conflicting with test base class PHPStan/PHPUnit on L13 highest No
#[DataProvider] attribute not recognized PHPUnit 9.x (Laravel 9) doesn't support PHP 8 attributes for data providers PHPUnit with L9 Yes — fixable
getParamImmediatelyInvokedCallableTagValues() Larastan --prefer-lowest pulls incompatible phpstan/phpdoc-parser version PHPUnit --prefer-lowest No (larastan interaction)

Current workflow state (validate.yml)

  • rector/rector is kept in all three jobs (PHPStan, PHPUnit, coverage)
  • larastan/larastan is kept in PHPUnit job (was previously removed)
  • phpstan/phpstan-mockery and phpbench/phpbench are still removed from PHPUnit

Next steps to get CI green

  1. Fix #[DataProvider] compat: The Rector test classes use #[DataProvider('provideData')] which PHPUnit 9.x ignores (it needs @dataProvider docblock annotations). Either:

    • Add @dataProvider annotations alongside the attributes (dual-compat)
    • Or exclude Rector tests when PHPUnit <10 is resolved
  2. Decide on larastan in PHPUnit job: The Rector test bootstrap.php requires vendor/larastan/larastan/bootstrap.php to set up the Laravel container. Options:

    • Make the bootstrap self-contained (don't depend on larastan) — this would let us remove larastan from PHPUnit again and avoid the --prefer-lowest version conflicts
    • Keep larastan and accept lowest-deps failures (they're pre-existing anyway)
    • Add a conditional: only require larastan bootstrap if the file exists, skip Rector tests otherwise
  3. Pre-existing issues (not blockers for this PR):

    • CanDirectiveTestBase::query() needs renaming to avoid Testbench conflict
    • Composer\InstalledVersions error on lowest deps needs a phpstan ignore or bootstrap fix

Key files

  • src/Rector/RootResolverSignatureRector.php — the Rector rule
  • tests/Unit/Rector/RootResolverSignatureRector/ — test suite
  • tests/Unit/Rector/RootResolverSignatureRector/bootstrap.php — requires larastan bootstrap
  • .github/workflows/validate.yml — CI matrix (modified)

Suggested skills for next session

  • pr — pushing updates
  • gh — CI monitoring
  • babysit — watching CI after pushes
  • composer — if dependency constraints change

spawnia added 15 commits July 10, 2026 10:35
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
Cover wrong ResolveInfo type and untyped context/resolveInfo params.

🤖 Generated with Claude Code

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 RootResolverSignatureRector to detect/fix root resolver __invoke parameter order, presence, types, and (optionally) names.
  • Extract RootType::namespaces() and reuse it from FieldValue.
  • 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.

Comment thread src/Rector/RootResolverSignatureRector.php
Comment thread src/Rector/RootResolverSignatureRector.php
Comment thread docs/master/api-reference/resolvers.md Outdated
Comment thread CHANGELOG.md Outdated
spawnia added 6 commits July 10, 2026 13:23
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
🤖 Generated with Claude Code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants