Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add FixerBlame class and one test #7865

Draft
wants to merge 53 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
54f56a1
Register hidden `WorkerCommand`
Wirone Jan 12, 2024
9410b9f
Configuration flow for parallel run
Wirone Jan 12, 2024
9319a3e
Extract factory method for linting aware file iterator
Wirone Jan 22, 2024
351f48c
Initial implementation of parallel analysis 🎉
Wirone Jan 25, 2024
a2bbbc2
Use sequential runner by default
Wirone Jan 27, 2024
3c91819
Display information about parallel runner being experimental feature
Wirone Jan 27, 2024
af14b86
Auto detection for parallel config
Wirone Jan 27, 2024
2769ad3
Use parallel runner with config auto-detection for the project itself
Wirone Jan 28, 2024
622e675
Pass analysis errors from worker to the main runner and report them t…
Wirone Jan 28, 2024
cc98df0
Use exact same PHP binary for worker process
Wirone Jan 29, 2024
d20f3aa
Remove quasi-parallel Composer script
Wirone Jan 29, 2024
aa27b5d
Document usage of parallel runner
Wirone Jan 29, 2024
be5a196
Allow only static factory for creating `Process`
Wirone Jan 30, 2024
0e60e18
Determine files count early from simple iterator
Wirone Jan 30, 2024
847a7b1
Revert BC break in `Runner` signature
Wirone Feb 1, 2024
d873e38
Improve type in `Error` constructor
Wirone Feb 1, 2024
396b5de
Fix test for fallback ParallelConfig
Wirone Feb 1, 2024
24c15b9
Fix CS
Wirone Feb 1, 2024
7e7e596
Explicitly require `react/stream`
Wirone Feb 7, 2024
c8b83a9
Introduce `DirectoryInterface::getAbsolutePath()`
Wirone Feb 7, 2024
60c72b1
Support cache in parallel analysis
Wirone Feb 7, 2024
dc2b8c1
Save cache info in parallel analysis only when it makes sense
Wirone Feb 7, 2024
7c8ecbe
Apply Julien's suggestions from code review
Wirone Feb 7, 2024
c889d18
Sync `--using-cache` description
Wirone Feb 7, 2024
a4fb45a
Use `InvalidArgumentException` for parallelisation misconfiguration +…
Wirone Feb 7, 2024
63e4dfc
Fix BC-break in `DirectoryInterface`
Wirone Feb 8, 2024
a566d41
Report file analysis result per file instead of per chunk
Wirone Feb 8, 2024
1f80310
Clean up parallel actions handling
Wirone Feb 8, 2024
f25abdc
Dirty workaround for running sequential runner in tests
Wirone Feb 12, 2024
0cf69be
Add most of the tests
Wirone Feb 13, 2024
7986cb8
Extract process creation to separate factory
Wirone Feb 14, 2024
cefdfae
Fix "$nextResult must not be accessed before initialization"
Wirone Feb 14, 2024
d54e66f
Fix tests on 7.4 with lowest deps
Wirone Feb 14, 2024
ddfb858
Test that `FixCommand` can be run with parallel runner
Wirone Feb 14, 2024
9ecdad4
Allow wider range of React packages
Wirone Feb 14, 2024
0ea17b6
Introduce `--sequential` option to `fix` command
Wirone Feb 15, 2024
f680ea2
More tests, more coverage
Wirone Feb 15, 2024
87172ca
Fix smoke tests
Wirone Feb 15, 2024
295c088
Fix parallelisation for PHAR usage
Wirone Feb 15, 2024
4a1aa7e
Add missing phpDoc for callable supported in `ProcessPool` constructor
Wirone Feb 15, 2024
e63922b
Add missing tests
Wirone Feb 15, 2024
424711c
Fix broken comment
Wirone Feb 16, 2024
88559e7
Add test for error manager in parallel run
Wirone Feb 16, 2024
c37cff3
Backward compatibility for `cs:fix:parallel` script
Wirone Feb 16, 2024
43e39e1
Test that callback is executed on server close
Wirone Feb 16, 2024
dff0884
Test server-worker communication
Wirone Feb 16, 2024
cac24b9
Add error handling
Wirone Mar 1, 2024
4e90bc3
Fix test file/class name
Wirone Mar 1, 2024
3dfe8b3
Add simple test for `PhpCsFixer\Runner\Parallel\Process` class
Wirone Mar 1, 2024
0764ef8
Remove superfluous assertion
Wirone Mar 1, 2024
0403f9b
Run `WorkerCommandTest::testWorkerCommunicatesWithTheServer` only on …
Wirone Mar 1, 2024
b2ebcce
Add various tests related to error handling
Wirone Mar 1, 2024
57bcf57
feat: add FixerBlame tool and one test
connorhu Mar 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

use PhpCsFixer\Config;
use PhpCsFixer\Finder;
use PhpCsFixer\Runner\Parallel\ParallelConfig;

$header = <<<'EOF'
This file is part of PHP CS Fixer.
Expand All @@ -33,6 +34,7 @@
;

return (new Config())
->setParallelConfig(ParallelConfig::detect())
->setRiskyAllowed(true)
->setRules([
'@PHP74Migration' => true,
Expand Down
14 changes: 12 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,15 @@
"ext-filter": "*",
"ext-json": "*",
"ext-tokenizer": "*",
"clue/ndjson-react": "^1.0",
"composer/semver": "^3.4",
"composer/xdebug-handler": "^3.0.3",
"fidry/cpu-core-counter": "^1.0",
"react/child-process": "^0.6.5",
"react/event-loop": "^1.0",
"react/promise": "^2.0 || ^3.0",
"react/socket": "^1.0",
"react/stream": "^1.0",
"sebastian/diff": "^4.0 || ^5.0 || ^6.0",
"symfony/console": "^5.4 || ^6.0 || ^7.0",
"symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0",
Expand Down Expand Up @@ -86,7 +93,10 @@
],
"cs:check": "@php php-cs-fixer check --verbose --diff",
"cs:fix": "@php php-cs-fixer fix",
"cs:fix:parallel": "echo '🔍 Will run in batches of 50 files.'; if [[ -f .php-cs-fixer.php ]]; then FIXER_CONFIG=.php-cs-fixer.php; else FIXER_CONFIG=.php-cs-fixer.dist.php; fi; php php-cs-fixer list-files --config=$FIXER_CONFIG | xargs -n 50 -P 8 php php-cs-fixer fix --config=$FIXER_CONFIG --path-mode intersection 2> /dev/null",
"cs:fix:parallel": [
"echo '⚠️ This script is deprecated! Utilise built-in parallelisation instead.';",
"@cs:fix"
],
"docs": "@php dev-tools/doc.php",
"install-tools": "@composer --working-dir=dev-tools install",
"mess-detector": "@php dev-tools/vendor/bin/phpmd . ansi dev-tools/mess-detector/phpmd.xml --exclude vendor/*,dev-tools/vendor/*,dev-tools/phpstan/*,tests/Fixtures/*",
Expand Down Expand Up @@ -150,7 +160,7 @@
"auto-review": "Execute Auto-review",
"cs:check": "Check coding standards",
"cs:fix": "Fix coding standards",
"cs:fix:parallel": "Fix coding standards in naive parallel mode (using xargs)",
"cs:fix:parallel": "⚠️DEPRECATED! Use cs:fix with proper parallel config",
"docs": "Regenerate docs",
"install-tools": "Install DEV tools",
"mess-detector": "Analyse code with Mess Detector",
Expand Down
22 changes: 19 additions & 3 deletions doc/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,25 @@ If you do not have config file, you can run following command to fix non-hidden,

php php-cs-fixer.phar fix .

With some magic of tools provided by your OS, you can also fix files in parallel:
You can also fix files in parallel, utilising more CPU cores. You can do this by using config class that implements ``PhpCsFixer\Runner\Parallel\ParallelConfig\ParallelRunnerConfigInterface``, and use ``setParallelConfig()`` method. Recommended way is to utilise auto-detecting parallel configuration:

.. code-block:: console
.. code-block:: php

php php-cs-fixer.phar list-files --config=.php-cs-fixer.dist.php | xargs -n 50 -P 8 php php-cs-fixer.phar fix --config=.php-cs-fixer.dist.php --path-mode intersection -v
<?php

return (new PhpCsFixer\Config())
->setParallelConfig(ParallelConfig::detect())
;

However, in some case you may want to fine-tune parallelisation with explicit values (e.g. in environments where auto-detection does not work properly and suggests more cores than it should):

.. code-block:: php

<?php

return (new PhpCsFixer\Config())
->setParallelConfig(new ParallelConfig(4, 20))
;

You can limit process to given file or files in a given directory and its subdirectories:

Expand Down Expand Up @@ -98,6 +112,8 @@ Complete configuration for rules can be supplied using a ``json`` formatted stri

The ``--dry-run`` flag will run the fixer without making changes to your files (implicitly set when you use ``check`` command).

The ``--sequential`` flag will enforce sequential analysis even if parallel config is provided.

The ``--diff`` flag can be used to let the fixer output all the changes it makes in ``udiff`` format.

The ``--allow-risky`` option (pass ``yes`` or ``no``) allows you to set whether risky rules may run. Default value is taken from config file.
Expand Down
19 changes: 18 additions & 1 deletion src/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@
namespace PhpCsFixer;

use PhpCsFixer\Fixer\FixerInterface;
use PhpCsFixer\Runner\Parallel\ParallelConfig;

/**
* @author Fabien Potencier <[email protected]>
* @author Katsuhiro Ogawa <[email protected]>
* @author Dariusz Rumiński <[email protected]>
*/
class Config implements ConfigInterface
class Config implements ConfigInterface, ParallelRunnerConfigInterface
{
private string $cacheFile = '.php-cs-fixer.cache';

Expand All @@ -47,6 +48,8 @@ class Config implements ConfigInterface

private string $name;

private ParallelConfig $parallelRunnerConfig;

/**
* @var null|string
*/
Expand All @@ -63,6 +66,8 @@ class Config implements ConfigInterface

public function __construct(string $name = 'default')
{
$this->parallelRunnerConfig = ParallelConfig::sequential();

// @TODO 4.0 cleanup
if (Utils::isFutureModeEnabled()) {
$this->name = $name.' (future mode)';
Expand Down Expand Up @@ -120,6 +125,11 @@ public function getName(): string
return $this->name;
}

public function getParallelConfig(): ParallelConfig
{
return $this->parallelRunnerConfig;
}

public function getPhpExecutable(): ?string
{
return $this->phpExecutable;
Expand Down Expand Up @@ -191,6 +201,13 @@ public function setLineEnding(string $lineEnding): ConfigInterface
return $this;
}

public function setParallelConfig(ParallelConfig $config): ConfigInterface
{
$this->parallelRunnerConfig = $config;

return $this;
}

public function setPhpExecutable(?string $phpExecutable): ConfigInterface
{
$this->phpExecutable = $phpExecutable;
Expand Down
37 changes: 37 additions & 0 deletions src/Console/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@
use PhpCsFixer\Console\Command\ListFilesCommand;
use PhpCsFixer\Console\Command\ListSetsCommand;
use PhpCsFixer\Console\Command\SelfUpdateCommand;
use PhpCsFixer\Console\Command\WorkerCommand;
use PhpCsFixer\Console\SelfUpdate\GithubClient;
use PhpCsFixer\Console\SelfUpdate\NewVersionChecker;
use PhpCsFixer\PharChecker;
use PhpCsFixer\ToolInfo;
use PhpCsFixer\Utils;
use Symfony\Component\Console\Application as BaseApplication;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\ListCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
Expand All @@ -45,6 +47,7 @@ final class Application extends BaseApplication
public const VERSION_CODENAME = 'Insomnia';

private ToolInfo $toolInfo;
private ?Command $executedCommand = null;

public function __construct()
{
Expand All @@ -63,6 +66,7 @@ public function __construct()
$this->toolInfo,
new PharChecker()
));
$this->add(new WorkerCommand($this->toolInfo));
}

public static function getMajorVersion(): int
Expand Down Expand Up @@ -160,4 +164,37 @@ protected function getDefaultCommands(): array
{
return [new HelpCommand(), new ListCommand()];
}

/**
* @throws \Throwable
*/
protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output): int
{
$this->executedCommand = $command;

return parent::doRunCommand($command, $input, $output);
}

protected function doRenderThrowable(\Throwable $e, OutputInterface $output): void
{
// Since parallel analysis utilises child processes, and they have their own output,
// we need to capture the output of the child process to determine it there was an exception.
// Default render format is not machine-friendly, so we need to override it for `worker` command,
// in order to be able to easily parse exception data for further displaying on main process' side.
if ($this->executedCommand instanceof WorkerCommand) {
$output->writeln(WorkerCommand::ERROR_PREFIX.json_encode(
[
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'code' => $e->getCode(),
'trace' => $e->getTraceAsString(),
]
));

return;
}

parent::doRenderThrowable($e, $output);
}
}
36 changes: 33 additions & 3 deletions src/Console/Command/FixCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ public function getHelp(): string

The <comment>--dry-run</comment> flag will run the fixer without making changes to your files.

The <comment>--sequential</comment> flag will enforce sequential analysis even if parallel config is provided.

The <comment>--diff</comment> flag can be used to let the fixer output all the changes it makes.

The <comment>--allow-risky</comment> option (pass `yes` or `no`) allows you to set whether risky rules may run. Default value is taken from config file.
Expand Down Expand Up @@ -206,12 +208,13 @@ protected function configure(): void
new InputOption('config', '', InputOption::VALUE_REQUIRED, 'The path to a config file.'),
new InputOption('dry-run', '', InputOption::VALUE_NONE, 'Only shows which files would have been modified.'),
new InputOption('rules', '', InputOption::VALUE_REQUIRED, 'List of rules that should be run against configured paths.'),
new InputOption('using-cache', '', InputOption::VALUE_REQUIRED, 'Does cache should be used (can be `yes` or `no`).'),
new InputOption('using-cache', '', InputOption::VALUE_REQUIRED, 'Should cache be used (can be `yes` or `no`).'),
new InputOption('cache-file', '', InputOption::VALUE_REQUIRED, 'The path to the cache file.'),
new InputOption('diff', '', InputOption::VALUE_NONE, 'Prints diff for each file.'),
new InputOption('format', '', InputOption::VALUE_REQUIRED, 'To output results in other formats.'),
new InputOption('stop-on-violation', '', InputOption::VALUE_NONE, 'Stop execution on first violation.'),
new InputOption('show-progress', '', InputOption::VALUE_REQUIRED, 'Type of progress indicator (none, dots).'),
new InputOption('sequential', 's', InputOption::VALUE_NONE, 'Enforce sequential analysis.'),
]
);
}
Expand Down Expand Up @@ -243,6 +246,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
'stop-on-violation' => $input->getOption('stop-on-violation'),
'verbosity' => $verbosity,
'show-progress' => $input->getOption('show-progress'),
'sequential' => $input->getOption('sequential'),
],
getcwd(),
$this->toolInfo
Expand All @@ -257,6 +261,23 @@ protected function execute(InputInterface $input, OutputInterface $output): int
if (null !== $stdErr) {
$stdErr->writeln(Application::getAboutWithRuntime(true));

// @TODO remove when parallel runner is mature enough and works as expected
if ($resolver->getParallelConfig()->getMaxProcesses() > 1) {
$stdErr->writeln(
sprintf(
$stdErr->isDecorated() ? '<bg=yellow;fg=black;>%s</>' : '%s',
'Parallel runner is an experimental feature and may be unstable, use it at your own risk. Feedback highly appreciated!'
)
);

$stdErr->writeln(sprintf(
'Running analysis on %d cores with %d file%s per process.',
$resolver->getParallelConfig()->getMaxProcesses(),
$resolver->getParallelConfig()->getFilesPerProcess(),
$resolver->getParallelConfig()->getFilesPerProcess() > 1 ? 's' : ''
));
}

$configFile = $resolver->getConfigFile();
$stdErr->writeln(sprintf('Loaded config <comment>%s</comment>%s.', $resolver->getConfig()->getName(), null === $configFile ? '' : ' from "'.$configFile.'"'));

Expand Down Expand Up @@ -297,7 +318,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$resolver->isDryRun(),
$resolver->getCacheManager(),
$resolver->getDirectory(),
$resolver->shouldStopOnViolation()
$resolver->shouldStopOnViolation(),
$resolver->getParallelConfig(),
$input,
$resolver->getConfigFile()
);

$this->eventDispatcher->addListener(FixerFileProcessedEvent::NAME, [$progressOutput, 'onFixerFileProcessed']);
Expand All @@ -324,13 +348,18 @@ protected function execute(InputInterface $input, OutputInterface $output): int
? $output->write($reporter->generate($reportSummary))
: $output->write($reporter->generate($reportSummary), false, OutputInterface::OUTPUT_RAW);

$workerErrors = $this->errorsManager->getWorkerErrors();
$invalidErrors = $this->errorsManager->getInvalidErrors();
$exceptionErrors = $this->errorsManager->getExceptionErrors();
$lintErrors = $this->errorsManager->getLintErrors();

if (null !== $stdErr) {
$errorOutput = new ErrorOutput($stdErr);

if (\count($workerErrors) > 0) {
$errorOutput->listWorkerErrors($workerErrors);
}

if (\count($invalidErrors) > 0) {
$errorOutput->listErrors('linting before fixing', $invalidErrors);
}
Expand All @@ -351,7 +380,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int
\count($changed) > 0,
\count($invalidErrors) > 0,
\count($exceptionErrors) > 0,
\count($lintErrors) > 0
\count($lintErrors) > 0,
\count($workerErrors) > 0
);
}

Expand Down
12 changes: 9 additions & 3 deletions src/Console/Command/FixCommandExitStatusCalculator.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,14 @@ final class FixCommandExitStatusCalculator
public const EXIT_STATUS_FLAG_HAS_INVALID_FIXER_CONFIG = 32;
public const EXIT_STATUS_FLAG_EXCEPTION_IN_APP = 64;

public function calculate(bool $isDryRun, bool $hasChangedFiles, bool $hasInvalidErrors, bool $hasExceptionErrors, bool $hasLintErrorsAfterFixing): int
{
public function calculate(
bool $isDryRun,
bool $hasChangedFiles,
bool $hasInvalidErrors,
bool $hasExceptionErrors,
bool $hasLintErrorsAfterFixing,
bool $hasWorkerErrors
): int {
$exitStatus = 0;

if ($isDryRun) {
Expand All @@ -42,7 +48,7 @@ public function calculate(bool $isDryRun, bool $hasChangedFiles, bool $hasInvali
}
}

if ($hasExceptionErrors || $hasLintErrorsAfterFixing) {
if ($hasExceptionErrors || $hasLintErrorsAfterFixing || $hasWorkerErrors) {
$exitStatus |= self::EXIT_STATUS_FLAG_EXCEPTION_IN_APP;
}

Expand Down
Loading
Loading