Completed
Push — renovate/pin-dependencies ( cad925...7b5606 )
by
unknown
39:51 queued 29:52
created

WordpressVersioning   A

Complexity

Total Complexity 32

Size/Duplication

Total Lines 191
Duplicated Lines 20.42 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 39
loc 191
rs 9.84
c 0
b 0
f 0
wmc 32
lcom 1
cbo 1

8 Methods

Rating   Name   Duplication   Size   Complexity  
A getOptions() 0 5 1
B parseVersion() 0 11 7
B normalizeVersion() 12 27 6
A validateExtra() 24 24 5
A nextVersion() 0 15 2
A parsePrerelease() 0 11 5
A compareVersions() 3 18 5
A firstVersion() 0 8 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php // phpcs:ignore WordPress.Files.FileName.NotHyphenatedLowercase
2
/**
3
 * WordPress versioning plugin.
4
 *
5
 * @package automattic/jetpack-changelogger
6
 */
7
8
namespace Automattic\Jetpack\Changelogger\Plugins;
9
10
use Automattic\Jetpack\Changelogger\PluginTrait;
11
use Automattic\Jetpack\Changelogger\VersioningPlugin;
12
use InvalidArgumentException;
13
use Symfony\Component\Console\Input\InputOption;
14
15
/**
16
 * WordPress versioning plugin.
17
 *
18
 * - Major versions are in the form of a decimal number with tenths, e.g "9.4".
19
 * - Point releases add another number after a dot.
20
 * - Prerelease versions add a suffix: "-dev", "-alpha", "-beta", or "-rc",
21
 *   with an optional number after all except "-dev".
22
 * - Buildinfo adds any other `[a-zA-Z0-9.-]` after a '+' suffix.
23
 */
24
class WordpressVersioning implements VersioningPlugin {
25
	use PluginTrait;
26
27
	/**
28
	 * Define any command line options the versioning plugin wants to accept.
29
	 *
30
	 * @return InputOption[]
31
	 */
32
	public function getOptions() {
33
		return array(
34
			new InputOption( 'point-release', null, InputOption::VALUE_NONE, 'Do a point release' ),
35
		);
36
	}
37
38
	/**
39
	 * Parse a WordPress-style version.
40
	 *
41
	 * @param string $version Version.
42
	 * @return array With components:
43
	 *  - major: (float) Major version.
44
	 *  - point: (int) Point version.
45
	 *  - prerelease: (string|null) Pre-release string.
46
	 *  - buildinfo: (string|null) Build metadata string.
47
	 * @throws InvalidArgumentException If the version number is not in a recognized format.
48
	 */
49
	private function parseVersion( $version ) {
50
		if ( ! preg_match( '/^(?P<major>\d+\.\d)(?:\.(?P<point>\d+))?(?:-(?P<prerelease>dev|(?:alpha|beta|rc)\d*))?(?:\+(?P<buildinfo>[0-9a-zA-Z.-]+))?$/', $version, $m ) ) {
51
			throw new InvalidArgumentException( "Version number \"$version\" is not in a recognized format." );
52
		}
53
		return array(
54
			'major'      => (float) $m['major'],
55
			'point'      => (int) ( isset( $m['point'] ) ? $m['point'] : null ),
56
			'prerelease' => isset( $m['prerelease'] ) && '' !== $m['prerelease'] ? $m['prerelease'] : null,
57
			'buildinfo'  => isset( $m['buildinfo'] ) && '' !== $m['buildinfo'] ? $m['buildinfo'] : null,
58
		);
59
	}
60
61
	/**
62
	 * Check and normalize a version number.
63
	 *
64
	 * @param string $version Version string.
65
	 * @return string Normalized version.
66
	 * @throws InvalidArgumentException If the version number is not in a recognized format.
67
	 */
68
	public function normalizeVersion( $version ) {
69
		// The ability to pass an array is an internal-only feature.
70 View Code Duplication
		if ( is_array( $version ) ) {
71
			$info = $version + array(
72
				'prerelease' => null,
73
				'buildinfo'  => null,
74
			);
75
			$test = $this->parseVersion( '0.0' );
76
			if ( array_intersect_key( $test, $info ) !== $test ) {
77
				throw new InvalidArgumentException( 'Version array is not in a recognized format.' );
78
			}
79
		} else {
80
			$info = $this->parseVersion( $version );
81
		}
82
83
		$ret = sprintf( '%.1f', $info['major'] );
84
		if ( 0 !== $info['point'] ) {
85
			$ret .= '.' . $info['point'];
86
		}
87
		if ( null !== $info['prerelease'] ) {
88
			$ret .= '-' . $info['prerelease'];
89
		}
90
		if ( null !== $info['buildinfo'] ) {
91
			$ret .= '+' . $info['buildinfo'];
92
		}
93
		return $ret;
94
	}
95
96
	/**
97
	 * Validate an `$extra` array.
98
	 *
99
	 * @param array $extra Extra components for the version. See `nextVersion()`.
100
	 * @return array
101
	 * @throws InvalidArgumentException If the `$extra` data is invalid.
102
	 */
103 View Code Duplication
	private function validateExtra( array $extra ) {
104
		$info = array();
105
106
		if ( isset( $extra['prerelease'] ) ) {
107
			try {
108
				$info['prerelease'] = $this->parseVersion( '0.0-' . $extra['prerelease'] )['prerelease'];
109
			} catch ( InvalidArgumentException $ex ) {
110
				throw new InvalidArgumentException( 'Invalid prerelease data' );
111
			}
112
		} else {
113
			$info['prerelease'] = null;
114
		}
115
		if ( isset( $extra['buildinfo'] ) ) {
116
			try {
117
				$info['buildinfo'] = $this->parseVersion( '0.0+' . $extra['buildinfo'] )['buildinfo'];
118
			} catch ( InvalidArgumentException $ex ) {
119
				throw new InvalidArgumentException( 'Invalid buildinfo data' );
120
			}
121
		} else {
122
			$info['buildinfo'] = null;
123
		}
124
125
		return $info;
126
	}
127
128
	/**
129
	 * Determine the next version given a current version and a set of changes.
130
	 *
131
	 * @param string        $version Current version.
132
	 * @param ChangeEntry[] $changes Changes.
133
	 * @param array         $extra Extra components for the version.
134
	 * @return string
135
	 * @throws InvalidArgumentException If the version number is not in a recognized format, or other arguments are invalid.
136
	 */
137
	public function nextVersion( $version, array $changes, array $extra = array() ) {
138
		$info = array_merge(
139
			$this->parseVersion( $version ),
140
			$this->validateExtra( $extra )
141
		);
142
143
		if ( $this->input->getOption( 'point-release' ) ) {
144
			$info['point']++;
145
		} else {
146
			$info['point']  = 0;
147
			$info['major'] += 0.1;
148
		}
149
150
		return $this->normalizeVersion( $info );
0 ignored issues
show
Documentation introduced by
$info is of type array<string,double|inte...ull,{"major":"double"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
151
	}
152
153
	/**
154
	 * Extract the index and count from a prerelease string.
155
	 *
156
	 * @param string|null $s String.
157
	 * @return array Two elements: index and count.
158
	 * @throws InvalidArgumentException If the string is invalid.
159
	 */
160
	private function parsePrerelease( $s ) {
161
		if ( null === $s ) {
162
			return array( 100, 0 );
163
		}
164
		foreach ( array( 'dev', 'alpha(\d*)', 'beta(\d*)', 'rc(\d*)' ) as $i => $re ) {
165
			if ( preg_match( "/^{$re}\$/", $s, $m ) ) {
166
				return array( $i, isset( $m[1] ) ? (int) $m[1] : 0 );
167
			}
168
		}
169
		throw new InvalidArgumentException( "Invalid prerelease string \"$s\"" ); // @codeCoverageIgnore
170
	}
171
172
	/**
173
	 * Compare two version numbers.
174
	 *
175
	 * @param string $a First version.
176
	 * @param string $b Second version.
177
	 * @return int Less than, equal to, or greater than 0 depending on whether `$a` is less than, equal to, or greater than `$b`.
178
	 * @throws InvalidArgumentException If the version numbers are not in a recognized format.
179
	 */
180
	public function compareVersions( $a, $b ) {
181
		$aa = $this->parseVersion( $a );
182
		$bb = $this->parseVersion( $b );
183
		if ( $aa['major'] !== $bb['major'] ) {
184
			return $aa['major'] < $bb['major'] ? -1 : 1;
185
186
		}
187 View Code Duplication
		if ( $aa['point'] !== $bb['point'] ) {
188
			return $aa['point'] - $bb['point'];
189
		}
190
191
		list( $aindex, $acount ) = $this->parsePrerelease( $aa['prerelease'] );
192
		list( $bindex, $bcount ) = $this->parsePrerelease( $bb['prerelease'] );
193
		if ( $aindex !== $bindex ) {
194
			return $aindex - $bindex;
195
		}
196
		return $acount - $bcount;
197
	}
198
199
	/**
200
	 * Return a valid "first" version number.
201
	 *
202
	 * @param array $extra Extra components for the version, as for `nextVersion()`.
203
	 * @return string
204
	 */
205
	public function firstVersion( array $extra = array() ) {
206
		return $this->normalizeVersion(
207
			array(
0 ignored issues
show
Documentation introduced by
array('major' => 0.0, 'p...->validateExtra($extra) is of type array<string,?>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
208
				'major' => 0.0,
209
				'point' => 0,
210
			) + $this->validateExtra( $extra )
211
		);
212
	}
213
214
}
215