Skip to content

Commit

Permalink
Prefer bin/split-phpstan-baseline, deprecate formatter approach (#10)
Browse files Browse the repository at this point in the history
* Prefer bin/split-phpstan-baseline, deprecate formatter approach

* Fix composer autoload path

* Wording

* Fix readme

* Avoid confusion in readme

* Fix deprecation message in formatter
  • Loading branch information
janedbal authored Nov 21, 2024
1 parent e554033 commit 217eb3c
Show file tree
Hide file tree
Showing 9 changed files with 284 additions and 41 deletions.
49 changes: 35 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ Each file looks like this:
# total 1 error
parameters:
ignoreErrors:
-
message: '#^Construct empty\(\) is not allowed\. Use more strict comparison\.$#'
path: ../app/index.php
count: 1
ignoreErrors:
-
message: '#^Construct empty\(\) is not allowed\. Use more strict comparison\.$#'
path: ../app/index.php
count: 1
```

## Installation:
Expand All @@ -30,20 +30,40 @@ parameters:
composer require --dev shipmonk/phpstan-baseline-per-identifier
```

Use [official extension-installer](https://phpstan.org/user-guide/extension-library#installing-extensions) or just load the extension:
## Usage

Setup baselines loader, other files will be placed beside that file:
```neon
# phpstan.neon.dist
includes:
- vendor/shipmonk/phpstan-baseline-per-identifier/extension.neon
- baselines/loader.neon # instead of traditional phpstan-baseline.neon
```

Run native baseline generation and split it into multiple files via our script:
```sh
vendor/bin/phpstan --generate-baseline=baselines/loader.neon && vendor/bin/split-phpstan-baseline baselines/loader.neon
```

## Usage:
_(optional)_ You can simplify generation with e.g. composer script:
```json
{
"scripts": {
"generate:baseline:phpstan": [
"@phpstan --generate-baseline=baselines/loader.neon",
"@split-phpstan-baseline baselines/loader.neon"
]
}
}
```

<details>
<summary><h3>Legacy usage</h3></summary>

Setup where your baseline files should be stored and include its loader:
```neon
# phpstan.neon.dist
includes:
- vendor/shipmonk/phpstan-baseline-per-identifier/extension.neon # or use extension-installer
- baselines/loader.neon
parameters:
Expand All @@ -66,14 +86,15 @@ Prepare composer script to simplify generation:
}
```

Regenerate the baselines:
</details>

```sh
composer generate:baseline:phpstan
```
## Cli options
- ``--tabs`` to use tabs as indents in generated neon files

## Migration from single baseline
## Migrating from single baseline

1. `rm phpstan-baseline.neon` (and remove its include from `phpstan.neon.dist`)
2. `mkdir baselines`
3. `composer generate:baseline:phpstan`
3. `touch baselines/loader.neon` (and include it in `phpstan.neon.dist`)
4. Run the split script from above

88 changes: 88 additions & 0 deletions bin/split-phpstan-baseline
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#!/usr/bin/env php
<?php declare(strict_types=1);

use Nette\Neon\Exception as NeonException;
use Nette\Neon\Neon;
use ShipMonk\PHPStan\Baseline\NeonHelper;

$autoloadFiles = [
__DIR__ . '/../../../autoload.php',
__DIR__ . '/../vendor/autoload.php',
];

foreach ($autoloadFiles as $autoloadFile) {
if (file_exists($autoloadFile)) {
require_once $autoloadFile;
break;
}
}

$providedOptions = getopt('', ['tabs'], $restIndex);
$args = array_slice($argv, $restIndex);

$loaderFile = $args[0] ?? null;
$indent = isset($providedOptions['tabs'])
? "\t"
: ' ';

if ($loaderFile === null) {
fwrite(STDERR, "\n! Missing argument of generated baseline file.\n\n");
exit(1);
}

if (!is_file($loaderFile)) {
fwrite(STDERR, "\n! File '$loaderFile' not found\n\n");
exit(1);
}

$splFile = new SplFileInfo($loaderFile);

$folder = $splFile->getPath();
$extension = $splFile->getExtension();

if ($extension !== 'neon') {
fwrite(STDERR, "\n! Invalid file extension '$extension' of '$loaderFile', expected neon file\n\n");
exit(1);
}

try {
$data = Neon::decodeFile($loaderFile);
} catch (NeonException $e) {
fwrite(STDERR, "\n! Invalid argument, expected a valid neon file: " . $e->getMessage() . "\n\n");
exit(1);
}
if (!isset($data['parameters']['ignoreErrors'])) {
fwrite(
STDERR,
"\n! Invalid argument, expected neon file with 'parameters.ignoreErrors' key in '$loaderFile'." .
"\n - Did you run native baseline generation first?" .
"\n - You can so via vendor/bin/phpstan --generate-baseline=$loaderFile\n\n"
);
exit(1);
}

$groupedErrors = [];
foreach ($data['parameters']['ignoreErrors'] as $error) {
$identifier = $error['identifier'] ?? 'missing-identifier';
unset($error['identifier']);
$groupedErrors[$identifier][] = $error;
}

ksort($groupedErrors);

$loaderData = [];

foreach ($groupedErrors as $identifier => $errors) {
$filePath = $folder . '/' . $identifier . '.neon';
$loaderData['includes'][] = $identifier . '.neon';
$outputData = ['parameters' => ['ignoreErrors' => $errors]];
$errorsCount = count($errors);
$plural = $errorsCount === 1 ? '' : 's';
$prefix = "# total $errorsCount error$plural\n\n";
$contents = $prefix . NeonHelper::encode($outputData, $indent);
file_put_contents($filePath, $contents);
echo "Writing baseline file $filePath with $errorsCount errors\n";
}

file_put_contents($loaderFile, NeonHelper::encode($loaderData, $indent));
echo "Writing baseline loader to $loaderFile\n";
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
"ShipMonk\\PHPStan\\Baseline\\": "tests/"
}
},
"bin": [
"bin/split-phpstan-baseline"
],
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": false,
Expand Down
39 changes: 15 additions & 24 deletions src/BaselinePerIdentifierFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace ShipMonk\PHPStan\Baseline;

use LogicException;
use Nette\Neon\Neon;
use PHPStan\Command\AnalysisResult;
use PHPStan\Command\ErrorFormatter\ErrorFormatter;
use PHPStan\Command\Output;
Expand All @@ -14,14 +13,15 @@
use function implode;
use function ksort;
use function preg_quote;
use function preg_replace;
use function realpath;
use function sprintf;
use function str_repeat;
use function trim;
use const DIRECTORY_SEPARATOR;
use const SORT_STRING;

/**
* @deprecated Use new approach, see readme
*/
class BaselinePerIdentifierFormatter implements ErrorFormatter
{

Expand Down Expand Up @@ -86,9 +86,9 @@ public function formatErrors(

foreach ($fileErrorsCounts as $message => $count) {
$errorsToOutput[] = [
'message' => $this->escape('#^' . preg_quote($message, '#') . '$#'),
'message' => NeonHelper::escape('#^' . preg_quote($message, '#') . '$#'),
'count' => $count,
'path' => $this->escape($file),
'path' => NeonHelper::escape($file),
];
}
}
Expand All @@ -99,38 +99,29 @@ public function formatErrors(

$output->writeLineFormatted(sprintf('Writing baseline file %s with %d errors', $baselineFilePath, $errorsCount));

$prefix = "# total $errorsCount errors\n\n";
$contents = $prefix . $this->getNeon(['parameters' => ['ignoreErrors' => $errorsToOutput]]);
$plurality = $errorsCount === 1 ? '' : 's';
$prefix = "# total $errorsCount error$plurality\n\n";
$contents = $prefix . NeonHelper::encode(['parameters' => ['ignoreErrors' => $errorsToOutput]], $this->indent);
$written = file_put_contents($baselineFilePath, $contents);

if ($written === false) {
throw new LogicException('Error while writing to ' . $baselineFilePath);
}
}

$writtenLoader = file_put_contents($this->baselinesDir . '/loader.neon', $this->getNeon(['includes' => $includes]));
$writtenLoader = file_put_contents($this->baselinesDir . '/loader.neon', NeonHelper::encode(['includes' => $includes], $this->indent));

if ($writtenLoader === false) {
throw new LogicException('Error while writing to ' . $this->baselinesDir . '/loader.neon');
}

return 0;
}

private function getNeon(mixed $data): string
{
return trim(Neon::encode($data, blockMode: true, indentation: $this->indent)) . "\n";
}
$output->writeLineFormatted('');
$output->writeLineFormatted('⚠️ <comment>You are using deprecated approach to split baselines which cannot utilize PHPStan result cache</comment> ⚠️');
$output->writeLineFormatted(' Consider switching to new approach via:');
$output->writeLineFormatted(" vendor/bin/phpstan --generate-baseline=$this->baselinesDir/loader.neon && vendor/bin/split-phpstan-baseline $this->baselinesDir/loader.neon");
$output->writeLineFormatted('');

private function escape(string $value): string
{
$return = preg_replace('#^@|%#', '$0$0', $value);

if ($return === null) {
throw new LogicException('Error while escaping ' . $value);
}

return $return;
return 0;
}

private function getPathDifference(string $from, string $to): string
Expand Down
29 changes: 29 additions & 0 deletions src/NeonHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php declare(strict_types = 1);

namespace ShipMonk\PHPStan\Baseline;

use LogicException;
use Nette\Neon\Neon;
use function preg_replace;
use function trim;

class NeonHelper
{

public static function encode(mixed $data, string $indent): string
{
return trim(Neon::encode($data, blockMode: true, indentation: $indent)) . "\n";
}

public static function escape(string $value): string
{
$return = preg_replace('#^@|%#', '$0$0', $value);

if ($return === null) {
throw new LogicException('Error while escaping ' . $value);
}

return $return;
}

}
Loading

0 comments on commit 217eb3c

Please sign in to comment.