Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -34,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<T>`, `list<T>`, 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
Expand All @@ -50,9 +58,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
Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions src/voku/PhpDocFixer/CliCommand/PhpDocFixerCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
12 changes: 7 additions & 5 deletions src/voku/PhpDocFixer/Type/TypeNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 = \trim(\substr($type, 1));
$type .= '|null';
}

Expand Down Expand Up @@ -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('/^(?: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\)\b/', '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);
Expand Down
24 changes: 24 additions & 0 deletions tests/CliCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -262,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());
}
}
9 changes: 9 additions & 0 deletions tests/fixtures/doc-safe-array.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<refentry xmlns="http://docbook.org/ns/docbook" version="5.0" xml:id="function.doc-safe-array">
<refsect1 role="description">
<methodsynopsis role="procedural">
<type>array</type><methodname>doc_safe_array</methodname>
<methodparam><type>array</type><parameter>items</parameter></methodparam>
</methodsynopsis>
</refsect1>
</refentry>
15 changes: 15 additions & 0 deletions tests/fixtures/functionMap-phpstan-types.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

return [
'phpstan_type_match' => [
'list<string>',
'value' => 'non-empty-string',
'callback' => 'pure-callable(int):literal-string',
'class' => 'class-string<stdClass>',
'items' => 'iterable<int,positive-int>',
'map' => 'array{foo:int,bar?:string}',
'payload' => 'object{foo:int}',
'mask' => 'int-mask<1, 2, 4>',
'stream' => 'resource (closed)',
],
];
10 changes: 10 additions & 0 deletions tests/fixtures/stubs/doc-safe-array/doc-safe-array.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

/**
* @param int[] $items
*
* @return string[]
*/
function doc_safe_array($items)
{
}
18 changes: 18 additions & 0 deletions tests/fixtures/stubs/phpstan-types/phpstan-types.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

/**
* @param resource $stream
*
* @return array
*/
function phpstan_type_match(
string $value,
callable $callback,
string $class,
iterable $items,
array $map,
object $payload,
int $mask,
$stream
) {
}