Versions: reproduced with symplify/easy-coding-standard 13.2.19 (bundling entropy/entropy 0.4.9) on PHP 8.4. By reading the source, not by running it: ECS 13.3.2 bundles the same parser and the same ecs_normalize_argv(), and entropy main after 0.4.14 still parses and maps options this way.
What happens
vendor/bin/ecs check --config ecs.php --clear-cache src/Foo.php does not check src/Foo.php. It checks the paths the config sets — typically the whole project — and --clear-cache is off: the mapper casts the swallowed path to bool with FILTER_VALIDATE_BOOLEAN, which gives false.
With --fix earlier on the line — ecs check --fix --config ecs.php --clear-cache src/Foo.php — the whole project is rewritten when one file was meant.
Reproduction — ecs.php:
<?php
use PhpCsFixer\Fixer\Basic\BracesPositionFixer;
use Symplify\EasyCodingStandard\Config\ECSConfig;
return ECSConfig::configure()
->withPaths( [ __DIR__ . '/src' ] )
->withRules( [ BracesPositionFixer::class ] );
src/A.php and src/B.php, both <?php class A {\n} style, both fixable; then:
| Command line |
Files checked |
ecs check src/A.php --config ecs.php --clear-cache |
src/A.php |
ecs check --config ecs.php src/A.php |
src/A.php |
ecs check --config ecs.php --clear-cache src/A.php |
src/A.php, src/B.php |
ecs check --config ecs.php --no-progress-bar src/A.php |
src/A.php, src/B.php |
ecs check --config ecs.php --fix src/A.php |
src/A.php, src/B.php, reported and not fixed: --fix reads as off |
ecs check --fix --config ecs.php --clear-cache src/A.php |
src/A.php, src/B.php, both fixed |
ecs check --config ecs.php -- src/A.php |
nothing |
Cause
Entropy\Console\Input\InputParser::parseLongOption() knows no option schema:
// --option value
if ($argv !== [] && strncmp((string) $argv[0], '-', strlen('-')) !== 0) {
return [$item, array_shift($argv)];
}
So any --name without = takes the next token that does not start with - as its value — --clear-cache, --fix, --no-progress-bar, --no-error-table, --no-diffs, --debug, and -- itself. The path is gone from the positional arguments, resolvePaths() in ConfigurationFactory finds none, and falls back to Option::PATHS from the config. CLIRequestMapper::castValueByParameterType() then casts the swallowed value to the flag's bool parameter with filter_var(…, FILTER_VALIDATE_BOOLEAN), so the flag itself reads as off.
Expected
A flag that takes no value never consumes the next token, as a Symfony Console InputOption::VALUE_NONE does not: the parser needs to know which options take a value — --config, --output-format, --memory-limit, and the worker's --port and --identifier — or check needs to declare them. -- should end option parsing rather than name an option.
Workaround
Put paths before options: ecs check src/A.php --fix --config ecs.php.
Versions: reproduced with symplify/easy-coding-standard 13.2.19 (bundling entropy/entropy 0.4.9) on PHP 8.4. By reading the source, not by running it: ECS 13.3.2 bundles the same parser and the same
ecs_normalize_argv(), and entropymainafter 0.4.14 still parses and maps options this way.What happens
vendor/bin/ecs check --config ecs.php --clear-cache src/Foo.phpdoes not checksrc/Foo.php. It checks the paths the config sets — typically the whole project — and--clear-cacheis off: the mapper casts the swallowed path toboolwithFILTER_VALIDATE_BOOLEAN, which givesfalse.With
--fixearlier on the line —ecs check --fix --config ecs.php --clear-cache src/Foo.php— the whole project is rewritten when one file was meant.Reproduction —
ecs.php:src/A.phpandsrc/B.php, both<?php class A {\n}style, both fixable; then:ecs check src/A.php --config ecs.php --clear-cachesrc/A.phpecs check --config ecs.php src/A.phpsrc/A.phpecs check --config ecs.php --clear-cache src/A.phpsrc/A.php,src/B.phpecs check --config ecs.php --no-progress-bar src/A.phpsrc/A.php,src/B.phpecs check --config ecs.php --fix src/A.phpsrc/A.php,src/B.php, reported and not fixed:--fixreads as offecs check --fix --config ecs.php --clear-cache src/A.phpsrc/A.php,src/B.php, both fixedecs check --config ecs.php -- src/A.phpCause
Entropy\Console\Input\InputParser::parseLongOption()knows no option schema:So any
--namewithout=takes the next token that does not start with-as its value —--clear-cache,--fix,--no-progress-bar,--no-error-table,--no-diffs,--debug, and--itself. The path is gone from the positional arguments,resolvePaths()inConfigurationFactoryfinds none, and falls back toOption::PATHSfrom the config.CLIRequestMapper::castValueByParameterType()then casts the swallowed value to the flag'sboolparameter withfilter_var(…, FILTER_VALIDATE_BOOLEAN), so the flag itself reads as off.Expected
A flag that takes no value never consumes the next token, as a Symfony Console
InputOption::VALUE_NONEdoes not: the parser needs to know which options take a value —--config,--output-format,--memory-limit, and the worker's--portand--identifier— orcheckneeds to declare them.--should end option parsing rather than name an option.Workaround
Put paths before options:
ecs check src/A.php --fix --config ecs.php.