Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prefer bin/split-phpstan-baseline, deprecate formatter approach #10

Merged
merged 6 commits into from
Nov 21, 2024
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
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
Loading