22
33namespace PHPStan \Command ;
44
5+ use Nette \DI \Config \Loader ;
6+ use Nette \FileNotFoundException ;
7+ use Nette \InvalidStateException ;
58use OndraM \CiDetector \CiDetector ;
69use Override ;
10+ use PHPStan \Analyser \Ignore \BaselineIgnoredErrorHelper ;
711use PHPStan \Analyser \InternalError ;
812use PHPStan \Command \ErrorFormatter \BaselineNeonErrorFormatter ;
913use PHPStan \Command \ErrorFormatter \BaselinePhpErrorFormatter ;
@@ -120,6 +124,7 @@ protected function configure(): void
120124 new InputOption ('watch ' , mode: InputOption::VALUE_NONE , description: 'Launch PHPStan Pro ' ),
121125 new InputOption ('pro ' , mode: InputOption::VALUE_NONE , description: 'Launch PHPStan Pro ' ),
122126 new InputOption ('fail-without-result-cache ' , mode: InputOption::VALUE_NONE , description: 'Return non-zero exit code when result cache is not used ' ),
127+ new InputOption ('only-remove-errors ' , mode: InputOption::VALUE_NONE , description: 'Only remove existing errors from the baseline. Do not add new ones. ' ),
123128 ]);
124129 }
125130
@@ -158,6 +163,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
158163 $ pro = (bool ) $ input ->getOption ('watch ' ) || (bool ) $ input ->getOption ('pro ' );
159164 $ fix = (bool ) $ input ->getOption ('fix ' );
160165 $ failWithoutResultCache = (bool ) $ input ->getOption ('fail-without-result-cache ' );
166+ $ onlyRemoveErrors = (bool ) $ input ->getOption ('only-remove-errors ' );
161167
162168 /** @var string|false|null $generateBaselineFile */
163169 $ generateBaselineFile = $ input ->getOption ('generate-baseline ' );
@@ -236,6 +242,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int
236242 $ inceptionResult ->getErrorOutput ()->getStyle ()->note ('The --fix CLI option no longer launches PHPStan Pro. Use --pro instead if you want to launch PHPStan Pro ' );
237243 }
238244
245+ if ($ generateBaselineFile === null && $ onlyRemoveErrors ) {
246+ $ inceptionResult ->getStdOutput ()->getStyle ()->error ('You must pass the --generate-baseline option alongside --only-remove-errors. ' );
247+ return $ inceptionResult ->handleReturn (1 , null , $ this ->analysisStartTime );
248+ }
249+
239250 $ errorOutput = $ inceptionResult ->getErrorOutput ();
240251 $ errorFormat = $ input ->getOption ('error-format ' );
241252
@@ -508,7 +519,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
508519 return $ inceptionResult ->handleReturn (1 , $ analysisResult ->getPeakMemoryUsageBytes (), $ this ->analysisStartTime , $ analysisResult ->getWorkerCount ());
509520 }
510521
511- return $ this ->generateBaseline ($ generateBaselineFile , $ inceptionResult , $ analysisResult , $ output , $ allowEmptyBaseline , $ baselineExtension , $ failWithoutResultCache );
522+ return $ this ->generateBaseline ($ generateBaselineFile , $ inceptionResult , $ analysisResult , $ output , $ allowEmptyBaseline , $ baselineExtension , $ failWithoutResultCache, $ onlyRemoveErrors , $ container );
512523 }
513524
514525 /** @var ErrorFormatter $errorFormatter */
@@ -757,8 +768,14 @@ private function getMessageFromInternalError(FileHelper $fileHelper, InternalErr
757768 return $ message ;
758769 }
759770
760- private function generateBaseline (string $ generateBaselineFile , InceptionResult $ inceptionResult , AnalysisResult $ analysisResult , OutputInterface $ output , bool $ allowEmptyBaseline , string $ baselineExtension , bool $ failWithoutResultCache ): int
771+ private function generateBaseline (string $ generateBaselineFile , InceptionResult $ inceptionResult , AnalysisResult $ analysisResult , OutputInterface $ output , bool $ allowEmptyBaseline , string $ baselineExtension , bool $ failWithoutResultCache, bool $ onlyRemoveErrors , Container $ container ): int
761772 {
773+ $ baselineFileDirectory = dirname ($ generateBaselineFile );
774+ $ baselinePathHelper = new ParentDirectoryRelativePathHelper ($ baselineFileDirectory );
775+ if ($ onlyRemoveErrors ) {
776+ $ analysisResult = $ this ->filterAnalysisResultForExistingErrors ($ analysisResult , $ generateBaselineFile , $ inceptionResult , $ container , $ baselinePathHelper );
777+ }
778+
762779 if (!$ allowEmptyBaseline && !$ analysisResult ->hasErrors ()) {
763780 $ inceptionResult ->getStdOutput ()->getStyle ()->error ('No errors were found during the analysis. Baseline could not be generated. ' );
764781 $ inceptionResult ->getStdOutput ()->writeLineFormatted ('To allow generating empty baselines, pass <fg=cyan>--allow-empty-baseline</> option. ' );
@@ -769,8 +786,6 @@ private function generateBaseline(string $generateBaselineFile, InceptionResult
769786 $ streamOutput = $ this ->createStreamOutput ();
770787 $ errorConsoleStyle = new ErrorsConsoleStyle (new StringInput ('' ), $ streamOutput );
771788 $ baselineOutput = new SymfonyOutput ($ streamOutput , new SymfonyStyle ($ errorConsoleStyle ));
772- $ baselineFileDirectory = dirname ($ generateBaselineFile );
773- $ baselinePathHelper = new ParentDirectoryRelativePathHelper ($ baselineFileDirectory );
774789 $ rawMessageInBaseline = $ inceptionResult ->getContainer ()->getParameter ('featureToggles ' )['rawMessageInBaseline ' ];
775790
776791 if ($ baselineExtension === 'php ' ) {
@@ -842,6 +857,20 @@ private function generateBaseline(string $generateBaselineFile, InceptionResult
842857 return $ inceptionResult ->handleReturn ($ exitCode , $ analysisResult ->getPeakMemoryUsageBytes (), $ this ->analysisStartTime , $ analysisResult ->getWorkerCount ());
843858 }
844859
860+ private function filterAnalysisResultForExistingErrors (AnalysisResult $ analysisResult , string $ generateBaselineFile , InceptionResult $ inceptionResult , Container $ container , ParentDirectoryRelativePathHelper $ baselinePathHelper ): AnalysisResult
861+ {
862+ $ currentAnalysisErrors = $ analysisResult ->getFileSpecificErrors ();
863+
864+ $ currentBaselinedErrors = $ this ->getCurrentBaselinedErrors ($ generateBaselineFile , $ inceptionResult );
865+
866+ /** @var BaselineIgnoredErrorHelper $baselineIgnoredErrorsHelper */
867+ $ baselineIgnoredErrorsHelper = $ container ->getByType (BaselineIgnoredErrorHelper::class);
868+
869+ $ nextBaselinedErrors = $ baselineIgnoredErrorsHelper ->removeUnusedIgnoredErrors ($ currentBaselinedErrors , $ currentAnalysisErrors , $ baselinePathHelper );
870+
871+ return $ analysisResult ->withFileSpecificErrors ($ nextBaselinedErrors );
872+ }
873+
845874 /**
846875 * @param string[] $files
847876 */
@@ -897,4 +926,23 @@ private function runDiagnoseExtensions(Container $container, Output $errorOutput
897926 }
898927 }
899928
929+ /**
930+ * @return mixed[][]
931+ */
932+ private function getCurrentBaselinedErrors (string $ generateBaselineFile , InceptionResult $ inceptionResult ): array
933+ {
934+ $ loader = new Loader ();
935+ try {
936+ $ currentBaselineConfig = $ loader ->load ($ generateBaselineFile );
937+ $ baselinedErrors = $ currentBaselineConfig ['parameters ' ]['ignoreErrors ' ] ?? [];
938+ } catch (FileNotFoundException ) {
939+ // currently no baseline file -> empty config
940+ $ baselinedErrors = [];
941+ } catch (InvalidStateException $ invalidStateException ) {
942+ $ inceptionResult ->getErrorOutput ()->writeLineFormatted ($ invalidStateException ->getMessage ());
943+ throw $ invalidStateException ;
944+ }
945+ return $ baselinedErrors ;
946+ }
947+
900948}
0 commit comments