From 0f089a7d88d70f9c07be1a4e137c72cbadad6578 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 26 Apr 2026 13:22:40 +0000
Subject: [PATCH 1/3] Use php-src workflow for doc-en sync
Agent-Logs-Url: https://github.com/voku/php-doc-fixer/sessions/7860c25c-293e-4add-b342-f9d01ea6229f
Co-authored-by: voku <264695+voku@users.noreply.github.com>
---
README.md | 16 +++++++++++-----
composer.json | 4 ++--
.../CliCommand/PhpDocFixerCommand.php | 4 ++--
tests/CliCommandTest.php | 12 ++++++++++++
tests/fixtures/doc-safe-array.xml | 9 +++++++++
.../stubs/doc-safe-array/doc-safe-array.php | 10 ++++++++++
6 files changed, 46 insertions(+), 9 deletions(-)
create mode 100644 tests/fixtures/doc-safe-array.xml
create mode 100644 tests/fixtures/stubs/doc-safe-array/doc-safe-array.php
diff --git a/README.md b/README.md
index 3ed4929..d9aebf8 100644
--- a/README.md
+++ b/README.md
@@ -5,9 +5,9 @@ This is a experiment! Lets check and fix the php documentation automatically.
### install
```bash
-git clone https://github.com/php/doc-en.git // optional: only if you want to check it
-git clone https://github.com/php/php-src.git // optional: by default we use the PhpStorm Stubs from vendor directory but you can also use different stubs
-git clone https://github.com/jetbrains/phpstorm-stubs.git // optional: by default we use the PhpStorm Stubs from vendor directory but you can also use external stubs
+git clone https://github.com/php/doc-en.git // required for the doc-en sync workflow
+git clone https://github.com/php/php-src.git // required for the doc-en sync workflow; this is the source of truth for synopsis updates
+git clone https://github.com/jetbrains/phpstorm-stubs.git // optional: useful for static analysis comparisons or alternative stub sources
git clone https://github.com/vimeo/psalm.git // optional: only if you want to check it
git clone https://github.com/phpstan/phpstan-src.git // optional: only if you want to check it
git clone https://github.com/voku/php-doc-fixer.git
@@ -22,8 +22,14 @@ composer scan-docs -- ../doc-en/reference/
composer fix-docs -- ../doc-en/reference/
```
+`scan-docs` and `fix-docs` use `../php-src` with `.stub.php` files so `php-src` is the source of truth for `php/doc-en` pull requests.
+
+The `run` command normalizes refined and pseudo-types back to manual-safe synopsis types by default, including collapsing array value info such as `int[]` to `array`. Pass `--remove-array-value-info="false"` if you need to keep that information for a custom comparison.
+
`fix-docs` re-checks the XML after updating files, so it exits successfully when all detected mismatches were applied automatically and reports the remaining count if manual follow-up is still needed. It can update both single types and union types in either direction when the XML synopsis and stub signature disagree.
+If the full `reference/` diff is too large for one review, run the same commands against extension or module subdirectories and open smaller pull requests, for example `../doc-en/reference/bc/` or `../doc-en/reference/mysqli/`.
+
### command for analysing static code analysis stubs (PHPStan, Psalm, ...)
```
php bin/phpdocfixer static_analysis [--remove-array-value-info="true"] [--stubs-path="vendor/jetbrains/phpstorm-stubs/"] [--stubs-file-extension=".php"] ../phpstan-src/resources/functionMap.php
@@ -50,9 +56,9 @@ php bin/phpdocfixer static_analysis ../psalm/dictionaries/CallMap_84.php
php bin/phpdocfixer run [--auto-fix="true"] [--remove-array-value-info="true"] [--stubs-path="../php-src/"] [--stubs-file-extension=".stub.php"] ../doc-en/reference/
```
-#### example: sync types from PhpStorm Stubs into the php-documentation
+#### example: sync types from php-src stubs into the php-documentation
```
-php bin/phpdocfixer run --auto-fix="true" --remove-array-value-info="true" ../doc-en/reference/
+php bin/phpdocfixer run --auto-fix="true" --stubs-path="../php-src/" --stubs-file-extension=".stub.php" ../doc-en/reference/
```
#### example: run the full scan and then apply all directly fixable updates
diff --git a/composer.json b/composer.json
index 9d0bb45..866cc19 100644
--- a/composer.json
+++ b/composer.json
@@ -38,8 +38,8 @@
},
"scripts": {
"test": "phpunit",
- "scan-docs": "@php bin/phpdocfixer run",
- "fix-docs": "@php bin/phpdocfixer run --auto-fix=true"
+ "scan-docs": "@php bin/phpdocfixer run --stubs-path=../php-src --stubs-file-extension=.stub.php",
+ "fix-docs": "@php bin/phpdocfixer run --auto-fix=true --stubs-path=../php-src --stubs-file-extension=.stub.php"
},
"bin": [
"bin/phpdocfixer"
diff --git a/src/voku/PhpDocFixer/CliCommand/PhpDocFixerCommand.php b/src/voku/PhpDocFixer/CliCommand/PhpDocFixerCommand.php
index 9b92c9d..7bb3e7c 100644
--- a/src/voku/PhpDocFixer/CliCommand/PhpDocFixerCommand.php
+++ b/src/voku/PhpDocFixer/CliCommand/PhpDocFixerCommand.php
@@ -44,8 +44,8 @@ public function configure(): void
'remove-array-value-info',
null,
\Symfony\Component\Console\Input\InputOption::VALUE_OPTIONAL,
- 'Automatically convert e.g. int[] into array. (false or true)',
- 'false'
+ 'Automatically convert e.g. int[] into array for manual-safe synopsis types. (false or true)',
+ 'true'
)->addOption(
'stubs-path',
null,
diff --git a/tests/CliCommandTest.php b/tests/CliCommandTest.php
index ee0e67d..c064c1c 100644
--- a/tests/CliCommandTest.php
+++ b/tests/CliCommandTest.php
@@ -202,6 +202,18 @@ public function testPhpDocFixerCommandPrefersNativeTypesOverStubPhpDoc(): void
static::assertStringContainsString('0 errors found', $commandTester->getDisplay());
}
+ public function testPhpDocFixerCommandNormalizesArrayValueInfoByDefault(): void
+ {
+ $commandTester = new CommandTester(new PhpDocFixerCommand());
+ $exitCode = $commandTester->execute([
+ 'path' => __DIR__ . '/fixtures/doc-safe-array.xml',
+ '--stubs-path' => __DIR__ . '/fixtures/stubs/doc-safe-array',
+ ]);
+
+ static::assertSame(Command::SUCCESS, $exitCode);
+ static::assertStringContainsString('0 errors found', $commandTester->getDisplay());
+ }
+
public function testStaticAnalysisCommandSucceedsForMatchingFixture(): void
{
$commandTester = new CommandTester(new StaticAnalysisFixerCommand());
diff --git a/tests/fixtures/doc-safe-array.xml b/tests/fixtures/doc-safe-array.xml
new file mode 100644
index 0000000..60af663
--- /dev/null
+++ b/tests/fixtures/doc-safe-array.xml
@@ -0,0 +1,9 @@
+
+
+
+
+ arraydoc_safe_array
+ arrayitems
+
+
+
diff --git a/tests/fixtures/stubs/doc-safe-array/doc-safe-array.php b/tests/fixtures/stubs/doc-safe-array/doc-safe-array.php
new file mode 100644
index 0000000..ab8917e
--- /dev/null
+++ b/tests/fixtures/stubs/doc-safe-array/doc-safe-array.php
@@ -0,0 +1,10 @@
+
Date: Sun, 26 Apr 2026 14:04:09 +0000
Subject: [PATCH 2/3] Add PHPStan pseudo-type normalization coverage
Agent-Logs-Url: https://github.com/voku/php-doc-fixer/sessions/d5444a9d-bef3-44e1-a3ca-24e83f2b154c
Co-authored-by: voku <264695+voku@users.noreply.github.com>
---
README.md | 2 ++
src/voku/PhpDocFixer/Type/TypeNormalizer.php | 12 +++++++-----
tests/CliCommandTest.php | 12 ++++++++++++
tests/fixtures/functionMap-phpstan-types.php | 15 +++++++++++++++
.../stubs/phpstan-types/phpstan-types.php | 18 ++++++++++++++++++
5 files changed, 54 insertions(+), 5 deletions(-)
create mode 100644 tests/fixtures/functionMap-phpstan-types.php
create mode 100644 tests/fixtures/stubs/phpstan-types/phpstan-types.php
diff --git a/README.md b/README.md
index d9aebf8..dd042dc 100644
--- a/README.md
+++ b/README.md
@@ -40,6 +40,8 @@ php bin/phpdocfixer static_analysis [--remove-array-value-info="true"] [--stubs-
php bin/phpdocfixer static_analysis --remove-array-value-info="true" --stubs-path="../php-src/" --stubs-file-extension=".stub.php" ../phpstan-src/resources/functionMap.php
```
+The static-analysis flow also normalizes PHPStan-only pseudo-types such as `class-string`, `list`, array shapes, callable signatures, and `int-mask<...>` back to comparable native types before reporting mismatches.
+
#### example: check types from phpstorm-stubs (mysqli) against static code analysis stubs from PHPStan
```
php bin/phpdocfixer static_analysis --stubs-path="../phpstorm-stubs/mysqli/" ../phpstan-src/resources/functionMap.php
diff --git a/src/voku/PhpDocFixer/Type/TypeNormalizer.php b/src/voku/PhpDocFixer/Type/TypeNormalizer.php
index 1b1da37..d9a2a9b 100644
--- a/src/voku/PhpDocFixer/Type/TypeNormalizer.php
+++ b/src/voku/PhpDocFixer/Type/TypeNormalizer.php
@@ -15,8 +15,10 @@ public function __construct(bool $removeArrayValueInfo = false)
public function normalize(string $type): string
{
- if (\strpos($type, '?') !== false) {
- $type = \str_replace('?', '', $type);
+ $type = \trim($type);
+
+ if (isset($type[0]) && $type[0] === '?') {
+ $type = \substr($type, 1);
$type .= '|null';
}
@@ -88,11 +90,11 @@ private function normalizeTypePart(string $typePart): string
$typePart = (string) \preg_replace('/\blist<.*>/', 'array', $typePart);
$typePart = (string) \preg_replace('/\barray\{.*\}/', 'array', $typePart);
$typePart = (string) \preg_replace('/\biterable<.*>/', 'iterable', $typePart);
- $typePart = (string) \preg_replace('/\b(?:pure-)?callable\(.*\)/', 'callable', $typePart);
- $typePart = (string) \preg_replace('/\bClosure\(.*\)/', 'callable', $typePart);
+ $typePart = (string) \preg_replace('/\b(?:pure-)?callable\(.*\)(?::.*)?/', 'callable', $typePart);
+ $typePart = (string) \preg_replace('/\bClosure\(.*\)(?::.*)?/', 'callable', $typePart);
$typePart = (string) \preg_replace('/\bobject\{.*\}/', 'object', $typePart);
$typePart = (string) \preg_replace('/\barray-key\b/', 'int|string', $typePart);
- $typePart = (string) \preg_replace('/\bresource \(closed\)\b/', 'resource', $typePart);
+ $typePart = (string) \preg_replace('/\bresource \(closed\)/', 'resource', $typePart);
if (\preg_match('/^[A-Za-z_][A-Za-z0-9_\\\\]*<.*>$/', $typePart)) {
$typePart = (string) \preg_replace('/<.*>$/', '', $typePart);
diff --git a/tests/CliCommandTest.php b/tests/CliCommandTest.php
index c064c1c..c78ec8e 100644
--- a/tests/CliCommandTest.php
+++ b/tests/CliCommandTest.php
@@ -274,4 +274,16 @@ public function testStaticAnalysisCommandNormalizesRefinedTypesToNativeTypes():
static::assertSame(Command::SUCCESS, $exitCode);
static::assertStringContainsString('0 errors found', $commandTester->getDisplay());
}
+
+ public function testStaticAnalysisCommandNormalizesPhpStanPseudoTypesToNativeTypes(): void
+ {
+ $commandTester = new CommandTester(new StaticAnalysisFixerCommand());
+ $exitCode = $commandTester->execute([
+ 'path' => __DIR__ . '/fixtures/functionMap-phpstan-types.php',
+ '--stubs-path' => __DIR__ . '/fixtures/stubs/phpstan-types',
+ ]);
+
+ static::assertSame(Command::SUCCESS, $exitCode);
+ static::assertStringContainsString('0 errors found', $commandTester->getDisplay());
+ }
}
diff --git a/tests/fixtures/functionMap-phpstan-types.php b/tests/fixtures/functionMap-phpstan-types.php
new file mode 100644
index 0000000..107125d
--- /dev/null
+++ b/tests/fixtures/functionMap-phpstan-types.php
@@ -0,0 +1,15 @@
+ [
+ 'list',
+ 'value' => 'non-empty-string',
+ 'callback' => 'pure-callable(int):literal-string',
+ 'class' => 'class-string',
+ 'items' => 'iterable',
+ 'map' => 'array{foo:int,bar?:string}',
+ 'payload' => 'object{foo:int}',
+ 'mask' => 'int-mask<1, 2, 4>',
+ 'stream' => 'resource (closed)',
+ ],
+];
diff --git a/tests/fixtures/stubs/phpstan-types/phpstan-types.php b/tests/fixtures/stubs/phpstan-types/phpstan-types.php
new file mode 100644
index 0000000..1013530
--- /dev/null
+++ b/tests/fixtures/stubs/phpstan-types/phpstan-types.php
@@ -0,0 +1,18 @@
+
Date: Sun, 26 Apr 2026 14:05:01 +0000
Subject: [PATCH 3/3] Tighten PHPStan pseudo-type normalization
Agent-Logs-Url: https://github.com/voku/php-doc-fixer/sessions/d5444a9d-bef3-44e1-a3ca-24e83f2b154c
Co-authored-by: voku <264695+voku@users.noreply.github.com>
---
src/voku/PhpDocFixer/Type/TypeNormalizer.php | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/voku/PhpDocFixer/Type/TypeNormalizer.php b/src/voku/PhpDocFixer/Type/TypeNormalizer.php
index d9a2a9b..8e5bf4d 100644
--- a/src/voku/PhpDocFixer/Type/TypeNormalizer.php
+++ b/src/voku/PhpDocFixer/Type/TypeNormalizer.php
@@ -18,7 +18,7 @@ public function normalize(string $type): string
$type = \trim($type);
if (isset($type[0]) && $type[0] === '?') {
- $type = \substr($type, 1);
+ $type = \trim(\substr($type, 1));
$type .= '|null';
}
@@ -90,11 +90,11 @@ private function normalizeTypePart(string $typePart): string
$typePart = (string) \preg_replace('/\blist<.*>/', 'array', $typePart);
$typePart = (string) \preg_replace('/\barray\{.*\}/', 'array', $typePart);
$typePart = (string) \preg_replace('/\biterable<.*>/', 'iterable', $typePart);
- $typePart = (string) \preg_replace('/\b(?:pure-)?callable\(.*\)(?::.*)?/', 'callable', $typePart);
- $typePart = (string) \preg_replace('/\bClosure\(.*\)(?::.*)?/', 'callable', $typePart);
+ $typePart = (string) \preg_replace('/^(?:pure-)?callable\(.*\)(?::.+)?$/', 'callable', $typePart);
+ $typePart = (string) \preg_replace('/^Closure\(.*\)(?::.+)?$/', 'callable', $typePart);
$typePart = (string) \preg_replace('/\bobject\{.*\}/', 'object', $typePart);
$typePart = (string) \preg_replace('/\barray-key\b/', 'int|string', $typePart);
- $typePart = (string) \preg_replace('/\bresource \(closed\)/', 'resource', $typePart);
+ $typePart = (string) \preg_replace('/^resource \(closed\)$/', 'resource', $typePart);
if (\preg_match('/^[A-Za-z_][A-Za-z0-9_\\\\]*<.*>$/', $typePart)) {
$typePart = (string) \preg_replace('/<.*>$/', '', $typePart);