Linter::validate()   F
last analyzed

Complexity

Conditions 22
Paths 208

Size

Total Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 44
rs 3.2333
c 0
b 0
f 0
cc 22
nc 208
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace SLLH\ComposerLint;
4
5
/**
6
 * @author Sullivan Senechal <[email protected]>
7
 */
8
final class Linter
9
{
10
    /**
11
     * @var array
12
     */
13
    private $config;
14
15
    public function __construct(array $config)
16
    {
17
        $defaultConfig = array(
18
            'php' => true,
19
            'type' => true,
20
            'minimum-stability' => true,
21
            'version-constraints' => true,
22
        );
23
24
        $this->config = array_merge($defaultConfig, $config);
25
    }
26
27
    /**
28
     * @param array $manifest composer.json file manifest
29
     *
30
     * @return string[]
31
     */
32
    public function validate($manifest)
33
    {
34
        $errors = array();
35
        $linksSections = array('require', 'require-dev', 'conflict', 'replace', 'provide', 'suggest');
36
37
        if (isset($manifest['config']['sort-packages']) && $manifest['config']['sort-packages']) {
38
            foreach ($linksSections as $linksSection) {
39
                if (\array_key_exists($linksSection, $manifest) && !$this->packagesAreSorted($manifest[$linksSection])) {
40
                    array_push($errors, 'Links under '.$linksSection.' section are not sorted.');
41
                }
42
            }
43
        }
44
45
        if (true === $this->config['php'] &&
46
            (\array_key_exists('require-dev', $manifest) || \array_key_exists('require', $manifest))) {
47
            $isOnRequireDev = \array_key_exists('require-dev', $manifest) && \array_key_exists('php', $manifest['require-dev']);
48
            $isOnRequire = \array_key_exists('require', $manifest) && \array_key_exists('php', $manifest['require']);
49
50
            if ($isOnRequireDev) {
51
                array_push($errors, 'PHP requirement should be in the require section, not in the require-dev section.');
52
            } elseif (!$isOnRequire) {
53
                array_push($errors, 'You must specifiy the PHP requirement.');
54
            }
55
        }
56
57
        if (true === $this->config['type'] && !\array_key_exists('type', $manifest)) {
58
            array_push($errors, 'The package type is not specified.');
59
        }
60
61
        if (true === $this->config['minimum-stability'] && \array_key_exists('minimum-stability', $manifest) &&
62
            \array_key_exists('type', $manifest) && 'project' !== $manifest['type']) {
63
            array_push($errors, 'The minimum-stability should be only used for packages of type "project".');
64
        }
65
66
        if (true === $this->config['version-constraints']) {
67
            foreach ($linksSections as $linksSection) {
68
                if (\array_key_exists($linksSection, $manifest)) {
69
                    $errors = array_merge($errors, $this->validateVersionConstraints($manifest[$linksSection]));
70
                }
71
            }
72
        }
73
74
        return $errors;
75
    }
76
77
    private function packagesAreSorted(array $packages)
78
    {
79
        $names = array_keys($packages);
80
81
        $hasPHP = \in_array('php', $names, true);
82
        $extNames = array_filter($names, function ($name) {
83
            return 'ext-' === substr($name, 0, 4) && !strstr($name, '/');
84
        });
85
        sort($extNames);
86
        $vendorName = array_filter($names, function ($name) {
87
            return 'ext-' !== substr($name, 0, 4) && 'php' !== $name;
88
        });
89
        sort($vendorName);
90
91
        $sortedNames = array_merge(
92
            $hasPHP ? array('php') : array(),
93
            $extNames,
94
            $vendorName
95
        );
96
97
        return $sortedNames === $names;
98
    }
99
100
    /**
101
     * @param string[] $packages
102
     *
103
     * @return array
104
     */
105
    private function validateVersionConstraints(array $packages)
106
    {
107
        $errors = array();
108
109
        foreach ($packages as $name => $constraint) {
110
            // Checks if OR format is correct
111
            // From Composer\Semver\VersionParser::parseConstraints
112
            $orConstraints = preg_split('{\s*\|\|?\s*}', trim($constraint));
113
            foreach ($orConstraints as &$subConstraint) {
114
                // Checks ~ usage
115
                $subConstraint = str_replace('~', '^', $subConstraint);
116
117
                // Checks for usage like ^2.1,>=2.1.5. Should be ^2.1.5.
118
                // From Composer\Semver\VersionParser::parseConstraints
119
                $andConstraints = preg_split('{(?<!^|as|[=>< ,]) *(?<!-)[, ](?!-) *(?!,|as|$)}', $subConstraint);
120
                if (2 === \count($andConstraints) && '>=' === substr($andConstraints[1], 0, 2)) {
121
                    $andConstraints[1] = '^'.substr($andConstraints[1], 2);
122
                    array_shift($andConstraints);
123
                    $subConstraint = implode(',', $andConstraints);
124
                }
125
            }
126
127
            $expectedConstraint = implode(' || ', $orConstraints);
128
129
            if ($expectedConstraint !== $constraint) {
130
                array_push($errors, sprintf(
131
                    "Requirement format of '%s:%s' is not valid. Should be '%s'.",
132
                    $name,
133
                    $constraint,
134
                    $expectedConstraint
135
                ));
136
            }
137
        }
138
139
        return $errors;
140
    }
141
}
142