Просмотр исходного кода

refactor: refactor to templated trait+interface (#7988)

Dariusz Rumiński 8 месяцев назад
Родитель
Сommit
064efa1f89

+ 1 - 0
.gitattributes

@@ -10,6 +10,7 @@
 /infection.json5.dist export-ignore
 /phpstan.dist.neon export-ignore
 /phpunit.xml.dist export-ignore
+/src/Fixer/Internal/ export-ignore
 /tests/ export-ignore
 
 *       text=auto eol=lf

+ 5 - 0
.php-cs-fixer.dist.php

@@ -14,17 +14,22 @@ declare(strict_types=1);
 
 use PhpCsFixer\Config;
 use PhpCsFixer\Finder;
+use PhpCsFixer\Fixer\Internal\ConfigurableFixerTemplateFixer;
 use PhpCsFixer\Runner\Parallel\ParallelConfigFactory;
 
 return (new Config())
     ->setParallelConfig(ParallelConfigFactory::detect()) // @TODO 4.0 no need to call this manually
     ->setRiskyAllowed(true)
+    ->registerCustomFixers([
+        new ConfigurableFixerTemplateFixer(),
+    ])
     ->setRules([
         '@PHP74Migration' => true,
         '@PHP74Migration:risky' => true,
         '@PHPUnit100Migration:risky' => true,
         '@PhpCsFixer' => true,
         '@PhpCsFixer:risky' => true,
+        'PhpCsFixerInternal/configurable_fixer_template' => true, // internal rules, shall not be used outside of main repo
         'general_phpdoc_annotation_remove' => ['annotations' => ['expectedDeprecation']], // one should use PHPUnit built-in method instead
         'header_comment' => ['header' => <<<'EOF'
             This file is part of PHP CS Fixer.

+ 1 - 1
.php-cs-fixer.php-highest.php

@@ -24,7 +24,7 @@ $config = require __DIR__.'/.php-cs-fixer.dist.php';
 $config->setRules(array_merge($config->getRules(), [
     '@PHP83Migration' => true,
     '@PHP80Migration:risky' => true,
-    'php_unit_attributes' => true,
+    'php_unit_attributes' => false, // as is not yet supported by PhpCsFixerInternal/configurable_fixer_template
 ]));
 
 return $config;

+ 4 - 1
composer.json

@@ -66,7 +66,10 @@
     "autoload": {
         "psr-4": {
             "PhpCsFixer\\": "src/"
-        }
+        },
+        "exclude-from-classmap": [
+            "src/Fixer/Internal/*"
+        ]
     },
     "autoload-dev": {
         "psr-4": {

+ 0 - 78
dev-tools/phpstan/baseline.php

@@ -271,12 +271,6 @@ $ignoreErrors[] = [
 	'count' => 1,
 	'path' => __DIR__ . '/../../src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php',
 ];
-$ignoreErrors[] = [
-	// identifier: assign.propertyType
-	'message' => '#^Property PhpCsFixer\\\\Fixer\\\\ClassNotation\\\\ClassAttributesSeparationFixer\\:\\:\\$classElementTypes \\(array\\<string, string\\>\\) does not accept array\\<int\\|string, mixed\\>\\.$#',
-	'count' => 1,
-	'path' => __DIR__ . '/../../src/Fixer/ClassNotation/ClassAttributesSeparationFixer.php',
-];
 $ignoreErrors[] = [
 	// identifier: return.type
 	'message' => '#^Method PhpCsFixer\\\\Fixer\\\\ClassNotation\\\\ClassDefinitionFixer\\:\\:getClassyDefinitionInfo\\(\\) should return array\\{start\\: int, classy\\: int, open\\: int, extends\\: array\\{start\\: int, numberOfExtends\\: int, multiLine\\: bool\\}\\|false, implements\\: array\\{start\\: int, numberOfImplements\\: int, multiLine\\: bool\\}\\|false, anonymousClass\\: bool, final\\: int\\|false, abstract\\: int\\|false, \\.\\.\\.\\} but returns array\\{classy\\: int, open\\: int\\|null, extends\\: array\\<string, bool\\|int\\>\\|false, implements\\: array\\<string, bool\\|int\\>\\|false, anonymousClass\\: bool, final\\: int\\|false, abstract\\: int\\|false, readonly\\: int\\|false, \\.\\.\\.\\}\\.$#',
@@ -373,12 +367,6 @@ $ignoreErrors[] = [
 	'count' => 1,
 	'path' => __DIR__ . '/../../src/Fixer/ClassNotation/OrderedClassElementsFixer.php',
 ];
-$ignoreErrors[] = [
-	// identifier: assign.propertyType
-	'message' => '#^Property PhpCsFixer\\\\Fixer\\\\ClassNotation\\\\OrderedClassElementsFixer\\:\\:\\$typePosition \\(array\\<string, int\\>\\) does not accept array\\<int\\|string, int\\>\\.$#',
-	'count' => 1,
-	'path' => __DIR__ . '/../../src/Fixer/ClassNotation/OrderedClassElementsFixer.php',
-];
 $ignoreErrors[] = [
 	// identifier: argument.unpackNonIterable
 	'message' => '#^Only iterables can be unpacked, array\\<int, PhpCsFixer\\\\Tokenizer\\\\Token\\>\\|int\\|PhpCsFixer\\\\Tokenizer\\\\Token\\|string given in argument \\#3\\.$#',
@@ -421,12 +409,6 @@ $ignoreErrors[] = [
 	'count' => 1,
 	'path' => __DIR__ . '/../../src/Fixer/ClassNotation/ProtectedToPrivateFixer.php',
 ];
-$ignoreErrors[] = [
-	// identifier: assign.propertyType
-	'message' => '#^Property PhpCsFixer\\\\Fixer\\\\Comment\\\\CommentToPhpdocFixer\\:\\:\\$ignoredTags \\(list\\<string\\>\\) does not accept array\\<string\\>\\.$#',
-	'count' => 1,
-	'path' => __DIR__ . '/../../src/Fixer/Comment/CommentToPhpdocFixer.php',
-];
 $ignoreErrors[] = [
 	// identifier: foreach.nonIterable
 	'message' => '#^Argument of an invalid type array\\<int\\<0, max\\>, PhpCsFixer\\\\Tokenizer\\\\Token\\>\\|PhpCsFixer\\\\Tokenizer\\\\Token supplied for foreach, only iterables are supported\\.$#',
@@ -547,12 +529,6 @@ $ignoreErrors[] = [
 	'count' => 2,
 	'path' => __DIR__ . '/../../src/Fixer/LanguageConstruct/CombineConsecutiveIssetsFixer.php',
 ];
-$ignoreErrors[] = [
-	// identifier: assign.propertyType
-	'message' => '#^Property PhpCsFixer\\\\Fixer\\\\LanguageConstruct\\\\FunctionToConstantFixer\\:\\:\\$functionsFixMap \\(array\\<string, list\\<PhpCsFixer\\\\Tokenizer\\\\Token\\>\\>\\) does not accept non\\-empty\\-array\\<int\\|string, list\\<PhpCsFixer\\\\Tokenizer\\\\Token\\>\\>\\.$#',
-	'count' => 1,
-	'path' => __DIR__ . '/../../src/Fixer/LanguageConstruct/FunctionToConstantFixer.php',
-];
 $ignoreErrors[] = [
 	// identifier: booleanAnd.leftNotBoolean
 	'message' => '#^Only booleans are allowed in &&, bool\\|int given on the left side\\.$#',
@@ -601,36 +577,12 @@ $ignoreErrors[] = [
 	'count' => 1,
 	'path' => __DIR__ . '/../../src/Fixer/LanguageConstruct/NoUnsetOnPropertyFixer.php',
 ];
-$ignoreErrors[] = [
-	// identifier: assign.propertyType
-	'message' => '#^Property PhpCsFixer\\\\Fixer\\\\LanguageConstruct\\\\SingleSpaceAroundConstructFixer\\:\\:\\$fixTokenMapContainASingleSpace \\(array\\<string, int\\>\\) does not accept array\\<int\\|string, int\\>\\.$#',
-	'count' => 1,
-	'path' => __DIR__ . '/../../src/Fixer/LanguageConstruct/SingleSpaceAroundConstructFixer.php',
-];
-$ignoreErrors[] = [
-	// identifier: assign.propertyType
-	'message' => '#^Property PhpCsFixer\\\\Fixer\\\\LanguageConstruct\\\\SingleSpaceAroundConstructFixer\\:\\:\\$fixTokenMapFollowedByASingleSpace \\(array\\<string, int\\>\\) does not accept array\\<int\\|string, int\\>\\.$#',
-	'count' => 1,
-	'path' => __DIR__ . '/../../src/Fixer/LanguageConstruct/SingleSpaceAroundConstructFixer.php',
-];
-$ignoreErrors[] = [
-	// identifier: assign.propertyType
-	'message' => '#^Property PhpCsFixer\\\\Fixer\\\\LanguageConstruct\\\\SingleSpaceAroundConstructFixer\\:\\:\\$fixTokenMapPrecededByASingleSpace \\(array\\<string, int\\>\\) does not accept array\\<int\\|string, int\\>\\.$#',
-	'count' => 1,
-	'path' => __DIR__ . '/../../src/Fixer/LanguageConstruct/SingleSpaceAroundConstructFixer.php',
-];
 $ignoreErrors[] = [
 	// identifier: plus.leftNonNumeric
 	'message' => '#^Only numeric types are allowed in \\+, int\\|false given on the left side\\.$#',
 	'count' => 1,
 	'path' => __DIR__ . '/../../src/Fixer/NamespaceNotation/BlankLinesBeforeNamespaceFixer.php',
 ];
-$ignoreErrors[] = [
-	// identifier: return.type
-	'message' => '#^Method PhpCsFixer\\\\Fixer\\\\Operator\\\\BinaryOperatorSpacesFixer\\:\\:resolveOperatorsFromConfig\\(\\) should return array\\<string, string\\> but returns array\\<int\\|string, mixed\\>\\.$#',
-	'count' => 1,
-	'path' => __DIR__ . '/../../src/Fixer/Operator/BinaryOperatorSpacesFixer.php',
-];
 $ignoreErrors[] = [
 	// identifier: booleanNot.exprNotBoolean
 	'message' => '#^Only booleans are allowed in a negated boolean, int\\|false given\\.$#',
@@ -751,18 +703,6 @@ $ignoreErrors[] = [
 	'count' => 1,
 	'path' => __DIR__ . '/../../src/Fixer/Phpdoc/PhpdocParamOrderFixer.php',
 ];
-$ignoreErrors[] = [
-	// identifier: argument.type
-	'message' => '#^Parameter \\#1 \\$callback of function array_map expects \\(callable\\(int\\|string\\)\\: mixed\\)\\|null, Closure\\(string\\)\\: string given\\.$#',
-	'count' => 1,
-	'path' => __DIR__ . '/../../src/Fixer/Phpdoc/PhpdocTagTypeFixer.php',
-];
-$ignoreErrors[] = [
-	// identifier: assign.propertyType
-	'message' => '#^Property PhpCsFixer\\\\Fixer\\\\Phpdoc\\\\PhpdocToCommentFixer\\:\\:\\$ignoredTags \\(list\\<string\\>\\) does not accept array\\<string\\>\\.$#',
-	'count' => 1,
-	'path' => __DIR__ . '/../../src/Fixer/Phpdoc/PhpdocToCommentFixer.php',
-];
 $ignoreErrors[] = [
 	// identifier: ternary.condNotBoolean
 	'message' => '#^Only booleans are allowed in a ternary operator condition, int\\|false given\\.$#',
@@ -781,12 +721,6 @@ $ignoreErrors[] = [
 	'count' => 1,
 	'path' => __DIR__ . '/../../src/Fixer/Whitespace/ArrayIndentationFixer.php',
 ];
-$ignoreErrors[] = [
-	// identifier: assign.propertyType
-	'message' => '#^Property PhpCsFixer\\\\Fixer\\\\Whitespace\\\\BlankLineBeforeStatementFixer\\:\\:\\$fixTokenMap \\(list\\<int\\>\\) does not accept non\\-empty\\-array\\<int\\|string, int\\>\\.$#',
-	'count' => 1,
-	'path' => __DIR__ . '/../../src/Fixer/Whitespace/BlankLineBeforeStatementFixer.php',
-];
 $ignoreErrors[] = [
 	// identifier: offsetAccess.notFound
 	'message' => '#^Offset \'new_indent\' might not exist on array\\{type\\: \'statement\', skip\\: bool, end_index\\: int\\|null, end_index_inclusive\\: bool, initial_indent\\: string, new_indent\\?\\: string, is_indented_block\\: bool\\}\\.$#',
@@ -1297,18 +1231,6 @@ $ignoreErrors[] = [
 	'count' => 3,
 	'path' => __DIR__ . '/../../tests/FixerDefinition/VersionSpecificationTest.php',
 ];
-$ignoreErrors[] = [
-	// identifier: return.type
-	'message' => '#^Method class@anonymous/tests/FixerFactoryTest\\.php\\:223\\:\\:getRuleConfiguration\\(\\) should return array\\<string, mixed\\> but returns array\\<string, mixed\\>\\|true\\.$#',
-	'count' => 1,
-	'path' => __DIR__ . '/../../tests/FixerFactoryTest.php',
-];
-$ignoreErrors[] = [
-	// identifier: return.type
-	'message' => '#^Method class@anonymous/tests/FixerFactoryTest\\.php\\:58\\:\\:getRules\\(\\) should return array\\<string, array\\<string, mixed\\>\\|true\\> but returns array\\<string, array\\<string, mixed\\>\\|bool\\>\\.$#',
-	'count' => 1,
-	'path' => __DIR__ . '/../../tests/FixerFactoryTest.php',
-];
 $ignoreErrors[] = [
 	// identifier: argument.type
 	'message' => '#^Parameter \\#1 \\$exception of method PHPUnit\\\\Framework\\\\TestCase\\:\\:expectException\\(\\) expects class\\-string\\<Throwable\\>, string given\\.$#',

+ 1 - 1
doc/rules/phpdoc/phpdoc_separation.rst

@@ -15,7 +15,7 @@ Configuration
 Sets of annotation types to be grouped together. Use ``*`` to match any tag
 character.
 
-Allowed types: ``list<string[]>``
+Allowed types: ``list<list<string>>``
 
 Default value: ``[['author', 'copyright', 'license'], ['category', 'package', 'subpackage'], ['property', 'property-read', 'property-write'], ['deprecated', 'link', 'see', 'since']]``
 

+ 1 - 5
phpstan.dist.neon

@@ -47,14 +47,10 @@ parameters:
         # This one is tricky, because adding @param to PHPDoc causes failures on PHP7.4 with lowest deps (phpdoc_to_param_type), using @phpstan-param causes failures on all PHP versions (phpdoc_add_missing_param_annotation)
         - '/^Parameter #1 \$class \(string\) of method PhpCsFixer\\StdinFileInfo::get(File|Path)+Info+\(\) should be contravariant with parameter \$class \(string\|null\) of method SplFileInfo::get(File|Path)+Info\(\)$/'
 
-        # Contract for configurable fixers defines `array<string, mixed>`, but we want narrowed array shape for each fixer
-        # See: https://github.com/phpstan/phpstan/discussions/9686
-        - '/^Parameter #1 \$configuration \(.*\) of method PhpCsFixer\\Fixer\\.*::configure\(\) should be contravariant with parameter \$configuration \(array<string, mixed>\) of method PhpCsFixer\\(AbstractFixer|Fixer\\ConfigurableFixerInterface)+::configure\(\)$/'
-
         # PHPUnit data providers return type were not maintained originally, this exception should shrink over time (eg with help of custom, re-usable type)
         -
             message: '#^Method PhpCsFixer\\Tests\\.+::provide.+Cases\(\) return type has no value type specified in iterable type iterable\.$#'
             path: tests
-            count: 1012
+            count: 1013
     tipsOfTheDay: false
     tmpDir: dev-tools/phpstan/cache

+ 12 - 1
src/AbstractDoctrineAnnotationFixer.php

@@ -26,6 +26,17 @@ use PhpCsFixer\Tokenizer\TokensAnalyzer;
 
 /**
  * @internal
+ *
+ * @phpstan-type _AutogeneratedInputConfiguration array{
+ *  scalar_types?: bool,
+ *  union_types?: bool
+ * }
+ * @phpstan-type _AutogeneratedComputedConfiguration array{
+ *  scalar_types: bool,
+ *  union_types: bool
+ * }
+ *
+ * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
  */
 abstract class AbstractDoctrineAnnotationFixer extends AbstractFixer implements ConfigurableFixerInterface
 {
@@ -53,7 +64,7 @@ abstract class AbstractDoctrineAnnotationFixer extends AbstractFixer implements
 
             $doctrineAnnotationTokens = DoctrineAnnotationTokens::createFromDocComment(
                 $docCommentToken,
-                $this->configuration['ignored_tags']
+                $this->configuration['ignored_tags'] // @phpstan-ignore-line
             );
 
             $this->fixAnnotations($doctrineAnnotationTokens);

+ 1 - 90
src/AbstractFixer.php

@@ -14,19 +14,11 @@ declare(strict_types=1);
 
 namespace PhpCsFixer;
 
-use PhpCsFixer\ConfigurationException\InvalidFixerConfigurationException;
-use PhpCsFixer\ConfigurationException\InvalidForEnvFixerConfigurationException;
 use PhpCsFixer\ConfigurationException\RequiredFixerConfigurationException;
-use PhpCsFixer\Console\Application;
 use PhpCsFixer\Fixer\ConfigurableFixerInterface;
 use PhpCsFixer\Fixer\FixerInterface;
 use PhpCsFixer\Fixer\WhitespacesAwareFixerInterface;
-use PhpCsFixer\FixerConfiguration\DeprecatedFixerOption;
-use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
-use PhpCsFixer\FixerConfiguration\InvalidOptionsForEnvException;
 use PhpCsFixer\Tokenizer\Tokens;
-use Symfony\Component\OptionsResolver\Exception\ExceptionInterface;
-use Symfony\Component\OptionsResolver\Exception\MissingOptionsException;
 
 /**
  * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
@@ -35,21 +27,11 @@ use Symfony\Component\OptionsResolver\Exception\MissingOptionsException;
  */
 abstract class AbstractFixer implements FixerInterface
 {
-    /**
-     * @var null|array<string, mixed>
-     */
-    protected $configuration;
-
     /**
      * @var WhitespacesFixerConfig
      */
     protected $whitespacesConfig;
 
-    /**
-     * @var null|FixerConfigurationResolverInterface
-     */
-    private $configurationDefinition;
-
     public function __construct()
     {
         if ($this instanceof ConfigurableFixerInterface) {
@@ -67,7 +49,7 @@ abstract class AbstractFixer implements FixerInterface
 
     final public function fix(\SplFileInfo $file, Tokens $tokens): void
     {
-        if ($this instanceof ConfigurableFixerInterface && null === $this->configuration) {
+        if ($this instanceof ConfigurableFixerInterface && property_exists($this, 'configuration') && null === $this->configuration) {
             throw new RequiredFixerConfigurationException($this->getName(), 'Configuration is required.');
         }
 
@@ -99,68 +81,6 @@ abstract class AbstractFixer implements FixerInterface
         return true;
     }
 
-    /**
-     * @param array<string, mixed> $configuration
-     */
-    public function configure(array $configuration): void
-    {
-        if (!$this instanceof ConfigurableFixerInterface) {
-            throw new \LogicException('Cannot configure using Abstract parent, child not implementing "PhpCsFixer\Fixer\ConfigurableFixerInterface".');
-        }
-
-        foreach ($this->getConfigurationDefinition()->getOptions() as $option) {
-            if (!$option instanceof DeprecatedFixerOption) {
-                continue;
-            }
-
-            $name = $option->getName();
-            if (\array_key_exists($name, $configuration)) {
-                Utils::triggerDeprecation(new \InvalidArgumentException(sprintf(
-                    'Option "%s" for rule "%s" is deprecated and will be removed in version %d.0. %s',
-                    $name,
-                    $this->getName(),
-                    Application::getMajorVersion() + 1,
-                    str_replace('`', '"', $option->getDeprecationMessage())
-                )));
-            }
-        }
-
-        try {
-            $this->configuration = $this->getConfigurationDefinition()->resolve($configuration);
-        } catch (MissingOptionsException $exception) {
-            throw new RequiredFixerConfigurationException(
-                $this->getName(),
-                sprintf('Missing required configuration: %s', $exception->getMessage()),
-                $exception
-            );
-        } catch (InvalidOptionsForEnvException $exception) {
-            throw new InvalidForEnvFixerConfigurationException(
-                $this->getName(),
-                sprintf('Invalid configuration for env: %s', $exception->getMessage()),
-                $exception
-            );
-        } catch (ExceptionInterface $exception) {
-            throw new InvalidFixerConfigurationException(
-                $this->getName(),
-                sprintf('Invalid configuration: %s', $exception->getMessage()),
-                $exception
-            );
-        }
-    }
-
-    public function getConfigurationDefinition(): FixerConfigurationResolverInterface
-    {
-        if (!$this instanceof ConfigurableFixerInterface) {
-            throw new \LogicException(sprintf('Cannot get configuration definition using Abstract parent, child "%s" not implementing "PhpCsFixer\Fixer\ConfigurableFixerInterface".', static::class));
-        }
-
-        if (null === $this->configurationDefinition) {
-            $this->configurationDefinition = $this->createConfigurationDefinition();
-        }
-
-        return $this->configurationDefinition;
-    }
-
     public function setWhitespacesConfig(WhitespacesFixerConfig $config): void
     {
         if (!$this instanceof WhitespacesAwareFixerInterface) {
@@ -172,15 +92,6 @@ abstract class AbstractFixer implements FixerInterface
 
     abstract protected function applyFix(\SplFileInfo $file, Tokens $tokens): void;
 
-    protected function createConfigurationDefinition(): FixerConfigurationResolverInterface
-    {
-        if (!$this instanceof ConfigurableFixerInterface) {
-            throw new \LogicException('Cannot create configuration definition using Abstract parent, child not implementing "PhpCsFixer\Fixer\ConfigurableFixerInterface".');
-        }
-
-        throw new \LogicException('Not implemented.');
-    }
-
     private function getDefaultWhitespacesFixerConfig(): WhitespacesFixerConfig
     {
         static $defaultWhitespacesFixerConfig = null;

+ 14 - 0
src/AbstractPhpdocToTypeDeclarationFixer.php

@@ -18,6 +18,7 @@ use PhpCsFixer\DocBlock\Annotation;
 use PhpCsFixer\DocBlock\DocBlock;
 use PhpCsFixer\DocBlock\TypeExpression;
 use PhpCsFixer\Fixer\ConfigurableFixerInterface;
+use PhpCsFixer\Fixer\ConfigurableFixerTrait;
 use PhpCsFixer\FixerConfiguration\FixerConfigurationResolver;
 use PhpCsFixer\FixerConfiguration\FixerConfigurationResolverInterface;
 use PhpCsFixer\FixerConfiguration\FixerOptionBuilder;
@@ -31,9 +32,22 @@ use PhpCsFixer\Tokenizer\Tokens;
  * @internal
  *
  * @phpstan-type _CommonTypeInfo array{commonType: string, isNullable: bool}
+ * @phpstan-type _AutogeneratedInputConfiguration array{
+ *  scalar_types?: bool,
+ *  union_types?: bool
+ * }
+ * @phpstan-type _AutogeneratedComputedConfiguration array{
+ *  scalar_types: bool,
+ *  union_types: bool
+ * }
+ *
+ * @implements ConfigurableFixerInterface<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration>
  */
 abstract class AbstractPhpdocToTypeDeclarationFixer extends AbstractFixer implements ConfigurableFixerInterface
 {
+    /** @use ConfigurableFixerTrait<_AutogeneratedInputConfiguration, _AutogeneratedComputedConfiguration> */
+    use ConfigurableFixerTrait;
+
     private const REGEX_CLASS = '(?:\\\?+'.TypeExpression::REGEX_IDENTIFIER
         .'(\\\\'.TypeExpression::REGEX_IDENTIFIER.')*+)';
 

Некоторые файлы не были показаны из-за большого количества измененных файлов