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;
48use function Safe\getcwd;
49
50/**
51 * Provides the default ECS configuration.
52 *
53 * Consumers can use this as a starting point and extend it:
54 *
55 *     $config = \FastForward\DevTools\Config\ECSConfig::configure();
56 *     $config->withRules([CustomRule::class]);
57 *     $config->withConfiguredRule(PhpdocAlignFixer::class, ['align' => 'vertical']);
58 *     return $config;
59 *
60 * @see https://github.com/symplify/easy-coding-standard
61 */
62 class ECSConfig
63{
64    /**
65     * @var array{symfony: bool, symfonyRisky: bool, auto: bool, autoRisky: bool} the PHP-CS-Fixer sets applied by default
66     */
67    public const array DEFAULT_PHP_CS_FIXER_SETS = [
68        'symfony' => true,
69        'symfonyRisky' => true,
70        'auto' => true,
71        'autoRisky' => true,
72    ];
73
74    /**
75     * @var array{psr12: bool, common: bool, symplify: bool, cleanCode: bool} the prepared ECS sets applied by default
76     */
77    public const array DEFAULT_PREPARED_SETS = [
78        'psr12' => true,
79        'common' => true,
80        'symplify' => true,
81        'cleanCode' => true,
82    ];
83
84    /**
85     * @var list<class-string> the ECS/CS Fixer rules that SHOULD be skipped by default
86     */
87    public const array DEFAULT_SKIPPED_RULES = [
88        PhpdocToCommentFixer::class,
89        NoSuperfluousPhpdocTagsFixer::class,
90        NoEmptyPhpdocFixer::class,
91        PhpdocNoEmptyReturnFixer::class,
92        GlobalNamespaceImportFixer::class,
93        GeneralPhpdocAnnotationRemoveFixer::class,
94    ];
95
96    /**
97     * @var array<class-string, array<string, mixed>> the configured ECS rules applied by default
98     */
99    public const array DEFAULT_CONFIGURED_RULES = [
100        PhpdocAlignFixer::class => [
101            'align' => 'left',
102        ],
103        PhpUnitTestCaseStaticMethodCallsFixer::class => [
104            'call_type' => 'self',
105        ],
106        PhpdocAddMissingParamAnnotationFixer::class => [
107            'only_untyped' => false,
108        ],
109    ];
110
111    /**
112     * Creates the default ECS configuration.
113     *
114     * @param callable|null $customize optional callback to customize the configuration builder
115     *
116     * @return ECSConfigBuilder the configured ECS configuration builder
117     */
118    public static function configure(?callable $customize = null): ECSConfigBuilder
119    {
120        $workingDirectory = getcwd();
121        $config = new ECSConfigBuilder();
122
123        self::applyDefaultPathsAndSkips($config, $workingDirectory);
124        self::applyDefaultRulesAndSets($config);
125
126        if (null !== $customize) {
127            $customize($config);
128        }
129
130        return $config;
131    }
132
133    /**
134     * Applies the default repository paths and skipped rules to an ECS builder.
135     *
136     * @param ECSConfigBuilder $config
137     * @param string $workingDirectory
138     */
139    public static function applyDefaultPathsAndSkips(
140        ECSConfigBuilder $config,
141        string $workingDirectory
142    ): ECSConfigBuilder {
143        $paths = WorkingProjectPathResolver::getToolingSourcePaths($workingDirectory);
144        $skipPaths = WorkingProjectPathResolver::getToolingExcludedDirectories();
145
146        return $config
147            ->withPaths($paths)
148            ->withSkip([...$skipPaths, ...self::DEFAULT_SKIPPED_RULES]);
149    }
150
151    /**
152     * Applies the default ECS sets, root files, and configured rules to an ECS builder.
153     *
154     * @param ECSConfigBuilder $config
155     */
156    public static function applyDefaultRulesAndSets(ECSConfigBuilder $config): ECSConfigBuilder
157    {
158        $config
159            ->withRootFiles()
160            ->withPreparedSets(
161                psr12: self::DEFAULT_PREPARED_SETS['psr12'],
162                common: self::DEFAULT_PREPARED_SETS['common'],
163                cleanCode: self::DEFAULT_PREPARED_SETS['cleanCode'],
164            );
165
166        $config->withSets([__DIR__ . '/../../resources/ecs/php-cs-fixer.php']);
167
168        foreach (self::DEFAULT_CONFIGURED_RULES as $rule => $configuration) {
169            $config->withConfiguredRule($rule, $configuration);
170        }
171
172        return $config;
173    }
174
175    /**
176     * Registers PHP-CS-Fixer presets and the legacy Symplify set without deprecated ECS APIs.
177     *
178     * @param ECSConfigInterface $config
179     */
180    public static function applyPhpCsFixerSets(ECSConfigInterface $config): void
181    {
182        $ruleSet = new RuleSet([
183            '@Symfony' => self::DEFAULT_PHP_CS_FIXER_SETS['symfony'],
184            '@Symfony:risky' => self::DEFAULT_PHP_CS_FIXER_SETS['symfonyRisky'],
185            '@auto' => self::DEFAULT_PHP_CS_FIXER_SETS['auto'],
186            '@auto:risky' => self::DEFAULT_PHP_CS_FIXER_SETS['autoRisky'],
187        ]);
188        $fixerFactory = new FixerFactory();
189        $fixerFactory->registerBuiltInFixers();
190        $fixerFactory->useRuleSet($ruleSet);
191        foreach ($fixerFactory->getFixers() as $fixer) {
192            $configuration = $ruleSet->getRuleConfiguration($fixer->getName());
193
194            if (null === $configuration) {
195                $config->rule($fixer::class);
196            } else {
197                $config->ruleWithConfiguration($fixer::class, $configuration);
198            }
199        }
200
201        if (self::DEFAULT_PREPARED_SETS['symplify']) {
202            $config->rules([
203                RemovePHPStormAnnotationFixer::class,
204                ParamReturnAndVarTagMalformsFixer::class,
205                RemoveUselessDefaultCommentFixer::class,
206                RemoveMethodNameDuplicateDescriptionFixer::class,
207                RemovePropertyVariableNameDescriptionFixer::class,
208                ArrayListItemNewlineFixer::class,
209                ArrayOpenerAndCloserNewlineFixer::class,
210                StandaloneLinePromotedPropertyFixer::class,
211                MethodChainingNewlineFixer::class,
212                SpaceAfterCommaHereNowDocFixer::class,
213                BlankLineAfterStrictTypesFixer::class,
214                LineLengthFixer::class,
215            ]);
216            $config->ruleWithConfiguration(GeneralPhpdocAnnotationRemoveFixer::class, [
217                'annotations' => ['throws', 'author', 'package', 'group', 'covers', 'category'],
218            ]);
219        }
220    }
221}