Completed
Pull Request — master (#33)
by Sullivan
09:09 queued 06:59
created

ConfigBridge::getLevel()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 16
rs 9.4285
cc 2
eloc 10
nc 2
nop 0
1
<?php
2
3
namespace SLLH\StyleCIBridge;
4
5
use Composer\Semver\Semver;
6
use PhpCsFixer\Config;
7
use PhpCsFixer\Console\Application;
8
use PhpCsFixer\Finder;
9
use SLLH\StyleCIBridge\StyleCI\Configuration;
10
use SLLH\StyleCIFixers\Fixers;
11
use Symfony\Component\Config\Definition\Processor;
12
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
13
use Symfony\Component\Console\Output\ConsoleOutput;
14
use Symfony\Component\Console\Output\OutputInterface;
15
use Symfony\Component\DependencyInjection\Container;
16
use Symfony\Component\Yaml\Yaml;
17
use Symfony\CS\Fixer;
18
use Symfony\CS\Fixer\Contrib\HeaderCommentFixer;
19
use Symfony\CS\FixerFactory;
20
use Symfony\CS\FixerInterface;
21
22
/**
23
 * @author Sullivan Senechal <[email protected]>
24
 */
25
final class ConfigBridge
26
{
27
    const CS_FIXER_MIN_VERSION = '1.6.1';
28
29
    /**
30
     * @var OutputInterface
31
     */
32
    private $output;
33
34
    /**
35
     * @var FixerFactory
36
     */
37
    private $fixerFactory = null;
38
39
    /**
40
     * @var string
41
     */
42
    private $styleCIConfigDir;
43
44
    /**
45
     * @var array|null
46
     */
47
    private $styleCIConfig = null;
48
49
    /**
50
     * @var string|array
51
     */
52
    private $finderDirs;
53
54
    /**
55
     * @param string|null       $styleCIConfigDir StyleCI config directory. Called script dir as default.
56
     * @param string|array|null $finderDirs       A directory path or an array of directories for Finder. Called script dir as default.
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 135 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
57
     */
58
    public function __construct($styleCIConfigDir = null, $finderDirs = null)
59
    {
60
        if (!Semver::satisfies(
61
            class_exists('Symfony\CS\Fixer') ? Fixer::VERSION : Application::VERSION, // PHP-CS-Fixer 1.x BC
62
            sprintf('>=%s', self::CS_FIXER_MIN_VERSION)
63
        )) {
64
            throw new \RuntimeException(sprintf(
65
                'PHP-CS-Fixer v%s is not supported, please upgrade to v%s or higher.',
66
                Fixer::VERSION,
67
                self::CS_FIXER_MIN_VERSION
68
            ));
69
        }
70
71
        // Guess config files path if not specified.
72
        // getcwd function is not enough. See: https://github.com/Soullivaneuh/php-cs-fixer-styleci-bridge/issues/46
73
        if (null === $styleCIConfigDir || null === $finderDirs) {
74
            $dbt = version_compare(PHP_VERSION, '5.4.0', '>=') ? debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2) : debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 160 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
75
76
            // Static call
77
            if (isset($dbt[1]['class']) && 'SLLH\StyleCIBridge\ConfigBridge' === $dbt[1]['class'] && 'create' === $dbt[1]['function']) {
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 136 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
78
                $configsPath = dirname($dbt[1]['file']);
79
            } elseif (isset($dbt[0]['class']) && 'SLLH\StyleCIBridge\ConfigBridge' === $dbt[0]['class'] && '__construct' === $dbt[0]['function']) { // Manual instance
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 166 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
80
                $configsPath = dirname($dbt[0]['file']);
81
            } else { // If no case found, fallback to not reliable getcwd method.
82
                $configsPath = getcwd();
83
            }
84
85
            $this->styleCIConfigDir = $styleCIConfigDir ?: $configsPath;
86
            $this->finderDirs = $finderDirs ?: $configsPath;
87
        }
88
89
        $this->output = new ConsoleOutput();
90
        $this->output->getFormatter()->setStyle('warning', new OutputFormatterStyle('black', 'yellow'));
91
        // PHP-CS-Fixer 1.x BC
92
        if (class_exists('Symfony\CS\FixerFactory')) {
93
            $this->fixerFactory = FixerFactory::create();
94
            $this->fixerFactory->registerBuiltInFixers();
95
        }
96
97
        $this->parseStyleCIConfig();
98
    }
99
100
    /**
101
     * @param string       $styleCIConfigDir
0 ignored issues
show
Documentation introduced by
Should the type for parameter $styleCIConfigDir not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
102
     * @param string|array $finderDirs       A directory path or an array of directories for Finder
0 ignored issues
show
Documentation introduced by
Should the type for parameter $finderDirs not be string|array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
103
     *
104
     * @return Config
105
     */
106
    public static function create($styleCIConfigDir = null, $finderDirs = null)
107
    {
108
        $bridge = new static($styleCIConfigDir, $finderDirs);
109
110
        // PHP-CS-Fixer 1.x BC
111
        if (class_exists('\Symfony\CS\Config\Config')) {
112
            $config = \Symfony\CS\Config\Config::create();
113
        } else {
114
            $config = Config::create();
115
        }
116
117
        // PHP-CS-Fixer 1.x BC
118
        if (method_exists($config, 'level')) {
119
            $config->level(FixerInterface::NONE_LEVEL);
120
        }
121
122
        if (method_exists($config, 'setRules')) {
123
            $config->setRules($bridge->getRules());
124
        } else { // PHP-CS-Fixer 1.x BC
125
            $config->fixers($bridge->getFixers());
126
        }
127
128
        // PHP-CS-Fixer 1.x BC
129
        if (method_exists($config, 'setRiskyAllowed')) {
130
            $config->setRiskyAllowed($bridge->getRisky());
131
        }
132
133
        return $config
134
            ->finder($bridge->getFinder())
135
        ;
136
    }
137
138
    /**
139
     * @return Finder|\Symfony\CS\Finder\DefaultFinder
140
     */
141
    public function getFinder()
142
    {
143
        // PHP-CS-Fixer 1.x BC
144
        if (class_exists('\Symfony\CS\Finder\DefaultFinder')) {
145
            $finder = \Symfony\CS\Finder\DefaultFinder::create()->in($this->finderDirs);
146
        } else {
147
            $finder = Finder::create()->in($this->finderDirs);
148
        }
149
        if (isset($this->styleCIConfig['finder'])) {
150
            $finderConfig = $this->styleCIConfig['finder'];
151
            foreach ($finderConfig as $key => $values) {
152
                $finderMethod = Container::camelize($key);
153
                foreach ($values as $value) {
154
                    if (method_exists($finder, $finderMethod)) {
155
                        $finder->$finderMethod($value);
156
                    } else {
157
                        $this->output->writeln(sprintf(
158
                            '<warning>Can not apply "%s" finder option with PHP-CS-Fixer v%s. You fixer config may be erroneous. Consider upgrading to fix it.</warning>',
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 170 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
159
                            str_replace('_', '-', $key),
160
                            Fixer::VERSION
161
                        ));
162
                    }
163
                }
164
            }
165
        }
166
167
        return $finder;
168
    }
169
170
    /**
171
     * @return string[]
172
     */
173
    public function getFixers()
174
    {
175
        $presetFixers = $this->resolveAliases($this->getPresetFixers());
176
        $enabledFixers = $this->resolveAliases($this->styleCIConfig['enabled']);
177
        $disabledFixers = $this->resolveAliases($this->styleCIConfig['disabled']);
178
179
        $fixers = array_merge(
180
            $enabledFixers,
181
            array_map(function ($disabledFixer) {
182
                return '-'.$disabledFixer;
183
            }, $disabledFixers),
184
            array_diff($presetFixers, $disabledFixers) // Remove disabled fixers from preset
185
        );
186
187
        // PHP-CS-Fixer 1.x BC
188
        if (method_exists('Symfony\CS\Fixer\Contrib\HeaderCommentFixer', 'getHeader') && HeaderCommentFixer::getHeader()) {
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 123 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
189
            array_push($fixers, 'header_comment');
190
        }
191
192
        return $fixers;
193
    }
194
195
    /**
196
     * Returns fixers converted to rules for PHP-CS-Fixer 2.x.
197
     *
198
     * @return array
199
     */
200
    public function getRules()
201
    {
202
        $fixers = $this->getFixers();
203
204
        $rules = array();
205
        foreach ($fixers as $fixer) {
206
            if ('-' === $fixer[0]) {
207
                $name = substr($fixer, 1);
208
                $enabled = false;
209
            } else {
210
                $name = $fixer;
211
                $enabled = true;
212
            }
213
214
            if ($this->isFixerAvailable($name)) {
215
                $rules[$name] = $enabled;
216
            } else {
217
                $this->output->writeln(sprintf('<warning>Fixer "%s" does not exist, skipping.</warning>', $name));
218
            }
219
        }
220
221
        return $rules;
222
    }
223
224
    /**
225
     * @return bool
226
     */
227
    public function getRisky()
0 ignored issues
show
Coding Style introduced by
function getRisky() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
228
    {
229
        return $this->styleCIConfig['risky'];
230
    }
231
232
    /**
233
     * @return string[]
234
     */
235
    private function getPresetFixers()
236
    {
237
        $preset = $this->styleCIConfig['preset'];
238
        $validPresets = Fixers::getPresets();
239
240
        return $validPresets[$preset];
241
    }
242
243
    /**
244
     * Adds both aliases and real fixers if set. PHP-CS-Fixer would not take care if not existing.
245
     * Better compatibility between PHP-CS-Fixer 1.x and 2.x.
246
     *
247
     * @param string[] $fixers
248
     *
249
     * @return string[]
250
     */
251
    private function resolveAliases(array $fixers)
252
    {
253
        foreach (Fixers::$aliases as $alias => $name) {
254 View Code Duplication
            if (in_array($alias, $fixers, true) && !in_array($name, $fixers, true) && $this->isFixerAvailable($name)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
255
                array_push($fixers, $name);
256
            }
257 View Code Duplication
            if (in_array($name, $fixers, true) && !in_array($alias, $fixers, true) && $this->isFixerAvailable($alias)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
258
                array_push($fixers, $alias);
259
            }
260
        }
261
262
        return $fixers;
263
    }
264
265
    /**
266
     * @param string $name
267
     *
268
     * @return bool
269
     */
270
    private function isFixerAvailable($name)
271
    {
272
        // PHP-CS-Fixer 1.x BC
273
        if (null === $this->fixerFactory) {
274
            return true;
275
        }
276
277
        return $this->fixerFactory->hasRule($name);
278
    }
279
280
    private function parseStyleCIConfig()
281
    {
282
        if (null === $this->styleCIConfig) {
283
            $config = Yaml::parse(file_get_contents(sprintf('%s/.styleci.yml', $this->styleCIConfigDir)));
284
            $processor = new Processor();
285
            $this->styleCIConfig = $processor->processConfiguration(new Configuration(), array('styleci' => $config));
286
        }
287
    }
288
}
289