Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
55 / 55
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
ECSConfig
100.00% covered (success)
100.00%
55 / 55
100.00% covered (success)
100.00%
4 / 4
9
100.00% covered (success)
100.00%
1 / 1
 configure
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 applyDefaultPathsAndSkips
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 applyDefaultRulesAndSets
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
2
 applyPhpCsFixerSets
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2
3declare(strict_types=1);
4
5/**
6 * Fast Forward Development Tools for PHP projects.
7 *
8 * This file is part of fast-forward/dev-tools project.
9 *
10 * @author   Felipe SayĆ£o Lobato Abreu <github@mentordosnerds.com>
11 * @license  https://opensource.org/licenses/MIT MIT License
12 *
13 * @see      https://github.com/php-fast-forward/
14 * @see      https://github.com/php-fast-forward/dev-tools
15 * @see      https://github.com/php-fast-forward/dev-tools/issues
16 * @see      https://php-fast-forward.github.io/dev-tools/
17 * @see      https://datatracker.ietf.org/doc/html/rfc2119
18 */
19
20namespace FastForward\DevTools\Config;
21
22use FastForward\DevTools\Path\WorkingProjectPathResolver;
23use PhpCsFixer\Fixer\Import\GlobalNamespaceImportFixer;
24use PhpCsFixer\Fixer\Phpdoc\GeneralPhpdocAnnotationRemoveFixer;
25use PhpCsFixer\Fixer\Phpdoc\PhpdocAlignFixer;
26use PhpCsFixer\Fixer\Phpdoc\NoEmptyPhpdocFixer;
27use PhpCsFixer\Fixer\Phpdoc\NoSuperfluousPhpdocTagsFixer;
28use PhpCsFixer\Fixer\Phpdoc\PhpdocAddMissingParamAnnotationFixer;
29use PhpCsFixer\Fixer\Phpdoc\PhpdocNoEmptyReturnFixer;
30use PhpCsFixer\Fixer\Phpdoc\PhpdocToCommentFixer;
31use PhpCsFixer\Fixer\PhpUnit\PhpUnitTestCaseStaticMethodCallsFixer;
32use PhpCsFixer\FixerFactory;
33use PhpCsFixer\RuleSet\RuleSet;
34use Symplify\CodingStandard\Fixer\Annotation\RemoveMethodNameDuplicateDescriptionFixer;
35use Symplify\CodingStandard\Fixer\Annotation\RemovePHPStormAnnotationFixer;
36use Symplify\CodingStandard\Fixer\Annotation\RemovePropertyVariableNameDescriptionFixer;
37use Symplify\CodingStandard\Fixer\ArrayNotation\ArrayListItemNewlineFixer;
38use Symplify\CodingStandard\Fixer\ArrayNotation\ArrayOpenerAndCloserNewlineFixer;
39use Symplify\CodingStandard\Fixer\Commenting\ParamReturnAndVarTagMalformsFixer;
40use Symplify\CodingStandard\Fixer\Commenting\RemoveUselessDefaultCommentFixer;
41use Symplify\CodingStandard\Fixer\LineLength\LineLengthFixer;
42use Symplify\CodingStandard\Fixer\Spacing\MethodChainingNewlineFixer;
43use Symplify\CodingStandard\Fixer\Spacing\SpaceAfterCommaHereNowDocFixer;
44use Symplify\CodingStandard\Fixer\Spacing\StandaloneLinePromotedPropertyFixer;
45use Symplify\CodingStandard\Fixer\Strict\BlankLineAfterStrictTypesFixer;
46use Symplify\EasyCodingStandard\Config\ECSConfig as ECSConfigInterface;
47use Symplify\EasyCodingStandard\Configuration\ECSConfigBuilder;
48
49use function Safe\getcwd;
50
51/**
52 * Provides the default ECS configuration.
53 *
54 * Consumers can use this as a starting point and extend it:
55 *
56 *     $config = \FastForward\DevTools\Config\ECSConfig::configure();
57 *     $config->withRules([CustomRule::class]);
58 *     $config->withConfiguredRule(PhpdocAlignFixer::class, ['align' => 'vertical']);
59 *     return $config;
60 *
61 * @see https://github.com/symplify/easy-coding-standard
62 */
63 class ECSConfig
64{
65    /**
66     * @var array{symfony: bool, symfonyRisky: bool, auto: bool, autoRisky: bool} the PHP-CS-Fixer sets applied by default
67     */
68    public const array DEFAULT_PHP_CS_FIXER_SETS = [
69        'symfony' => true,
70        'symfonyRisky' => true,
71        'auto' => true,
72        'autoRisky' => true,
73    ];
74
75    /**
76     * @var array{psr12: bool, common: bool, symplify: bool, cleanCode: bool} the prepared ECS sets applied by default
77     */
78    public const array DEFAULT_PREPARED_SETS = [
79        'psr12' => true,
80        'common' => true,
81        'symplify' => true,
82        'cleanCode' => true,
83    ];
84
85    /**
86     * @var list<class-string> the ECS/CS Fixer rules that SHOULD be skipped by default
87     */
88    public const array DEFAULT_SKIPPED_RULES = [
89        PhpdocToCommentFixer::class,
90        NoSuperfluousPhpdocTagsFixer::class,
91        NoEmptyPhpdocFixer::class,
92        PhpdocNoEmptyReturnFixer::class,
93        GlobalNamespaceImportFixer::class,
94        GeneralPhpdocAnnotationRemoveFixer::class,
95    ];
96
97    /**
98     * @var array<class-string, array<string, mixed>> the configured ECS rules applied by default
99     */
100    public const array DEFAULT_CONFIGURED_RULES = [
101        PhpdocAlignFixer::class => [
102            'align' => 'left',
103        ],
104        PhpUnitTestCaseStaticMethodCallsFixer::class => [
105            'call_type' => 'self',
106        ],
107        PhpdocAddMissingParamAnnotationFixer::class => [
108            'only_untyped' => false,
109        ],
110    ];
111
112    /**
113     * Creates the default ECS configuration.
114     *
115     * @param callable|null $customize optional callback to customize the configuration builder
116     *
117     * @return ECSConfigBuilder the configured ECS configuration builder
118     */
119    public static function configure(?callable $customize = null): ECSConfigBuilder
120    {
121        $workingDirectory = getcwd();
122        $config = new ECSConfigBuilder();
123
124        self::applyDefaultPathsAndSkips($config, $workingDirectory);
125        self::applyDefaultRulesAndSets($config);
126
127        if (null !== $customize) {
128            $customize($config);
129        }
130
131        return $config;
132    }
133
134    /**
135     * Applies the default repository paths and skipped rules to an ECS builder.
136     *
137     * @param ECSConfigBuilder $config
138     * @param string $workingDirectory
139     */
140    public static function applyDefaultPathsAndSkips(
141        ECSConfigBuilder $config,
142        string $workingDirectory
143    ): ECSConfigBuilder {
144        $paths = WorkingProjectPathResolver::getToolingSourcePaths($workingDirectory);
145        $skipPaths = WorkingProjectPathResolver::getToolingExcludedDirectories();
146
147        return $config
148            ->withPaths($paths)
149            ->withSkip([...$skipPaths, ...self::DEFAULT_SKIPPED_RULES]);
150    }
151
152    /**
153     * Applies the default ECS sets, root files, and configured rules to an ECS builder.
154     *
155     * @param ECSConfigBuilder $config
156     */
157    public static function applyDefaultRulesAndSets(ECSConfigBuilder $config): ECSConfigBuilder
158    {
159        $config
160            ->withRootFiles()
161            ->withPreparedSets(
162                psr12: self::DEFAULT_PREPARED_SETS['psr12'],
163                common: self::DEFAULT_PREPARED_SETS['common'],
164                cleanCode: self::DEFAULT_PREPARED_SETS['cleanCode'],
165            );
166
167        $config->withSets([__DIR__ . '/../../resources/ecs/php-cs-fixer.php']);
168
169        foreach (self::DEFAULT_CONFIGURED_RULES as $rule => $configuration) {
170            $config->withConfiguredRule($rule, $configuration);
171        }
172
173        return $config;
174    }
175
176    /**
177     * Registers PHP-CS-Fixer presets and the legacy Symplify set without deprecated ECS APIs.
178     *
179     * @param ECSConfigInterface $config
180     */
181    public static function applyPhpCsFixerSets(ECSConfigInterface $config): void
182    {
183        $ruleSet = new RuleSet([
184            '@Symfony' => self::DEFAULT_PHP_CS_FIXER_SETS['symfony'],
185            '@Symfony:risky' => self::DEFAULT_PHP_CS_FIXER_SETS['symfonyRisky'],
186            '@auto' => self::DEFAULT_PHP_CS_FIXER_SETS['auto'],
187            '@auto:risky' => self::DEFAULT_PHP_CS_FIXER_SETS['autoRisky'],
188        ]);
189        $fixerFactory = new FixerFactory();
190        $fixerFactory->registerBuiltInFixers();
191        $fixerFactory->useRuleSet($ruleSet);
192        foreach ($fixerFactory->getFixers() as $fixer) {
193            $configuration = $ruleSet->getRuleConfiguration($fixer->getName());
194
195            if (null === $configuration) {
196                $config->rule($fixer::class);
197            } else {
198                $config->ruleWithConfiguration($fixer::class, $configuration);
199            }
200        }
201
202        if (self::DEFAULT_PREPARED_SETS['symplify']) {
203            $config->rules([
204                RemovePHPStormAnnotationFixer::class,
205                ParamReturnAndVarTagMalformsFixer::class,
206                RemoveUselessDefaultCommentFixer::class,
207                RemoveMethodNameDuplicateDescriptionFixer::class,
208                RemovePropertyVariableNameDescriptionFixer::class,
209                ArrayListItemNewlineFixer::class,
210                ArrayOpenerAndCloserNewlineFixer::class,
211                StandaloneLinePromotedPropertyFixer::class,
212                MethodChainingNewlineFixer::class,
213                SpaceAfterCommaHereNowDocFixer::class,
214                BlankLineAfterStrictTypesFixer::class,
215                LineLengthFixer::class,
216            ]);
217            $config->ruleWithConfiguration(GeneralPhpdocAnnotationRemoveFixer::class, [
218                'annotations' => ['throws', 'author', 'package', 'group', 'covers', 'category'],
219            ]);
220        }
221    }
222}