123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665 |
- <?php
- /*
- * This file is part of PHP CS Fixer.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- * Dariusz Rumiński <dariusz.ruminski@gmail.com>
- *
- * This source file is subject to the MIT license that is bundled
- * with this source code in the file LICENSE.
- */
- namespace PhpCsFixer\Console\Command;
- use PhpCsFixer\AbstractFixer;
- use PhpCsFixer\Console\Application;
- use PhpCsFixer\Fixer\ConfigurableFixerInterface;
- use PhpCsFixer\Fixer\DeprecatedFixerInterface;
- use PhpCsFixer\Fixer\FixerInterface;
- use PhpCsFixer\FixerConfiguration\AliasedFixerOption;
- use PhpCsFixer\FixerConfiguration\AllowedValueSubset;
- use PhpCsFixer\FixerConfiguration\DeprecatedFixerOption;
- use PhpCsFixer\FixerConfiguration\FixerOptionInterface;
- use PhpCsFixer\FixerFactory;
- use PhpCsFixer\Preg;
- use PhpCsFixer\RuleSet\RuleSet;
- use PhpCsFixer\RuleSet\RuleSets;
- use PhpCsFixer\Utils;
- use Symfony\Component\Console\Command\HelpCommand as BaseHelpCommand;
- use Symfony\Component\Console\Formatter\OutputFormatter;
- use Symfony\Component\Console\Formatter\OutputFormatterStyle;
- use Symfony\Component\Console\Input\InputInterface;
- use Symfony\Component\Console\Output\OutputInterface;
- /**
- * @author Fabien Potencier <fabien@symfony.com>
- * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
- * @author SpacePossum
- *
- * @internal
- */
- final class HelpCommand extends BaseHelpCommand
- {
- protected static $defaultName = 'help';
- /**
- * Returns help-copy suitable for console output.
- *
- * @return string
- */
- public static function getHelpCopy()
- {
- $template =
- <<<'EOF'
- The <info>%command.name%</info> command tries to fix as much coding standards
- problems as possible on a given file or files in a given directory and its subdirectories:
- <info>$ php %command.full_name% /path/to/dir</info>
- <info>$ php %command.full_name% /path/to/file</info>
- By default <comment>--path-mode</comment> is set to `override`, which means, that if you specify the path to a file or a directory via
- command arguments, then the paths provided to a `Finder` in config file will be ignored. You can use <comment>--path-mode=intersection</comment>
- to merge paths from the config file and from the argument:
- <info>$ php %command.full_name% --path-mode=intersection /path/to/dir</info>
- The <comment>--format</comment> option for the output format. Supported formats are `txt` (default one), `json`, `xml`, `checkstyle`, `junit` and `gitlab`.
- NOTE: the output for the following formats are generated in accordance with XML schemas
- * `checkstyle` follows the common `"checkstyle" xml schema </doc/report-schema/checkstyle.xsd>`_
- * `junit` follows the `JUnit xml schema from Jenkins </doc/report-schema/junit-10.xsd>`_
- The <comment>--quiet</comment> Do not output any message.
- The <comment>--verbose</comment> option will show the applied rules. When using the `txt` format it will also display progress notifications.
- NOTE: if there is an error like "errors reported during linting after fixing", you can use this to be even more verbose for debugging purpose
- * `-v`: verbose
- * `-vv`: very verbose
- * `-vvv`: debug
- The <comment>--rules</comment> option limits the rules to apply to the
- project:
- <info>$ php %command.full_name% /path/to/project --rules=@PSR2</info>
- By default the PSR1 and PSR2 rules are used.
- The <comment>--rules</comment> option lets you choose the exact rules to
- apply (the rule names must be separated by a comma):
- <info>$ php %command.full_name% /path/to/dir --rules=line_ending,full_opening_tag,indentation_type</info>
- You can also exclude the rules you don't want by placing a dash in front of the rule name, if this is more convenient,
- using <comment>-name_of_fixer</comment>:
- <info>$ php %command.full_name% /path/to/dir --rules=-full_opening_tag,-indentation_type</info>
- When using combinations of exact and exclude rules, applying exact rules along with above excluded results:
- <info>$ php %command.full_name% /path/to/project --rules=@Symfony,-@PSR1,-blank_line_before_statement,strict_comparison</info>
- Complete configuration for rules can be supplied using a `json` formatted string.
- <info>$ php %command.full_name% /path/to/project --rules='{"concat_space": {"spacing": "none"}}'</info>
- The <comment>--dry-run</comment> flag will run the fixer without making changes to your files.
- The <comment>--diff</comment> flag can be used to let the fixer output all the changes it makes.
- The <comment>--diff-format</comment> option allows to specify in which format the fixer should output the changes it makes:
- * <comment>null</comment>: no diff;
- * <comment>udiff</comment>: unified diff format.
- 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.
- A rule is considered risky if it could change code behaviour. By default no risky rules are run.
- The <comment>--stop-on-violation</comment> flag stops the execution upon first file that needs to be fixed.
- The <comment>--show-progress</comment> option allows you to choose the way process progress is rendered:
- * <comment>none</comment>: disables progress output;
- * <comment>dots</comment>: multiline progress output with number of files and percentage on each line.
- If the option is not provided, it defaults to <comment>dots</comment> unless a config file that disables output is used, in which case it defaults to <comment>none</comment>. This option has no effect if the verbosity of the command is less than <comment>verbose</comment>.
- <info>$ php %command.full_name% --verbose --show-progress=dots</info>
- The command can also read from standard input, in which case it won't
- automatically fix anything:
- <info>$ cat foo.php | php %command.full_name% --diff -</info>
- Finally, if you don't need BC kept on CLI level, you might use `PHP_CS_FIXER_FUTURE_MODE` to start using options that
- would be default in next MAJOR release and to forbid using deprecated configuration:
- <info>$ PHP_CS_FIXER_FUTURE_MODE=1 php %command.full_name% -v --diff</info>
- Rules
- -----
- Use the following command to quickly understand what a rule will do to your code:
- <info>$ php php-cs-fixer.phar describe align_multiline_comment</info>
- To visualize all the rules that belong to a ruleset:
- <info>$ php php-cs-fixer.phar describe @PSR2</info>
- Choose from the list of available rules:
- %%%FIXERS_DETAILS%%%
- The <comment>--dry-run</comment> option displays the files that need to be
- fixed but without actually modifying them:
- <info>$ php %command.full_name% /path/to/code --dry-run</info>
- Config file
- -----------
- Instead of using command line options to customize the rule, you can save the
- project configuration in a <comment>.php_cs.dist</comment> file in the root directory of your project.
- The file must return an instance of `PhpCsFixer\ConfigInterface` (<url>%%%CONFIG_INTERFACE_URL%%%</url>)
- which lets you configure the rules, the files and directories that
- need to be analyzed. You may also create <comment>.php_cs</comment> file, which is
- the local configuration that will be used instead of the project configuration. It
- is a good practice to add that file into your <comment>.gitignore</comment> file.
- With the <comment>--config</comment> option you can specify the path to the
- <comment>.php_cs</comment> file.
- The example below will add two rules to the default list of PSR2 set rules:
- <?php
- $finder = PhpCsFixer\Finder::create()
- ->exclude('somedir')
- ->notPath('src/Symfony/Component/Translation/Tests/fixtures/resources.php')
- ->in(__DIR__)
- ;
- $config = new Config();
- return $config
- ->setRules([
- '@PSR2' => true,
- 'strict_param' => true,
- 'array_syntax' => ['syntax' => 'short'],
- ])
- ->setFinder($finder)
- ;
- ?>
- **NOTE**: `exclude` will work only for directories, so if you need to exclude file, try `notPath`.
- Both `exclude` and `notPath` methods accept only relative paths to the ones defined with the `in` method.
- See `Symfony\Finder` (<url>https://symfony.com/doc/current/components/finder.html</url>)
- online documentation for other `Finder` methods.
- You may also use an exclude list for the rules instead of the above shown include approach.
- The following example shows how to use all `Symfony` rules but the `full_opening_tag` rule.
- <?php
- $finder = PhpCsFixer\Finder::create()
- ->exclude('somedir')
- ->in(__DIR__)
- ;
- $config = new Config();
- return $config
- ->setRules([
- '@Symfony' => true,
- 'full_opening_tag' => false,
- ])
- ->setFinder($finder)
- ;
- ?>
- You may want to use non-linux whitespaces in your project. Then you need to
- configure them in your config file.
- <?php
- $config = new Config();
- return $config
- ->setIndent("\t")
- ->setLineEnding("\r\n")
- ;
- ?>
- By using `--using-cache` option with `yes` or `no` you can set if the caching
- mechanism should be used.
- Caching
- -------
- The caching mechanism is enabled by default. This will speed up further runs by
- fixing only files that were modified since the last run. The tool will fix all
- files if the tool version has changed or the list of rules has changed.
- Cache is supported only for tool downloaded as phar file or installed via
- composer.
- Cache can be disabled via `--using-cache` option or config file:
- <?php
- $config = new Config();
- return $config->setUsingCache(false);
- ?>
- Cache file can be specified via `--cache-file` option or config file:
- <?php
- $config = new Config();
- return $config->setCacheFile(__DIR__.'/.php_cs.cache');
- ?>
- Using PHP CS Fixer on CI
- ------------------------
- Require `friendsofphp/php-cs-fixer` as a `dev` dependency:
- $ ./composer.phar require --dev friendsofphp/php-cs-fixer
- Then, add the following command to your CI:
- %%%CI_INTEGRATION%%%
- Where `$COMMIT_RANGE` is your range of commits, e.g. `$TRAVIS_COMMIT_RANGE` or `HEAD~..HEAD`.
- Exit code
- ---------
- Exit code of the fix command is built using following bit flags:
- * 0 - OK.
- * 1 - General error (or PHP minimal requirement not matched).
- * 4 - Some files have invalid syntax (only in dry-run mode).
- * 8 - Some files need fixing (only in dry-run mode).
- * 16 - Configuration error of the application.
- * 32 - Configuration error of a Fixer.
- * 64 - Exception raised within the application.
- EOF
- ;
- return strtr($template, [
- '%%%CONFIG_INTERFACE_URL%%%' => sprintf(
- 'https://github.com/FriendsOfPHP/PHP-CS-Fixer/blob/v%s/src/ConfigInterface.php',
- self::getLatestReleaseVersionFromChangeLog()
- ),
- '%%%CI_INTEGRATION%%%' => implode("\n", array_map(
- static function ($line) { return ' $ '.$line; },
- \array_slice(file(__DIR__.'/../../../ci-integration.sh', FILE_IGNORE_NEW_LINES), 3)
- )),
- '%%%FIXERS_DETAILS%%%' => self::getFixersHelp(),
- ]);
- }
- /**
- * @param mixed $value
- *
- * @return string
- */
- public static function toString($value)
- {
- return \is_array($value)
- ? static::arrayToString($value)
- : static::scalarToString($value)
- ;
- }
- /**
- * Returns the allowed values of the given option that can be converted to a string.
- *
- * @return null|array
- */
- public static function getDisplayableAllowedValues(FixerOptionInterface $option)
- {
- $allowed = $option->getAllowedValues();
- if (null !== $allowed) {
- $allowed = array_filter($allowed, static function ($value) {
- return !($value instanceof \Closure);
- });
- usort($allowed, static function ($valueA, $valueB) {
- if ($valueA instanceof AllowedValueSubset) {
- return -1;
- }
- if ($valueB instanceof AllowedValueSubset) {
- return 1;
- }
- return strcasecmp(
- self::toString($valueA),
- self::toString($valueB)
- );
- });
- if (0 === \count($allowed)) {
- $allowed = null;
- }
- }
- return $allowed;
- }
- /**
- * @throws \RuntimeException when failing to parse the change log file
- *
- * @return string
- */
- public static function getLatestReleaseVersionFromChangeLog()
- {
- static $version = null;
- if (null !== $version) {
- return $version;
- }
- $changelogFile = self::getChangeLogFile();
- if (null === $changelogFile) {
- $version = Application::VERSION;
- return $version;
- }
- $changelog = @file_get_contents($changelogFile);
- if (false === $changelog) {
- $error = error_get_last();
- throw new \RuntimeException(sprintf(
- 'Failed to read content of the changelog file "%s".%s',
- $changelogFile,
- $error ? ' '.$error['message'] : ''
- ));
- }
- for ($i = Application::getMajorVersion(); $i > 0; --$i) {
- if (1 === Preg::match('/Changelog for v('.$i.'.\d+.\d+)/', $changelog, $matches)) {
- $version = $matches[1];
- break;
- }
- }
- if (null === $version) {
- throw new \RuntimeException(sprintf('Failed to parse changelog data of "%s".', $changelogFile));
- }
- return $version;
- }
- /**
- * {@inheritdoc}
- */
- protected function initialize(InputInterface $input, OutputInterface $output)
- {
- $output->getFormatter()->setStyle('url', new OutputFormatterStyle('blue'));
- }
- /**
- * @return null|string
- */
- private static function getChangeLogFile()
- {
- $changelogFile = __DIR__.'/../../../CHANGELOG.md';
- return is_file($changelogFile) ? $changelogFile : null;
- }
- /**
- * @return string
- */
- private static function getFixersHelp()
- {
- $help = '';
- $fixerFactory = new FixerFactory();
- /** @var AbstractFixer[] $fixers */
- $fixers = $fixerFactory->registerBuiltInFixers()->getFixers();
- // sort fixers by name
- usort(
- $fixers,
- static function (FixerInterface $a, FixerInterface $b) {
- return strcmp($a->getName(), $b->getName());
- }
- );
- $ruleSets = [];
- foreach (RuleSets::getSetDefinitionNames() as $setName) {
- $ruleSets[$setName] = new RuleSet([$setName => true]);
- }
- $getSetsWithRule = static function ($rule) use ($ruleSets) {
- $sets = [];
- foreach ($ruleSets as $setName => $ruleSet) {
- if ($ruleSet->hasRule($rule)) {
- $sets[] = $setName;
- }
- }
- return $sets;
- };
- $count = \count($fixers) - 1;
- foreach ($fixers as $i => $fixer) {
- $sets = $getSetsWithRule($fixer->getName());
- $description = $fixer->getDefinition()->getSummary();
- if ($fixer instanceof DeprecatedFixerInterface) {
- $successors = $fixer->getSuccessorsNames();
- $message = [] === $successors
- ? 'will be removed on next major version'
- : sprintf('use %s instead', Utils::naturalLanguageJoinWithBackticks($successors));
- $description .= sprintf(' DEPRECATED: %s.', $message);
- }
- $description = implode("\n | ", self::wordwrap(
- Preg::replace('/(`.+?`)/', '<info>$1</info>', $description),
- 72
- ));
- if (!empty($sets)) {
- $help .= sprintf(" * <comment>%s</comment> [%s]\n | %s\n", $fixer->getName(), implode(', ', $sets), $description);
- } else {
- $help .= sprintf(" * <comment>%s</comment>\n | %s\n", $fixer->getName(), $description);
- }
- if ($fixer->isRisky()) {
- $help .= sprintf(
- " | *Risky rule: %s.*\n",
- Preg::replace(
- '/(`.+?`)/',
- '<info>$1</info>',
- lcfirst(Preg::replace('/\.$/', '', $fixer->getDefinition()->getRiskyDescription()))
- )
- );
- }
- if ($fixer instanceof ConfigurableFixerInterface) {
- $configurationDefinition = $fixer->getConfigurationDefinition();
- $configurationDefinitionOptions = $configurationDefinition->getOptions();
- if (\count($configurationDefinitionOptions)) {
- $help .= " |\n | Configuration options:\n";
- usort(
- $configurationDefinitionOptions,
- static function (FixerOptionInterface $optionA, FixerOptionInterface $optionB) {
- return strcmp($optionA->getName(), $optionB->getName());
- }
- );
- foreach ($configurationDefinitionOptions as $option) {
- $line = '<info>'.OutputFormatter::escape($option->getName()).'</info>';
- $allowed = self::getDisplayableAllowedValues($option);
- if (null !== $allowed) {
- foreach ($allowed as &$value) {
- if ($value instanceof AllowedValueSubset) {
- $value = 'a subset of <comment>'.self::toString($value->getAllowedValues()).'</comment>';
- } else {
- $value = '<comment>'.self::toString($value).'</comment>';
- }
- }
- } else {
- $allowed = array_map(
- static function ($type) {
- return '<comment>'.$type.'</comment>';
- },
- $option->getAllowedTypes()
- );
- }
- if (null !== $allowed) {
- $line .= ' ('.implode(', ', $allowed).')';
- }
- $line .= ': '.Preg::replace(
- '/(`.+?`)/',
- '<info>$1</info>',
- lcfirst(Preg::replace('/\.$/', '', OutputFormatter::escape($option->getDescription())))
- ).'; ';
- if ($option->hasDefault()) {
- $line .= 'defaults to <comment>'.self::toString($option->getDefault()).'</comment>';
- } else {
- $line .= 'required';
- }
- if ($option instanceof DeprecatedFixerOption) {
- $line .= '. DEPRECATED: '.Preg::replace(
- '/(`.+?`)/',
- '<info>$1</info>',
- lcfirst(Preg::replace('/\.$/', '', OutputFormatter::escape($option->getDeprecationMessage())))
- );
- }
- if ($option instanceof AliasedFixerOption) {
- $line .= '; DEPRECATED alias: <comment>'.$option->getAlias().'</comment>';
- }
- foreach (self::wordwrap($line, 72) as $index => $line) {
- $help .= (0 === $index ? ' | - ' : ' | ').$line."\n";
- }
- }
- }
- } elseif ($fixer instanceof ConfigurableFixerInterface) {
- $help .= " | *Configurable rule.*\n";
- }
- if ($count !== $i) {
- $help .= "\n";
- }
- }
- // prevent "\</foo>" from being rendered as an escaped literal style tag
- return Preg::replace('#\\\\(</.*?>)#', '<<$1', $help);
- }
- /**
- * Wraps a string to the given number of characters, ignoring style tags.
- *
- * @param string $string
- * @param int $width
- *
- * @return string[]
- */
- private static function wordwrap($string, $width)
- {
- $result = [];
- $currentLine = 0;
- $lineLength = 0;
- foreach (explode(' ', $string) as $word) {
- $wordLength = \strlen(Preg::replace('~</?(\w+)>~', '', $word));
- if (0 !== $lineLength) {
- ++$wordLength; // space before word
- }
- if ($lineLength + $wordLength > $width) {
- ++$currentLine;
- $lineLength = 0;
- }
- $result[$currentLine][] = $word;
- $lineLength += $wordLength;
- }
- return array_map(static function ($line) {
- return implode(' ', $line);
- }, $result);
- }
- /**
- * @param mixed $value
- *
- * @return string
- */
- private static function scalarToString($value)
- {
- $str = var_export($value, true);
- return Preg::replace('/\bNULL\b/', 'null', $str);
- }
- /**
- * @return string
- */
- private static function arrayToString(array $value)
- {
- if (0 === \count($value)) {
- return '[]';
- }
- $isHash = static::isHash($value);
- $str = '[';
- foreach ($value as $k => $v) {
- if ($isHash) {
- $str .= static::scalarToString($k).' => ';
- }
- $str .= \is_array($v)
- ? static::arrayToString($v).', '
- : static::scalarToString($v).', '
- ;
- }
- return substr($str, 0, -2).']';
- }
- /**
- * @return bool
- */
- private static function isHash(array $array)
- {
- $i = 0;
- foreach ($array as $k => $v) {
- if ($k !== $i) {
- return true;
- }
- ++$i;
- }
- return false;
- }
- }
|