1 | <?php |
||
2 | |||
3 | namespace Noitran\CsFixer\Console; |
||
4 | |||
5 | use Illuminate\Console\Command; |
||
6 | |||
7 | use PhpCsFixer\Config; |
||
8 | use PhpCsFixer\Console\Command\FixCommandExitStatusCalculator; |
||
9 | use PhpCsFixer\Console\ConfigurationResolver; |
||
10 | use PhpCsFixer\Console\Output\ErrorOutput; |
||
11 | use PhpCsFixer\Console\Output\NullOutput; |
||
12 | use PhpCsFixer\Console\Output\ProcessOutput; |
||
13 | use PhpCsFixer\Error\ErrorsManager; |
||
14 | use PhpCsFixer\Finder; |
||
15 | use PhpCsFixer\Report\ReportSummary; |
||
16 | use PhpCsFixer\Runner\Runner; |
||
17 | use PhpCsFixer\ToolInfo; |
||
18 | use Symfony\Component\Console\Output\OutputInterface; |
||
19 | use Symfony\Component\Console\Terminal; |
||
20 | use Symfony\Component\EventDispatcher\EventDispatcher; |
||
21 | use Symfony\Component\Stopwatch\Stopwatch; |
||
22 | use RuntimeException; |
||
23 | use ArrayIterator; |
||
24 | |||
25 | /** |
||
26 | * Class PhpCsCommand |
||
27 | */ |
||
28 | class PhpCsCommand extends Command |
||
29 | { |
||
30 | /** |
||
31 | * The default verbosity of output commands. |
||
32 | * |
||
33 | * @var int |
||
34 | */ |
||
35 | protected $verbosity = OutputInterface::VERBOSITY_VERBOSE; |
||
36 | |||
37 | /** |
||
38 | * The console command name. |
||
39 | * |
||
40 | * @var string |
||
41 | */ |
||
42 | protected $name = 'phpcs:fix'; |
||
43 | |||
44 | /** |
||
45 | * The name and signature of the console command. |
||
46 | * |
||
47 | * @var string |
||
48 | */ |
||
49 | protected $signature = 'phpcs:fix |
||
50 | {--path=* : The path.} |
||
51 | {--path-mode=override : Specify path mode (can be override or intersection).} |
||
52 | {--allow-risky= : Are risky fixers allowed (can be yes or no).} |
||
53 | {--config= : The path to a .php_cs file.} |
||
54 | {--dry-run : Only shows which files would have been modified.} |
||
55 | {--rules= : The Rules} |
||
56 | {--using-cache=yes : Does cache should be used (can be yes or no).} |
||
57 | {--cache-file= : The path to the cache file.} |
||
58 | {--diff : Also produce diff for each file.} |
||
59 | {--diff-format= : Specify diff format.} |
||
60 | {--format= : To output results in other formats.} |
||
61 | {--stop-on-violation : Stop execution on first violation.} |
||
62 | {--show-progress= : Type of progress indicator (none, run-in, estimating or estimating-max).}'; |
||
63 | |||
64 | /** |
||
65 | * The console command description. |
||
66 | * |
||
67 | * @var string |
||
68 | */ |
||
69 | protected $description = 'Runs PHPCS-Fixer tool to fix code to follow coding standards.'; |
||
70 | |||
71 | /** |
||
72 | * @var ErrorsManager |
||
73 | */ |
||
74 | private $errorsManager; |
||
75 | |||
76 | /** |
||
77 | * @var EventDispatcher |
||
78 | */ |
||
79 | private $eventDispatcher; |
||
80 | |||
81 | /** |
||
82 | * @var Stopwatch |
||
83 | */ |
||
84 | private $stopwatch; |
||
85 | |||
86 | /** |
||
87 | * @var ToolInfo |
||
88 | */ |
||
89 | private $toolInfo; |
||
90 | |||
91 | /** |
||
92 | * PhpCsCommand constructor. |
||
93 | */ |
||
94 | 1 | public function __construct() |
|
95 | { |
||
96 | 1 | parent::__construct(); |
|
97 | |||
98 | 1 | $this->errorsManager = new ErrorsManager(); |
|
99 | 1 | $this->eventDispatcher = new EventDispatcher(); |
|
100 | 1 | $this->stopwatch = new Stopwatch(); |
|
101 | 1 | $this->toolInfo = new ToolInfo(); |
|
102 | 1 | } |
|
103 | |||
104 | /** |
||
105 | * Handles the Command |
||
106 | */ |
||
107 | 1 | public function handle(): int |
|
108 | { |
||
109 | 1 | $this->validateOptions(); |
|
110 | 1 | $resolver = $this->getResolver(); |
|
111 | 1 | [$finder, $progressOutput] = $this->manageProgress($resolver); |
|
112 | 1 | $runner = $this->getRunner($finder, $resolver); |
|
113 | |||
114 | 1 | $this->stopwatch->start('fixFiles'); |
|
115 | 1 | $changed = $runner->fix(); |
|
116 | 1 | $this->stopwatch->stop('fixFiles'); |
|
117 | 1 | $progressOutput->printLegend(); |
|
118 | 1 | $fixEvent = $this->stopwatch->getEvent('fixFiles'); |
|
119 | |||
120 | 1 | $reportSummary = $this->createReport($changed, $fixEvent, $resolver); |
|
121 | |||
122 | 1 | $this->getOutput()->isDecorated() ? |
|
123 | $this->getOutput()->write($resolver->getReporter()->generate($reportSummary)) : |
||
124 | 1 | $this->getOutput()->write( |
|
125 | 1 | $resolver->getReporter()->generate($reportSummary), |
|
126 | 1 | false, |
|
127 | 1 | OutputInterface::OUTPUT_RAW |
|
128 | ); |
||
129 | |||
130 | 1 | $invalidErrors = $this->errorsManager->getInvalidErrors(); |
|
131 | 1 | $exceptionErrors = $this->errorsManager->getExceptionErrors(); |
|
132 | 1 | $lintErrors = $this->errorsManager->getLintErrors(); |
|
133 | |||
134 | 1 | $errorOutput = new ErrorOutput($this->getOutput()); |
|
135 | |||
136 | 1 | if (\count($invalidErrors) > 0) { |
|
137 | $errorOutput->listErrors('linting before fixing', $invalidErrors); |
||
138 | } |
||
139 | |||
140 | 1 | if (\count($exceptionErrors) > 0) { |
|
141 | $errorOutput->listErrors('fixing', $exceptionErrors); |
||
142 | } |
||
143 | |||
144 | 1 | if (\count($lintErrors) > 0) { |
|
145 | $errorOutput->listErrors('linting after fixing', $lintErrors); |
||
146 | } |
||
147 | |||
148 | 1 | $exitStatusCalculator = new FixCommandExitStatusCalculator(); |
|
149 | |||
150 | 1 | return $exitStatusCalculator->calculate( |
|
151 | 1 | $resolver->isDryRun(), |
|
152 | 1 | \count($changed) > 0, |
|
153 | 1 | \count($invalidErrors) > 0, |
|
154 | 1 | \count($exceptionErrors) > 0 |
|
155 | ); |
||
156 | } |
||
157 | |||
158 | /** |
||
159 | * @param $changed |
||
160 | * @param $fixEvent |
||
161 | * @param ConfigurationResolver $resolver |
||
162 | * |
||
163 | * @return ReportSummary |
||
164 | */ |
||
165 | 1 | protected function createReport($changed, $fixEvent, ConfigurationResolver $resolver): ReportSummary |
|
166 | { |
||
167 | 1 | return new ReportSummary( |
|
168 | 1 | $changed, |
|
169 | 1 | $fixEvent->getDuration(), |
|
170 | 1 | $fixEvent->getMemory(), |
|
171 | 1 | OutputInterface::VERBOSITY_VERBOSE <= $this->verbosity, |
|
172 | 1 | $resolver->isDryRun(), |
|
173 | 1 | $this->getOutput()->isDecorated() |
|
174 | ); |
||
175 | } |
||
176 | |||
177 | /** |
||
178 | * @param $finder |
||
179 | * @param ConfigurationResolver $resolver |
||
180 | * |
||
181 | * @return Runner |
||
182 | */ |
||
183 | 1 | protected function getRunner($finder, ConfigurationResolver $resolver): Runner |
|
184 | { |
||
185 | 1 | return new Runner( |
|
186 | 1 | $finder, |
|
187 | 1 | $resolver->getFixers(), |
|
188 | 1 | $resolver->getDiffer(), |
|
189 | 1 | 'none' !== $resolver->getProgress() ? $this->eventDispatcher : null, |
|
190 | 1 | $this->errorsManager, |
|
191 | 1 | $resolver->getLinter(), |
|
192 | 1 | $resolver->isDryRun(), |
|
193 | 1 | $resolver->getCacheManager(), |
|
194 | 1 | $resolver->getDirectory(), |
|
195 | 1 | $resolver->shouldStopOnViolation() |
|
196 | ); |
||
197 | } |
||
198 | |||
199 | /** |
||
200 | * @return Config |
||
201 | */ |
||
202 | 1 | protected function getConfig(): Config |
|
203 | { |
||
204 | 1 | $config = new Config('artisan'); |
|
205 | |||
206 | 1 | $config->setRules(config('phpcs.rules')) |
|
207 | 1 | ->setFinder($this->getFinder()) |
|
208 | 1 | ->setCacheFile(storage_path('framework/cache/phpcs.json')); |
|
209 | |||
210 | 1 | return $config; |
|
211 | } |
||
212 | |||
213 | /** |
||
214 | * @return Finder |
||
215 | */ |
||
216 | 1 | protected function getFinder(): Finder |
|
217 | { |
||
218 | 1 | return Finder::create() |
|
219 | 1 | ->in(base_path()) |
|
220 | 1 | ->exclude(config('phpcs.excludes')) |
|
221 | 1 | ->notPath('_ide_helper_models.php') |
|
222 | 1 | ->notPath('_ide_helper.php') |
|
223 | 1 | ->notPath('.phpstorm.meta.php') |
|
224 | 1 | ->notName('*.blade.php') |
|
225 | 1 | ->ignoreDotFiles(true) |
|
226 | 1 | ->ignoreVCS(true); |
|
227 | } |
||
228 | |||
229 | /** |
||
230 | * @return ConfigurationResolver |
||
231 | */ |
||
232 | 1 | protected function getResolver(): ConfigurationResolver |
|
233 | { |
||
234 | 1 | $resolver = new ConfigurationResolver( |
|
235 | 1 | $this->getConfig(), |
|
236 | [ |
||
237 | 1 | 'allow-risky' => $this->option('allow-risky'), |
|
238 | 1 | 'config' => $this->option('config'), |
|
239 | 1 | 'dry-run' => $this->option('dry-run'), |
|
240 | 1 | 'rules' => $this->option('rules'), |
|
241 | 1 | 'path' => $this->option('path'), |
|
242 | 1 | 'path-mode' => $this->option('path-mode'), |
|
243 | 1 | 'using-cache' => $this->option('using-cache'), |
|
244 | 1 | 'cache-file' => $this->option('cache-file'), |
|
245 | 1 | 'format' => $this->option('format'), |
|
246 | 1 | 'diff' => $this->option('diff'), |
|
247 | 1 | 'diff-format' => $this->option('diff-format'), |
|
248 | 1 | 'stop-on-violation' => $this->option('stop-on-violation'), |
|
249 | 1 | 'verbosity' => $this->verbosity, |
|
250 | 1 | 'show-progress' => $this->option('show-progress'), |
|
251 | ], |
||
252 | 1 | getcwd(), |
|
253 | 1 | $this->toolInfo |
|
254 | ); |
||
255 | |||
256 | 1 | $this->info(sprintf( |
|
257 | 1 | 'Loaded config <comment>%s</comment>%s.', |
|
258 | 1 | $resolver->getConfig()->getName(), |
|
259 | 1 | null === $resolver->getConfigFile() ? '' : ' from "' . $resolver->getConfigFile() . '"' |
|
260 | )); |
||
261 | |||
262 | 1 | if ($resolver->getUsingCache()) { |
|
263 | 1 | $cacheFile = $resolver->getCacheFile(); |
|
264 | 1 | if (is_file($cacheFile)) { |
|
265 | 1 | $this->info(sprintf('Using cache file <comment>%s</comment>.', $cacheFile)); |
|
266 | } |
||
267 | } |
||
268 | |||
269 | 1 | if ($resolver->configFinderIsOverridden()) { |
|
270 | $this->info( |
||
271 | 'Paths from configuration file have been overridden by paths provided as command arguments.' |
||
272 | ); |
||
273 | } |
||
274 | |||
275 | 1 | return $resolver; |
|
276 | } |
||
277 | |||
278 | /** |
||
279 | * |
||
280 | */ |
||
281 | 1 | protected function validateOptions(): void |
|
282 | { |
||
283 | 1 | if (null !== $this->option('config') && null !== $this->option('rules')) { |
|
0 ignored issues
–
show
introduced
by
![]() |
|||
284 | if (getenv('PHP_CS_FIXER_FUTURE_MODE')) { |
||
285 | throw new RuntimeException( |
||
286 | 'Passing both `config` and `rules` options is not possible. |
||
287 | This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set.' |
||
288 | ); |
||
289 | } |
||
290 | |||
291 | $this->warn('When passing both "--config" and "--rules" |
||
292 | the rules within the configuration file are not used.'); |
||
293 | $this->warn('Passing both options is deprecated; version |
||
294 | v3.0 PHP-CS-Fixer will exit with a configuration error code.'); |
||
295 | } |
||
296 | 1 | } |
|
297 | |||
298 | /** |
||
299 | * @param ConfigurationResolver $resolver |
||
300 | * |
||
301 | * @return array |
||
302 | */ |
||
303 | 1 | protected function manageProgress(ConfigurationResolver $resolver): array |
|
304 | { |
||
305 | 1 | $finder = $resolver->getFinder(); |
|
306 | 1 | if ('none' === $resolver->getProgress()) { |
|
307 | $progressOutput = new NullOutput(); |
||
308 | 1 | } elseif ('run-in' === $resolver->getProgress()) { |
|
309 | 1 | $progressOutput = new ProcessOutput($this->getOutput(), $this->eventDispatcher, null, null); |
|
310 | } else { |
||
311 | $finder = new ArrayIterator(iterator_to_array($finder)); |
||
312 | $progressOutput = new ProcessOutput( |
||
313 | $this->getOutput(), |
||
314 | $this->eventDispatcher, |
||
315 | 'estimating-max' === $resolver->getProgress() ? (new Terminal())->getWidth() : null, |
||
316 | \count($finder) |
||
317 | ); |
||
318 | } |
||
319 | |||
320 | 1 | return [$finder, $progressOutput]; |
|
321 | } |
||
322 | } |
||
323 |