From 2fba281b149ae8b2127e9acaa45e6df934cace76 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 4 Mar 2026 12:00:42 +0100 Subject: [PATCH] Fix PHPDoc placement before PHP 8 class attributes and add regression tests (#1769) * Initial plan * Add tests for PHP class attribute placement and fix whitespace in regex Co-authored-by: barryvdh <973269+barryvdh@users.noreply.github.com> * Add advanced edge case tests: final+nested-brackets and multiple attributes Co-authored-by: barryvdh <973269+barryvdh@users.noreply.github.com> * Add PHP 8.5 test for closures inside attribute arguments Co-authored-by: barryvdh <973269+barryvdh@users.noreply.github.com> * composer fix-style --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: barryvdh <973269+barryvdh@users.noreply.github.com> Co-authored-by: laravel-ide-helper --- .gitignore | 1 + src/Console/ModelsCommand.php | 2 +- .../Models/FinalWithNested.php | 14 ++++ .../Models/MultipleAttributes.php | 15 +++++ .../Models/Simple.php | 12 ++++ .../PhpAttributesBeforeClass/Test.php | 24 +++++++ .../__snapshots__/Test__test__1.php | 65 +++++++++++++++++++ .../Models/ClosureInAttribute.php | 14 ++++ .../PhpAttributesClosureInAttribute/Test.php | 27 ++++++++ .../__snapshots__/Test__test__1.php | 22 +++++++ 10 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 tests/Console/ModelsCommand/PhpAttributesBeforeClass/Models/FinalWithNested.php create mode 100644 tests/Console/ModelsCommand/PhpAttributesBeforeClass/Models/MultipleAttributes.php create mode 100644 tests/Console/ModelsCommand/PhpAttributesBeforeClass/Models/Simple.php create mode 100644 tests/Console/ModelsCommand/PhpAttributesBeforeClass/Test.php create mode 100644 tests/Console/ModelsCommand/PhpAttributesBeforeClass/__snapshots__/Test__test__1.php create mode 100644 tests/Console/ModelsCommand/PhpAttributesClosureInAttribute/Models/ClosureInAttribute.php create mode 100644 tests/Console/ModelsCommand/PhpAttributesClosureInAttribute/Test.php create mode 100644 tests/Console/ModelsCommand/PhpAttributesClosureInAttribute/__snapshots__/Test__test__1.php diff --git a/.gitignore b/.gitignore index 0cdbc31..71de373 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .phpunit.result.cache +/auth.json /build /.idea /.php-cs-fixer.cache diff --git a/src/Console/ModelsCommand.php b/src/Console/ModelsCommand.php index 757c3d7..9c65627 100644 --- a/src/Console/ModelsCommand.php +++ b/src/Console/ModelsCommand.php @@ -1206,7 +1206,7 @@ class ModelsCommand extends Command // declaration, insert the docblock before the first attribute so that // the resulting order is: docblock → attributes → class. $before = substr($contents, 0, $pos); - if (preg_match('/(\s*(?:#\[.+?\]\s*)+)$/s', $before, $matches)) { + if (preg_match('/((?:#\[.+?\]\s*)+)$/s', $before, $matches)) { $pos -= strlen($matches[1]); $replace = "{$modelDocComment}\n"; } diff --git a/tests/Console/ModelsCommand/PhpAttributesBeforeClass/Models/FinalWithNested.php b/tests/Console/ModelsCommand/PhpAttributesBeforeClass/Models/FinalWithNested.php new file mode 100644 index 0000000..aa43c69 --- /dev/null +++ b/tests/Console/ModelsCommand/PhpAttributesBeforeClass/Models/FinalWithNested.php @@ -0,0 +1,14 @@ +app->make(ModelsCommand::class); + + $tester = $this->runCommand($command, [ + '--write' => true, + ]); + + $this->assertSame(0, $tester->getStatusCode()); + $this->assertStringContainsString('Written new phpDocBlock to', $tester->getDisplay()); + $this->assertMatchesMockedSnapshot(); + } +} diff --git a/tests/Console/ModelsCommand/PhpAttributesBeforeClass/__snapshots__/Test__test__1.php b/tests/Console/ModelsCommand/PhpAttributesBeforeClass/__snapshots__/Test__test__1.php new file mode 100644 index 0000000..6c6fd67 --- /dev/null +++ b/tests/Console/ModelsCommand/PhpAttributesBeforeClass/__snapshots__/Test__test__1.php @@ -0,0 +1,65 @@ +|FinalWithNested newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|FinalWithNested newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|FinalWithNested query() + * @method static \Illuminate\Database\Eloquent\Builder|FinalWithNested whereId($value) + * @mixin \Eloquent + */ +#[ObservedByStub([StubObserver::class])] +final class FinalWithNested extends Model +{ + protected $table = 'simples'; +} +|MultipleAttributes newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|MultipleAttributes newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|MultipleAttributes query() + * @method static \Illuminate\Database\Eloquent\Builder|MultipleAttributes whereId($value) + * @mixin \Eloquent + */ +#[\AllowDynamicProperties] +#[SecondAttribute] +class MultipleAttributes extends Model +{ + protected $table = 'simples'; +} +|Simple newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Simple newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|Simple query() + * @method static \Illuminate\Database\Eloquent\Builder|Simple whereId($value) + * @mixin \Eloquent + */ +#[\AllowDynamicProperties] +class Simple extends Model +{ +} diff --git a/tests/Console/ModelsCommand/PhpAttributesClosureInAttribute/Models/ClosureInAttribute.php b/tests/Console/ModelsCommand/PhpAttributesClosureInAttribute/Models/ClosureInAttribute.php new file mode 100644 index 0000000..a1a59b8 --- /dev/null +++ b/tests/Console/ModelsCommand/PhpAttributesClosureInAttribute/Models/ClosureInAttribute.php @@ -0,0 +1,14 @@ + true)] +class ClosureInAttribute extends Model +{ + protected $table = 'simples'; +} diff --git a/tests/Console/ModelsCommand/PhpAttributesClosureInAttribute/Test.php b/tests/Console/ModelsCommand/PhpAttributesClosureInAttribute/Test.php new file mode 100644 index 0000000..1208731 --- /dev/null +++ b/tests/Console/ModelsCommand/PhpAttributesClosureInAttribute/Test.php @@ -0,0 +1,27 @@ +app->make(ModelsCommand::class); + + $tester = $this->runCommand($command, [ + '--write' => true, + ]); + + $this->assertSame(0, $tester->getStatusCode()); + $this->assertStringContainsString('Written new phpDocBlock to', $tester->getDisplay()); + $this->assertMatchesMockedSnapshot(); + } +} diff --git a/tests/Console/ModelsCommand/PhpAttributesClosureInAttribute/__snapshots__/Test__test__1.php b/tests/Console/ModelsCommand/PhpAttributesClosureInAttribute/__snapshots__/Test__test__1.php new file mode 100644 index 0000000..77e0f03 --- /dev/null +++ b/tests/Console/ModelsCommand/PhpAttributesClosureInAttribute/__snapshots__/Test__test__1.php @@ -0,0 +1,22 @@ +|ClosureInAttribute newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|ClosureInAttribute newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|ClosureInAttribute query() + * @method static \Illuminate\Database\Eloquent\Builder|ClosureInAttribute whereId($value) + * @mixin \Eloquent + */ +#[SomeClosureAttr(fn() => true)] +class ClosureInAttribute extends Model +{ + protected $table = 'simples'; +}