2
2
3
3
namespace PHPStan \Command ;
4
4
5
+ use PHPStan \Command \ErrorFormatter \BaselineNeonErrorFormatter ;
5
6
use PHPStan \Command \ErrorFormatter \ErrorFormatter ;
7
+ use PHPStan \Command \Symfony \SymfonyOutput ;
8
+ use PHPStan \Command \Symfony \SymfonyStyle ;
9
+ use PHPStan \File \ParentDirectoryRelativePathHelper ;
6
10
use Symfony \Component \Console \Input \InputArgument ;
7
11
use Symfony \Component \Console \Input \InputInterface ;
8
12
use Symfony \Component \Console \Input \InputOption ;
13
+ use Symfony \Component \Console \Input \StringInput ;
9
14
use Symfony \Component \Console \Output \OutputInterface ;
15
+ use Symfony \Component \Console \Output \StreamOutput ;
16
+ use function file_put_contents ;
17
+ use function stream_get_contents ;
10
18
11
19
class AnalyseCommand extends \Symfony \Component \Console \Command \Command
12
20
{
@@ -44,6 +52,7 @@ protected function configure(): void
44
52
new InputOption ('debug ' , null , InputOption::VALUE_NONE , 'Show debug information - which file is analysed, do not catch internal errors ' ),
45
53
new InputOption ('autoload-file ' , 'a ' , InputOption::VALUE_REQUIRED , 'Project \'s additional autoload file path ' ),
46
54
new InputOption ('error-format ' , null , InputOption::VALUE_REQUIRED , 'Format in which to print the result of the analysis ' , 'table ' ),
55
+ new InputOption ('generate-baseline ' , null , InputOption::VALUE_OPTIONAL , 'Path to a file where the baseline should be saved ' , false ),
47
56
new InputOption ('memory-limit ' , null , InputOption::VALUE_REQUIRED , 'Memory limit for analysis ' ),
48
57
new InputOption ('xdebug ' , null , InputOption::VALUE_NONE , 'Allow running with XDebug for debugging purposes ' ),
49
58
]);
@@ -79,6 +88,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int
79
88
$ pathsFile = $ input ->getOption ('paths-file ' );
80
89
$ allowXdebug = $ input ->getOption ('xdebug ' );
81
90
91
+ /** @var string|false|null $generateBaselineFile */
92
+ $ generateBaselineFile = $ input ->getOption ('generate-baseline ' );
93
+ if ($ generateBaselineFile === false ) {
94
+ $ generateBaselineFile = null ;
95
+ } elseif ($ generateBaselineFile === null ) {
96
+ $ generateBaselineFile = 'phpstan-baseline.neon ' ;
97
+ }
98
+
82
99
if (
83
100
!is_array ($ paths )
84
101
|| (!is_string ($ memoryLimit ) && $ memoryLimit !== null )
@@ -101,6 +118,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
101
118
$ autoloadFile ,
102
119
$ this ->composerAutoloaderProjectPaths ,
103
120
$ configuration ,
121
+ $ generateBaselineFile ,
104
122
$ level ,
105
123
$ allowXdebug ,
106
124
true
@@ -129,6 +147,19 @@ protected function execute(InputInterface $input, OutputInterface $output): int
129
147
return 1 ;
130
148
}
131
149
150
+ if ($ errorFormat === 'baselineNeon ' ) {
151
+ $ errorOutput = $ inceptionResult ->getErrorOutput ();
152
+ $ errorOutput ->writeLineFormatted ('⚠️ You \'re using an obsolete option <fg=cyan>--error-format baselineNeon</>. ⚠️️ ' );
153
+ $ errorOutput ->writeLineFormatted ('' );
154
+ $ errorOutput ->writeLineFormatted (' There \'s a new and much better option <fg=cyan>--generate-baseline</>. Here are the advantages: ' );
155
+ $ errorOutput ->writeLineFormatted (' 1) The current baseline file does not have to be commented-out ' );
156
+ $ errorOutput ->writeLineFormatted (' nor emptied when generating the new baseline. It \'s excluded automatically. ' );
157
+ $ errorOutput ->writeLineFormatted (' 2) Output no longer has to be redirected to a file, PHPStan saves the baseline ' );
158
+ $ errorOutput ->writeLineFormatted (' to a specified path (defaults to <fg=cyan>phpstan-baseline.neon</>). ' );
159
+ $ errorOutput ->writeLineFormatted (' 3) Baseline contains correct relative paths if saved to a subdirectory. ' );
160
+ $ errorOutput ->writeLineFormatted ('' );
161
+ }
162
+
132
163
/** @var ErrorFormatter $errorFormatter */
133
164
$ errorFormatter = $ container ->getService ($ errorFormatterServiceName );
134
165
@@ -140,6 +171,21 @@ protected function execute(InputInterface $input, OutputInterface $output): int
140
171
throw new \PHPStan \ShouldNotHappenException ();
141
172
}
142
173
174
+ $ generateBaselineFile = $ inceptionResult ->getGenerateBaselineFile ();
175
+ if ($ generateBaselineFile !== null ) {
176
+ $ baselineExtension = pathinfo ($ generateBaselineFile , PATHINFO_EXTENSION );
177
+ if ($ baselineExtension === '' ) {
178
+ $ inceptionResult ->getStdOutput ()->getStyle ()->error (sprintf ('Baseline filename must have an extension, %s provided instead. ' , pathinfo ($ generateBaselineFile , PATHINFO_BASENAME )));
179
+ return $ inceptionResult ->handleReturn (1 );
180
+ }
181
+
182
+ if ($ baselineExtension !== 'neon ' ) {
183
+ $ inceptionResult ->getStdOutput ()->getStyle ()->error (sprintf ('Baseline filename extension must be .neon, .%s was used instead. ' , $ baselineExtension ));
184
+
185
+ return $ inceptionResult ->handleReturn (1 );
186
+ }
187
+ }
188
+
143
189
$ analysisResult = $ application ->analyse (
144
190
$ inceptionResult ->getFiles (),
145
191
$ inceptionResult ->isOnlyFiles (),
@@ -151,9 +197,81 @@ protected function execute(InputInterface $input, OutputInterface $output): int
151
197
$ input
152
198
);
153
199
200
+ if ($ generateBaselineFile !== null ) {
201
+ if (!$ analysisResult ->hasErrors ()) {
202
+ $ inceptionResult ->getStdOutput ()->getStyle ()->error ('No errors were found during the analysis. Baseline could not be generated. ' );
203
+
204
+ return $ inceptionResult ->handleReturn (1 );
205
+ }
206
+
207
+ $ baselineFileDirectory = dirname ($ generateBaselineFile );
208
+ $ baselineErrorFormatter = new BaselineNeonErrorFormatter (new ParentDirectoryRelativePathHelper ($ baselineFileDirectory ));
209
+
210
+ $ streamOutput = $ this ->createStreamOutput ();
211
+ $ errorConsoleStyle = new ErrorsConsoleStyle (new StringInput ('' ), $ streamOutput );
212
+ $ output = new SymfonyOutput ($ streamOutput , new SymfonyStyle ($ errorConsoleStyle ));
213
+ $ baselineErrorFormatter ->formatErrors ($ analysisResult , $ output );
214
+
215
+ $ stream = $ streamOutput ->getStream ();
216
+ rewind ($ stream );
217
+ $ baselineContents = stream_get_contents ($ stream );
218
+ if ($ baselineContents === false ) {
219
+ throw new \PHPStan \ShouldNotHappenException ();
220
+ }
221
+
222
+ if (!is_dir ($ baselineFileDirectory )) {
223
+ $ mkdirResult = @mkdir ($ baselineFileDirectory , 0644 , true );
224
+ if ($ mkdirResult === false ) {
225
+ $ inceptionResult ->getStdOutput ()->writeLineFormatted (sprintf ('Failed to create directory "%s". ' , $ baselineFileDirectory ));
226
+
227
+ return $ inceptionResult ->handleReturn (1 );
228
+ }
229
+ }
230
+
231
+ $ writeResult = @file_put_contents ($ generateBaselineFile , $ baselineContents );
232
+ if ($ writeResult === false ) {
233
+ $ inceptionResult ->getStdOutput ()->writeLineFormatted (sprintf ('Failed to write the baseline to file "%s". ' , $ generateBaselineFile ));
234
+
235
+ return $ inceptionResult ->handleReturn (1 );
236
+ }
237
+
238
+ $ errorsCount = 0 ;
239
+ $ unignorableCount = 0 ;
240
+ foreach ($ analysisResult ->getFileSpecificErrors () as $ fileSpecificError ) {
241
+ if (!$ fileSpecificError ->canBeIgnored ()) {
242
+ $ unignorableCount ++;
243
+ continue ;
244
+ }
245
+
246
+ $ errorsCount ++;
247
+ }
248
+
249
+ $ message = sprintf ('Baseline generated with %d %s. ' , $ errorsCount , $ errorsCount === 1 ? 'error ' : 'errors ' );
250
+
251
+ if (
252
+ $ unignorableCount === 0
253
+ && count ($ analysisResult ->getNotFileSpecificErrors ()) === 0
254
+ ) {
255
+ $ inceptionResult ->getStdOutput ()->getStyle ()->success ($ message );
256
+ } else {
257
+ $ inceptionResult ->getStdOutput ()->getStyle ()->warning ($ message . "\nSome errors could not be put into baseline. Re-run PHPStan and fix them. " );
258
+ }
259
+
260
+ return $ inceptionResult ->handleReturn (0 );
261
+ }
262
+
154
263
return $ inceptionResult ->handleReturn (
155
264
$ errorFormatter ->formatErrors ($ analysisResult , $ inceptionResult ->getStdOutput ())
156
265
);
157
266
}
158
267
268
+ private function createStreamOutput (): StreamOutput
269
+ {
270
+ $ resource = fopen ('php://memory ' , 'w ' , false );
271
+ if ($ resource === false ) {
272
+ throw new \PHPStan \ShouldNotHappenException ();
273
+ }
274
+ return new StreamOutput ($ resource );
275
+ }
276
+
159
277
}
0 commit comments