1
|
|
|
<?php |
2
|
|
|
/* |
3
|
|
|
* This file is part of project-quality-inspector. |
4
|
|
|
* |
5
|
|
|
* (c) Alexandre GESLIN <[email protected]> |
6
|
|
|
* |
7
|
|
|
* For the full copyright and license information, please view the LICENSE |
8
|
|
|
* file that was distributed with this source code. |
9
|
|
|
*/ |
10
|
|
|
|
11
|
|
|
namespace ProjectQualityInspector\Rule; |
12
|
|
|
|
13
|
|
|
use ProjectQualityInspector\Exception\ExpectationFailedException; |
14
|
|
|
use Composer\Semver\Semver; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* Class ComposerConfigRule |
18
|
|
|
* |
19
|
|
|
* @package ProjectQualityInspector\Rule |
20
|
|
|
*/ |
21
|
|
|
class ComposerConfigRule extends AbstractRule |
22
|
|
|
{ |
23
|
|
|
public function __construct(array $config, $baseDir) |
24
|
|
|
{ |
25
|
|
|
parent::__construct($config, $baseDir); |
26
|
|
|
} |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* @inheritdoc |
30
|
|
|
*/ |
31
|
|
|
public function evaluate() |
32
|
|
|
{ |
33
|
|
|
$expectationsFailedExceptions = []; |
34
|
|
|
$requirements = $this->getComposerRequirements(); |
35
|
|
|
|
36
|
|
|
foreach ($this->config['packages'] as $package) { |
37
|
|
|
try { |
38
|
|
|
$this->expectsPackagePresence($package, $requirements); |
39
|
|
|
if (isset($package['semver'])) { |
40
|
|
|
$this->expectsPackageSemver($package, $package['semver']); |
41
|
|
|
} |
42
|
|
|
$this->addAssertion($this->getValue($package), [], 2); |
|
|
|
|
43
|
|
|
} catch (ExpectationFailedException $e) { |
44
|
|
|
$expectationsFailedExceptions[] = $e; |
45
|
|
|
$this->addAssertion($this->getValue($package), [['message' => $e->getMessage() . $e->getReason(), 'type' => 'expectsPackagePresence|expectsPackageSemver']]); |
|
|
|
|
46
|
|
|
} |
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
if ($this->config['disallow-wildcard-versioning']) { |
50
|
|
|
foreach ($requirements as $requirement => $version) { |
51
|
|
|
try { |
52
|
|
|
$this->expectsRequirementsHasNoWildCard($requirement, $version); |
53
|
|
|
} catch (ExpectationFailedException $e) { |
54
|
|
|
$expectationsFailedExceptions[] = $e; |
55
|
|
|
} |
56
|
|
|
} |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
if (count($expectationsFailedExceptions)) { |
60
|
|
|
$this->throwRuleViolationException($expectationsFailedExceptions); |
61
|
|
|
} |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* @param string $requirement |
66
|
|
|
* @param string $version |
67
|
|
|
* |
68
|
|
|
* @throws ExpectationFailedException |
69
|
|
|
*/ |
70
|
|
|
protected function expectsRequirementsHasNoWildCard($requirement, $version) |
71
|
|
|
{ |
72
|
|
|
$message = sprintf('Requirement <fg=green>"%s"</> should contains at least major explicit version. Version "%s" is not authorized', $requirement, $version); |
73
|
|
|
|
74
|
|
|
if (!preg_match('/\\d/', $version)) { |
75
|
|
|
throw new ExpectationFailedException($requirement, $message); |
76
|
|
|
} |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* @param string|array $raw |
81
|
|
|
* @param array $requirements |
82
|
|
|
*/ |
83
|
|
|
protected function expectsPackagePresence($raw, $requirements) |
84
|
|
|
{ |
85
|
|
|
$package = $this->getValue($raw); |
86
|
|
|
$reason = $this->getReason($raw); |
87
|
|
|
|
88
|
|
|
if ($package[0] == '!') { |
89
|
|
|
$package = ltrim($package, '!'); |
90
|
|
|
$this->packageShouldNotExists($package, $requirements, $reason); |
|
|
|
|
91
|
|
|
} else { |
92
|
|
|
$this->packageShouldExists($package, $requirements, $reason); |
|
|
|
|
93
|
|
|
} |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* @param array|string $raw |
98
|
|
|
* @param string $semver |
99
|
|
|
* |
100
|
|
|
* @throws ExpectationFailedException |
101
|
|
|
*/ |
102
|
|
|
protected function expectsPackageSemver($raw, $semver) |
103
|
|
|
{ |
104
|
|
|
$package = $this->getValue($raw); |
105
|
|
|
$reason = $this->getReason($raw); |
106
|
|
|
|
107
|
|
|
$composerLock = $this->getComposerLock(); |
108
|
|
|
|
109
|
|
|
if ($composerLock && $key = array_search($package, array_column($composerLock['packages'], 'name'))) { |
110
|
|
|
$cprInstalledVersion = $composerLock['packages'][$key]['version']; |
111
|
|
|
$message = sprintf('Installed package <fg=green>"%s</> <fg=green>%s"</> should satisfies this expected semver <fg=green>"%s"</>', $package, $cprInstalledVersion, $semver); |
112
|
|
|
|
113
|
|
|
if (!Semver::satisfies($this->sanitizeVersion($cprInstalledVersion), $semver)) { |
114
|
|
|
throw new ExpectationFailedException($package, $message, $reason); |
|
|
|
|
115
|
|
|
}; |
116
|
|
|
} |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* @param string $package |
121
|
|
|
* @param array $requirements |
122
|
|
|
* @param string $reason |
123
|
|
|
* |
124
|
|
|
* @throws ExpectationFailedException |
125
|
|
|
*/ |
126
|
|
View Code Duplication |
protected function packageShouldExists($package, $requirements, $reason) |
|
|
|
|
127
|
|
|
{ |
128
|
|
|
$message = sprintf('Package <fg=green>"%s"</> should be installed', $package); |
129
|
|
|
|
130
|
|
|
if (!key_exists($package, $requirements)) { |
131
|
|
|
throw new ExpectationFailedException($package, $message, $reason); |
132
|
|
|
} |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* @param string $package |
137
|
|
|
* @param array $requirements |
138
|
|
|
* @param string $reason |
139
|
|
|
* |
140
|
|
|
* @throws ExpectationFailedException |
141
|
|
|
*/ |
142
|
|
View Code Duplication |
protected function packageShouldNotExists($package, $requirements, $reason) |
|
|
|
|
143
|
|
|
{ |
144
|
|
|
$message = sprintf('Package <fg=green>"%s"</> should not be used', $package); |
145
|
|
|
|
146
|
|
|
if (key_exists($package, $requirements)) { |
147
|
|
|
throw new ExpectationFailedException($package, $message, $reason); |
148
|
|
|
} |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
/** |
152
|
|
|
* @return array |
153
|
|
|
*/ |
154
|
|
|
protected function getComposerRequirements() |
155
|
|
|
{ |
156
|
|
|
$composerConfig = $this->getComposerConfig(); |
157
|
|
|
$requirements = $composerConfig['require']; |
158
|
|
|
|
159
|
|
|
if ($composerConfig['require-dev']) { |
160
|
|
|
$requirements = array_merge($composerConfig['require'], $composerConfig['require-dev']); |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
return $requirements; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* @return array |
168
|
|
|
* |
169
|
|
|
* @throws \InvalidArgumentException |
170
|
|
|
*/ |
171
|
|
|
protected function getComposerConfig() |
172
|
|
|
{ |
173
|
|
|
$configFile = $this->baseDir . DIRECTORY_SEPARATOR . $this->config['file']; |
174
|
|
|
if (!file_exists($configFile)) { |
175
|
|
|
throw new \InvalidArgumentException(sprintf('config file "%s" not found.', $configFile)); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
return json_decode(file_get_contents($configFile), true); |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* @return array|null |
183
|
|
|
*/ |
184
|
|
|
protected function getComposerLock() |
185
|
|
|
{ |
186
|
|
|
$composerLock = $this->baseDir . DIRECTORY_SEPARATOR . str_replace('.json', '.lock', $this->config['file']); |
187
|
|
|
|
188
|
|
|
return (file_exists($composerLock)) ? json_decode(file_get_contents($composerLock), true) : null; |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
/** |
192
|
|
|
* @param string $version |
193
|
|
|
* |
194
|
|
|
* @return mixed |
195
|
|
|
*/ |
196
|
|
|
protected function sanitizeVersion($version) |
197
|
|
|
{ |
198
|
|
|
return str_replace('v', '', $version); |
199
|
|
|
} |
200
|
|
|
} |
This check looks at variables that are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.