From 997c1993976f280d926f079fff64fd1d6da04ef6 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Fri, 31 May 2024 22:47:43 +0900 Subject: [PATCH] add --bare --- README.md | 18 ++++++++-- ecs.php | 13 +++---- phpstan-bodyscan.neon | 29 +++++++++++++++ rector.php | 14 ++++---- src/Command/RunCommand.php | 34 +++++++++++++----- src/PHPStanConfigFactory.php | 36 ++++++++++++++----- src/ValueObject/PHPStanConfig.php | 6 ---- .../expected-some-project-phpstan.neon | 2 ++ .../PHPStanConfigFactoryTest.php | 5 ++- 9 files changed, 114 insertions(+), 43 deletions(-) create mode 100644 phpstan-bodyscan.neon diff --git a/README.md b/README.md index d3513c8..369684d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,9 @@ [![Downloads total](https://img.shields.io/packagist/dt/tomasvotruba/phpstan-bodyscan.svg?style=flat-square)](https://packagist.org/packages/tomasvotruba/phpstan-bodyscan/stats) -Do you want to get quick glimpse of new project code quality? +* Do you want to get quick glimpse of new project code quality? +* Do you want to know, what PHPStan level is the best for your project? +* Do you want to know, how much errors you're facing per level to see how hard it will be to reach them? Get error count for each PHPStan level! @@ -10,7 +12,10 @@ Get error count for each PHPStan level! ## How does it work? -First, we look into the project root for `phpstan.neon` file. If found, we reuse its `parameters > paths` configuration. If not, we look for defaults source code paths like `/src`, `/app`, `/tests`, etc. +First, we look into the project root for `phpstan.neon` file. + +* If found, we reuse it. +* If not, we look for defaults source code paths like `/src`, `/app`, `/tests`, etc. Then we run PHPStan for each level from 0 to 8. We count errors and display them in a table. @@ -53,6 +58,15 @@ To get errors count per level:
+## Do you want to run levels without extensions? + +```bash +vendor/bin/phpstan-bodyscan --bare +``` + +
+ + ## Do you need a JSON format? We got you covered: diff --git a/ecs.php b/ecs.php index d2e270f..54010c0 100644 --- a/ecs.php +++ b/ecs.php @@ -1,17 +1,12 @@ withPaths([ - __DIR__ . '/bin', - __DIR__ . '/src', - __DIR__ . '/tests', - ]) - ->withRules([ - LineLengthFixer::class, - ]) + ->withPaths([__DIR__ . '/bin', __DIR__ . '/src', __DIR__ . '/tests']) + ->withRootFiles() + ->withRules([LineLengthFixer::class]) ->withPreparedSets(psr12: true, common: true, symplify: true); diff --git a/phpstan-bodyscan.neon b/phpstan-bodyscan.neon new file mode 100644 index 0000000..4bd6713 --- /dev/null +++ b/phpstan-bodyscan.neon @@ -0,0 +1,29 @@ +includes: + - vendor/symplify/phpstan-rules/config/code-complexity-rules.neon + - vendor/symplify/phpstan-rules/config/collector-rules.neon + - vendor/symplify/phpstan-rules/config/naming-rules.neon + - vendor/symplify/phpstan-rules/config/regex-rules.neon + - vendor/symplify/phpstan-rules/config/static-rules.neon + +parameters: + level: 8 + excludePaths: + - */Fixture/* + + paths: + - bin + - src + - tests + + unused_public: + methods: true + constants: true + properties: true + + type_coverage: + param: 99 + property: 99 + return: 99 + declare: 99 + + reportUnmatchedIgnoredErrors: false diff --git a/rector.php b/rector.php index e677893..e874621 100644 --- a/rector.php +++ b/rector.php @@ -5,13 +5,15 @@ use Rector\Config\RectorConfig; return RectorConfig::configure() - ->withPaths([ - __DIR__ . '/bin', - __DIR__ . '/src', - __DIR__ . '/tests', - ]) + ->withPaths([__DIR__ . '/bin', __DIR__ . '/src', __DIR__ . '/tests']) ->withPreparedSets( - deadCode: true, naming: true, privatization: true, earlyReturn: true, codeQuality: true, codingStyle: true, typeDeclarations: true + deadCode: true, + naming: true, + privatization: true, + earlyReturn: true, + codeQuality: true, + codingStyle: true, + typeDeclarations: true ) ->withPhpSets() ->withRootFiles() diff --git a/src/Command/RunCommand.php b/src/Command/RunCommand.php index 95dc9ca..38ed0ea 100644 --- a/src/Command/RunCommand.php +++ b/src/Command/RunCommand.php @@ -7,6 +7,7 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; use TomasVotruba\PHPStanBodyscan\Logger; @@ -22,6 +23,11 @@ final class RunCommand extends Command { + /** + * @var array + */ + private const DOT_STATES = [' ', '. ', '.. ', '...', '....', '.....']; + public function __construct( private readonly SymfonyStyle $symfonyStyle, private readonly AnalyseProcessFactory $analyseProcessFactory, @@ -51,9 +57,12 @@ protected function configure(): void InputOption::VALUE_NONE, 'Without any extensions, without ignores, without baselines, just pure PHPStan' ); + + // @todo nobaseline - without ignores and baseline files } /** + * @param ConsoleOutput $output * @return Command::* */ protected function execute(InputInterface $input, OutputInterface $output): int @@ -61,7 +70,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int /** @var string $projectDirectory */ $projectDirectory = getcwd(); - $minPhpStanLevel = (int) $input->getOption('min-level'); + $minPhpStanLevel = $input->getOption('min-level'); $maxPhpStanLevel = (int) $input->getOption('max-level'); Assert::lessThanEq($minPhpStanLevel, $maxPhpStanLevel); @@ -77,8 +86,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int // 1. prepare empty phpstan config // no baselines, ignores etc. etc :) - $phpstanConfiguration = $this->phpStanConfigFactory->create($projectDirectory, $isBare); - file_put_contents($projectDirectory . '/phpstan-bodyscan.neon', $phpstanConfiguration); + $phpstanConfig = $this->phpStanConfigFactory->create($projectDirectory, [], $isBare); + file_put_contents($projectDirectory . '/phpstan-bodyscan.neon', $phpstanConfig->getFileContents()); $levelResults = []; @@ -94,21 +103,28 @@ protected function execute(InputInterface $input, OutputInterface $output): int // 2. measure phpstan levels for ($phpStanLevel = $minPhpStanLevel; $phpStanLevel <= $maxPhpStanLevel; ++$phpStanLevel) { - $this->symfonyStyle->section(sprintf('Running PHPStan level %d %s', $phpStanLevel, $withExtensions ? - 'with extensions' : 'without extensions')); + $infoMessage = '' . sprintf('Running PHPStan level %d%s', $phpStanLevel, $isBare ? + ' without extensions' : '') . ''; + + $section = $output->section(); + + for ($i = 0; $i < 20; $i++) { + $stateIndex = $i % count(self::DOT_STATES); + $section->overwrite($infoMessage . ': ' . self::DOT_STATES[$stateIndex]); + usleep(700_000); + } $levelResult = $this->measureErrorCountInLevel($phpStanLevel, $projectDirectory, $envVariables); $levelResults[] = $levelResult; - $this->symfonyStyle->writeln(sprintf(' * Found %d errors', $levelResult->getErrorCount())); - $this->symfonyStyle->newLine(); + $section->overwrite(sprintf($infoMessage . ': found %d errors', $levelResult->getErrorCount())); } - if ($withExtensions === false) { + if ($isBare) { + // restore PHPStan extension file $this->symfonyStyle->writeln('Restoring PHPStan extensions...'); $this->symfonyStyle->newLine(); - // restore PHPStan extension file $phpstanExtensionFile = $projectDirectory . '/vendor/phpstan/extension-installer/src/GeneratedConfig.php'; rename($phpstanExtensionFile . '.bak', $phpstanExtensionFile); } diff --git a/src/PHPStanConfigFactory.php b/src/PHPStanConfigFactory.php index 2cc75a3..a2bcf15 100644 --- a/src/PHPStanConfigFactory.php +++ b/src/PHPStanConfigFactory.php @@ -20,7 +20,7 @@ final class PHPStanConfigFactory /** * @var string[] */ - private const PHPSTAN_FILE_NAMES = ['phpstan.neon', 'phpstan.neon.dist', 'phpstan.php', 'phpstan.php.dist']; + private const PHPSTAN_FILE_NAMES = ['phpstan.neon', 'phpstan.neon.dist']; /** * @param array $extraConfiguration @@ -28,7 +28,7 @@ final class PHPStanConfigFactory public function create(string $projectDirectory, array $extraConfiguration = [], bool $bare = false): PHPStanConfig { $existingPHPStanFile = null; - $phpstanFileName = null; + foreach (self::PHPSTAN_FILE_NAMES as $phpstanFileName) { if (file_exists($projectDirectory . '/' . $phpstanFileName)) { $existingPHPStanFile = $projectDirectory . '/' . $phpstanFileName; @@ -39,20 +39,26 @@ public function create(string $projectDirectory, array $extraConfiguration = [], // no config found? we have to create it if ($existingPHPStanFile === null) { $phpstanConfiguration = $this->createBasicPHPStanConfiguration($projectDirectory); - $phpstanNeon = $this->dumpToNeon($phpstanConfiguration); - return new PHPStanConfig($phpstanNeon, null); + $phpStanNeonContents = $this->dumpNeonToString($phpstanConfiguration); + return new PHPStanConfig($phpStanNeonContents); } // keep original setup if ($bare === false) { - return new PHPStanConfig(file_get_contents($existingPHPStanFile), $phpstanFileName); + $phpStanNeonContents = $this->loadFileAndMergeParameters($existingPHPStanFile, [ + 'parameters' => [ + // disable ignored error reporting, to make no fatal errors + 'reportUnmatchedIgnoredErrors' => false, + ], + ]); + return new PHPStanConfig($phpStanNeonContents); } $phpstanConfiguration = $this->createBarePHPStanConfiguration($existingPHPStanFile); $phpstanConfiguration = array_merge_recursive($phpstanConfiguration, $extraConfiguration); - $phpstanNeon = $this->dumpToNeon($phpstanConfiguration); - return new PHPStanConfig($phpstanNeon, $phpstanFileName); + $phpstanNeon = $this->dumpNeonToString($phpstanConfiguration); + return new PHPStanConfig($phpstanNeon); } /** @@ -93,7 +99,21 @@ private function createBasicPHPStanConfiguration(string $projectDirectory): arra ]; } - private function dumpToNeon(array $phpstanConfiguration): string + /** + * @param array $extraContents + */ + private function loadFileAndMergeParameters(string $existingPHPStanFile, array $extraContents): string + { + $neon = Neon::decodeFile($existingPHPStanFile); + $neon = array_merge_recursive($neon, $extraContents); + + return $this->dumpNeonToString($neon); + } + + /** + * @param array $phpstanConfiguration + */ + private function dumpNeonToString(array $phpstanConfiguration): string { $encodedNeon = Neon::encode($phpstanConfiguration, true, ' '); return trim($encodedNeon) . PHP_EOL; diff --git a/src/ValueObject/PHPStanConfig.php b/src/ValueObject/PHPStanConfig.php index d289fbd..bfbdc13 100644 --- a/src/ValueObject/PHPStanConfig.php +++ b/src/ValueObject/PHPStanConfig.php @@ -8,7 +8,6 @@ { public function __construct( private string $fileContents, - private ?string $originalFileName ) { } @@ -16,9 +15,4 @@ public function getFileContents(): string { return $this->fileContents; } - - public function getOriginalFileName(): ?string - { - return $this->originalFileName; - } } diff --git a/tests/PHPStanConfigFactory/Fixture/expected-some-project-phpstan.neon b/tests/PHPStanConfigFactory/Fixture/expected-some-project-phpstan.neon index 9d5c032..57a429d 100644 --- a/tests/PHPStanConfigFactory/Fixture/expected-some-project-phpstan.neon +++ b/tests/PHPStanConfigFactory/Fixture/expected-some-project-phpstan.neon @@ -5,3 +5,5 @@ parameters: excludePaths: - src/SomeOther + + reportUnmatchedIgnoredErrors: false diff --git a/tests/PHPStanConfigFactory/PHPStanConfigFactoryTest.php b/tests/PHPStanConfigFactory/PHPStanConfigFactoryTest.php index f0a5dbc..a34595f 100644 --- a/tests/PHPStanConfigFactory/PHPStanConfigFactoryTest.php +++ b/tests/PHPStanConfigFactory/PHPStanConfigFactoryTest.php @@ -21,9 +21,8 @@ protected function setUp(): void #[DataProvider('provideData')] public function test(string $projectDirectory, string $expectedPHPStanConfigFile): void { - $phpstanNeon = $this->phpStanConfigFactory->create($projectDirectory); - - $this->assertStringEqualsFile($expectedPHPStanConfigFile, $phpstanNeon->getFileContents()); + $phpStanConfig = $this->phpStanConfigFactory->create($projectDirectory); + $this->assertStringEqualsFile($expectedPHPStanConfigFile, $phpStanConfig->getFileContents()); } public static function provideData(): Iterator