2
2
3
3
namespace PHPStan \Command ;
4
4
5
+ use Nette \DI \Config \Loader ;
6
+ use Nette \FileNotFoundException ;
7
+ use Nette \InvalidStateException ;
5
8
use OndraM \CiDetector \CiDetector ;
9
+ use PHPStan \Analyser \Ignore \IgnoredError ;
6
10
use PHPStan \Analyser \InternalError ;
7
11
use PHPStan \Command \ErrorFormatter \BaselineNeonErrorFormatter ;
8
12
use PHPStan \Command \ErrorFormatter \BaselinePhpErrorFormatter ;
@@ -102,6 +106,7 @@ protected function configure(): void
102
106
new InputOption ('watch ' , null , InputOption::VALUE_NONE , 'Launch PHPStan Pro ' ),
103
107
new InputOption ('pro ' , null , InputOption::VALUE_NONE , 'Launch PHPStan Pro ' ),
104
108
new InputOption ('fail-without-result-cache ' , null , InputOption::VALUE_NONE , 'Return non-zero exit code when result cache is not used ' ),
109
+ new InputOption ('ignore-new-errors ' , null , InputOption::VALUE_NONE , 'Ignore new errors when generating the baseline. ' ),
105
110
]);
106
111
}
107
112
@@ -136,6 +141,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
136
141
$ debugEnabled = (bool ) $ input ->getOption ('debug ' );
137
142
$ fix = (bool ) $ input ->getOption ('fix ' ) || (bool ) $ input ->getOption ('watch ' ) || (bool ) $ input ->getOption ('pro ' );
138
143
$ failWithoutResultCache = (bool ) $ input ->getOption ('fail-without-result-cache ' );
144
+ $ ignoreNewErrors = (bool ) $ input ->getOption ('ignore-new-errors ' );
139
145
140
146
/** @var string|false|null $generateBaselineFile */
141
147
$ generateBaselineFile = $ input ->getOption ('generate-baseline ' );
@@ -182,6 +188,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int
182
188
return $ inceptionResult ->handleReturn (1 , null , $ this ->analysisStartTime );
183
189
}
184
190
191
+ if ($ generateBaselineFile === null && $ ignoreNewErrors ) {
192
+ $ inceptionResult ->getStdOutput ()->getStyle ()->error ('You must pass the --generate-baseline option alongside --ignore-new-errors. ' );
193
+ return $ inceptionResult ->handleReturn (1 , null , $ this ->analysisStartTime );
194
+ }
195
+
185
196
$ errorOutput = $ inceptionResult ->getErrorOutput ();
186
197
$ errorFormat = $ input ->getOption ('error-format ' );
187
198
@@ -411,7 +422,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
411
422
return $ inceptionResult ->handleReturn (1 , $ analysisResult ->getPeakMemoryUsageBytes (), $ this ->analysisStartTime );
412
423
}
413
424
414
- return $ this ->generateBaseline ($ generateBaselineFile , $ inceptionResult , $ analysisResult , $ output , $ allowEmptyBaseline , $ baselineExtension , $ failWithoutResultCache );
425
+ return $ this ->generateBaseline ($ generateBaselineFile , $ inceptionResult , $ analysisResult , $ output , $ allowEmptyBaseline , $ baselineExtension , $ failWithoutResultCache, $ ignoreNewErrors , $ container );
415
426
}
416
427
417
428
/** @var ErrorFormatter $errorFormatter */
@@ -587,8 +598,13 @@ private function getMessageFromInternalError(FileHelper $fileHelper, InternalErr
587
598
return $ message ;
588
599
}
589
600
590
- private function generateBaseline (string $ generateBaselineFile , InceptionResult $ inceptionResult , AnalysisResult $ analysisResult , OutputInterface $ output , bool $ allowEmptyBaseline , string $ baselineExtension , bool $ failWithoutResultCache ): int
601
+ private function generateBaseline (string $ generateBaselineFile , InceptionResult $ inceptionResult , AnalysisResult $ analysisResult , OutputInterface $ output , bool $ allowEmptyBaseline , string $ baselineExtension , bool $ failWithoutResultCache, bool $ ignoreNewErrors , Container $ container ): int
591
602
{
603
+ $ baselineFileDirectory = dirname ($ generateBaselineFile );
604
+ $ fileHelper = $ container ->getByType (FileHelper::class);
605
+ $ baselinePathHelper = new ParentDirectoryRelativePathHelper ($ baselineFileDirectory );
606
+ $ analysisResult = $ this ->processFileSpecificErrorsFromAnalysisResult ($ analysisResult , $ ignoreNewErrors , $ generateBaselineFile , $ inceptionResult , $ fileHelper , $ baselinePathHelper );
607
+
592
608
if (!$ allowEmptyBaseline && !$ analysisResult ->hasErrors ()) {
593
609
$ inceptionResult ->getStdOutput ()->getStyle ()->error ('No errors were found during the analysis. Baseline could not be generated. ' );
594
610
$ inceptionResult ->getStdOutput ()->writeLineFormatted ('To allow generating empty baselines, pass <fg=cyan>--allow-empty-baseline</> option. ' );
@@ -599,7 +615,6 @@ private function generateBaseline(string $generateBaselineFile, InceptionResult
599
615
$ streamOutput = $ this ->createStreamOutput ();
600
616
$ errorConsoleStyle = new ErrorsConsoleStyle (new StringInput ('' ), $ streamOutput );
601
617
$ baselineOutput = new SymfonyOutput ($ streamOutput , new SymfonyStyle ($ errorConsoleStyle ));
602
- $ baselineFileDirectory = dirname ($ generateBaselineFile );
603
618
$ baselinePathHelper = new ParentDirectoryRelativePathHelper ($ baselineFileDirectory );
604
619
605
620
if ($ baselineExtension === 'php ' ) {
@@ -674,6 +689,63 @@ private function generateBaseline(string $generateBaselineFile, InceptionResult
674
689
return $ inceptionResult ->handleReturn ($ exitCode , $ analysisResult ->getPeakMemoryUsageBytes (), $ this ->analysisStartTime );
675
690
}
676
691
692
+ private function processFileSpecificErrorsFromAnalysisResult (AnalysisResult $ analysisResult , bool $ ignoreNewErrors , string $ generateBaselineFile , InceptionResult $ inceptionResult , FileHelper $ fileHelper , RelativePathHelper $ baselinePathHelper ): AnalysisResult
693
+ {
694
+ $ fileSpecificErrors = $ analysisResult ->getFileSpecificErrors ();
695
+ if (!$ ignoreNewErrors ) {
696
+ return $ analysisResult ;
697
+ }
698
+
699
+ $ baselineIgnoreErrors = $ this ->getCurrentBaselineIgnoreErrors ($ generateBaselineFile , $ inceptionResult );
700
+ $ ignoreErrorsByFile = $ this ->mapIgnoredErrors ($ baselineIgnoreErrors , $ fileHelper );
701
+
702
+ foreach ($ fileSpecificErrors as $ errorIndex => $ error ) {
703
+ $ filePath = $ baselinePathHelper ->getRelativePath ($ error ->getFilePath ());
704
+ if (isset ($ ignoreErrorsByFile [$ filePath ])) {
705
+ foreach ($ ignoreErrorsByFile [$ filePath ] as $ ignoreError ) {
706
+ $ ignore = $ ignoreError ['ignoreError ' ];
707
+ $ shouldIgnore = IgnoredError::shouldIgnore ($ fileHelper , $ error , $ ignore ['message ' ] ?? null , $ ignore ['identifier ' ] ?? null , null );
708
+ if ($ shouldIgnore ) {
709
+ continue 2 ;
710
+ }
711
+ }
712
+ }
713
+
714
+ $ traitFilePath = $ error ->getTraitFilePath ();
715
+ if ($ traitFilePath !== null ) {
716
+ $ normalizedTraitFilePath = $ baselinePathHelper ->getRelativePath ($ traitFilePath );
717
+ if (isset ($ ignoreErrorsByFile [$ normalizedTraitFilePath ])) {
718
+ foreach ($ ignoreErrorsByFile [$ normalizedTraitFilePath ] as $ ignoreError ) {
719
+ $ ignore = $ ignoreError ['ignoreError ' ];
720
+ $ shouldIgnore = IgnoredError::shouldIgnore ($ fileHelper , $ error , $ ignore ['message ' ] ?? null , $ ignore ['identifier ' ] ?? null , null );
721
+ if ($ shouldIgnore ) {
722
+ continue 2 ;
723
+ }
724
+ }
725
+ }
726
+ }
727
+
728
+ // the error was not matched in the baseline, making it a new error, new errors should be ignored here
729
+ unset($ fileSpecificErrors [$ errorIndex ]);
730
+ }
731
+
732
+ $ fileSpecificErrors = array_values ($ fileSpecificErrors );
733
+
734
+ return new AnalysisResult (
735
+ $ fileSpecificErrors ,
736
+ $ analysisResult ->getNotFileSpecificErrors (),
737
+ $ analysisResult ->getInternalErrorObjects (),
738
+ $ analysisResult ->getWarnings (),
739
+ $ analysisResult ->getCollectedData (),
740
+ $ analysisResult ->isDefaultLevelUsed (),
741
+ $ analysisResult ->getProjectConfigFile (),
742
+ $ analysisResult ->isResultCacheSaved (),
743
+ $ analysisResult ->getPeakMemoryUsageBytes (),
744
+ $ analysisResult ->isResultCacheUsed (),
745
+ $ analysisResult ->getChangedProjectExtensionFilesOutsideOfAnalysedPaths (),
746
+ );
747
+ }
748
+
677
749
/**
678
750
* @param string[] $files
679
751
*/
@@ -716,4 +788,109 @@ private function runDiagnoseExtensions(Container $container, Output $errorOutput
716
788
}
717
789
}
718
790
791
+ private function getCurrentBaselineIgnoreErrors (string $ generateBaselineFile , InceptionResult $ inceptionResult ): mixed
792
+ {
793
+ $ loader = new Loader ();
794
+ try {
795
+ $ currentBaselineConfig = $ loader ->load ($ generateBaselineFile );
796
+ $ baselineIgnoreErrors = $ currentBaselineConfig ['parameters ' ]['ignoreErrors ' ] ?? [];
797
+ } catch (FileNotFoundException ) {
798
+ // currently no baseline file -> empty config
799
+ $ baselineIgnoreErrors = [];
800
+ } catch (InvalidStateException $ invalidStateException ) {
801
+ $ inceptionResult ->getErrorOutput ()->writeLineFormatted ($ invalidStateException ->getMessage ());
802
+ throw $ invalidStateException ;
803
+ }
804
+ return $ baselineIgnoreErrors ;
805
+ }
806
+
807
+ /**
808
+ * @param (string|mixed[])[] $baselineIgnoreErrors
809
+ * @return mixed[][]
810
+ * @throws ShouldNotHappenException
811
+ */
812
+ private function mapIgnoredErrors (array $ baselineIgnoreErrors , FileHelper $ fileHelper ): array
813
+ {
814
+ $ ignoreErrorsByFile = [];
815
+
816
+ $ expandedIgnoreErrors = [];
817
+ foreach ($ baselineIgnoreErrors as $ ignoreError ) {
818
+ if (!is_array ($ ignoreError )) {
819
+ throw new ShouldNotHappenException ('Baseline should not have ignore error strings ' );
820
+ }
821
+
822
+ if (!isset ($ ignoreError ['message ' ]) && !isset ($ ignoreError ['messages ' ]) && !isset ($ ignoreError ['identifier ' ])) {
823
+ continue ;
824
+ }
825
+ if (isset ($ ignoreError ['messages ' ])) {
826
+ foreach ($ ignoreError ['messages ' ] as $ message ) {
827
+ $ expandedIgnoreError = $ ignoreError ;
828
+ unset($ expandedIgnoreError ['messages ' ]);
829
+ $ expandedIgnoreError ['message ' ] = $ message ;
830
+ $ expandedIgnoreErrors [] = $ expandedIgnoreError ;
831
+ }
832
+ } else {
833
+ $ expandedIgnoreErrors [] = $ ignoreError ;
834
+ }
835
+ }
836
+ $ uniquedExpandedIgnoreErrors = [];
837
+ foreach ($ expandedIgnoreErrors as $ ignoreError ) {
838
+ if (!isset ($ ignoreError ['message ' ]) && !isset ($ ignoreError ['identifier ' ])) {
839
+ $ uniquedExpandedIgnoreErrors [] = $ ignoreError ;
840
+ continue ;
841
+ }
842
+ if (!isset ($ ignoreError ['path ' ])) {
843
+ $ uniquedExpandedIgnoreErrors [] = $ ignoreError ;
844
+ continue ;
845
+ }
846
+
847
+ $ key = $ ignoreError ['path ' ];
848
+ if (isset ($ ignoreError ['message ' ])) {
849
+ $ key = sprintf ("%s \n%s " , $ key , $ ignoreError ['message ' ]);
850
+ }
851
+ if (isset ($ ignoreError ['identifier ' ])) {
852
+ $ key = sprintf ("%s \n%s " , $ key , $ ignoreError ['identifier ' ]);
853
+ }
854
+ if ($ key === '' ) {
855
+ throw new ShouldNotHappenException ();
856
+ }
857
+
858
+ if (!array_key_exists ($ key , $ uniquedExpandedIgnoreErrors )) {
859
+ $ uniquedExpandedIgnoreErrors [$ key ] = $ ignoreError ;
860
+ continue ;
861
+ }
862
+
863
+ $ uniquedExpandedIgnoreErrors [$ key ] = [
864
+ 'message ' => $ ignoreError ['message ' ] ?? null ,
865
+ 'path ' => $ ignoreError ['path ' ],
866
+ 'identifier ' => $ ignoreError ['identifier ' ] ?? null ,
867
+ 'count ' => ($ uniquedExpandedIgnoreErrors [$ key ]['count ' ] ?? 1 ) + ($ ignoreError ['count ' ] ?? 1 ),
868
+ 'reportUnmatched ' => false ,
869
+ ];
870
+ }
871
+ $ expandedIgnoreErrors = array_values ($ uniquedExpandedIgnoreErrors );
872
+
873
+ foreach ($ expandedIgnoreErrors as $ i => $ ignoreError ) {
874
+ $ ignoreErrorEntry = [
875
+ 'index ' => $ i ,
876
+ 'ignoreError ' => $ ignoreError ,
877
+ ];
878
+
879
+ if (!isset ($ ignoreError ['message ' ]) && !isset ($ ignoreError ['identifier ' ])) {
880
+ continue ;
881
+ }
882
+ if (!isset ($ ignoreError ['path ' ])) {
883
+ throw new ShouldNotHappenException ('Baseline should not have ignore errors without path ' );
884
+ }
885
+
886
+ $ normalizedPath = $ fileHelper ->normalizePath ($ ignoreError ['path ' ]);
887
+ $ ignoreError ['path ' ] = $ normalizedPath ;
888
+ $ ignoreErrorsByFile [$ normalizedPath ][] = $ ignoreErrorEntry ;
889
+ $ ignoreError ['realPath ' ] = $ normalizedPath ;
890
+ $ expandedIgnoreErrors [$ i ] = $ ignoreError ;
891
+ }
892
+
893
+ return $ ignoreErrorsByFile ;
894
+ }
895
+
719
896
}
0 commit comments