Completed
Push — add/changelog-tooling ( 86359e...64ad4d )
by
unknown
40:44 queued 31:04
created

WordpressVersioning::parseVersion()   B

Complexity

Conditions 7
Paths 33

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
nc 33
nop 1
dl 0
loc 11
rs 8.8333
c 0
b 0
f 0
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
	 * Determine the next version given a current version and a set of changes.
98
	 *
99
	 * @param string        $version Current version.
100
	 * @param ChangeEntry[] $changes Changes.
101
	 * @param array         $extra Extra components for the version.
102
	 * @return string
103
	 * @throws InvalidArgumentException If the version number is not in a recognized format, or other arguments are invalid.
104
	 */
105
	public function nextVersion( $version, array $changes, array $extra = array() ) {
106
		$info = $this->parseVersion( $version );
107
108 View Code Duplication
		if ( isset( $extra['prerelease'] ) ) {
109
			try {
110
				$info['prerelease'] = $this->parseVersion( '0.0-' . $extra['prerelease'] )['prerelease'];
111
			} catch ( InvalidArgumentException $ex ) {
112
				throw new InvalidArgumentException( 'Invalid prerelease data' );
113
			}
114
		} else {
115
			$info['prerelease'] = null;
116
		}
117 View Code Duplication
		if ( isset( $extra['buildinfo'] ) ) {
118
			try {
119
				$info['buildinfo'] = $this->parseVersion( '0.0+' . $extra['buildinfo'] )['buildinfo'];
120
			} catch ( InvalidArgumentException $ex ) {
121
				throw new InvalidArgumentException( 'Invalid buildinfo data' );
122
			}
123
		} else {
124
			$info['buildinfo'] = null;
125
		}
126
127
		if ( $this->input->getOption( 'point-release' ) ) {
128
			$info['point']++;
129
		} else {
130
			$info['point']  = 0;
131
			$info['major'] += 0.1;
132
		}
133
134
		return $this->normalizeVersion( $info );
0 ignored issues
show
Documentation introduced by
$info is of type array<string,double|integer|string|null>, 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...
135
	}
136
137
	/**
138
	 * Extract the index and count from a prerelease string.
139
	 *
140
	 * @param string|null $s String.
141
	 * @return array Two elements: index and count.
142
	 * @throws InvalidArgumentException If the string is invalid.
143
	 */
144
	private function parsePrerelease( $s ) {
145
		if ( null === $s ) {
146
			return array( 100, 0 );
147
		}
148
		foreach ( array( 'dev', 'alpha(\d*)', 'beta(\d*)', 'rc(\d*)' ) as $i => $re ) {
149
			if ( preg_match( "/^{$re}\$/", $s, $m ) ) {
150
				return array( $i, isset( $m[1] ) ? (int) $m[1] : 0 );
151
			}
152
		}
153
		throw new InvalidArgumentException( "Invalid prerelease string \"$s\"" ); // @codeCoverageIgnore
154
	}
155
156
	/**
157
	 * Compare two version numbers.
158
	 *
159
	 * @param string $a First version.
160
	 * @param string $b Second version.
161
	 * @return int Less than, equal to, or greater than 0 depending on whether `$a` is less than, equal to, or greater than `$b`.
162
	 * @throws InvalidArgumentException If the version numbers are not in a recognized format.
163
	 */
164
	public function compareVersions( $a, $b ) {
165
		$aa = $this->parseVersion( $a );
166
		$bb = $this->parseVersion( $b );
167
		if ( $aa['major'] !== $bb['major'] ) {
168
			return $aa['major'] < $bb['major'] ? -1 : 1;
169
170
		}
171 View Code Duplication
		if ( $aa['point'] !== $bb['point'] ) {
172
			return $aa['point'] - $bb['point'];
173
		}
174
175
		list( $aindex, $acount ) = $this->parsePrerelease( $aa['prerelease'] );
176
		list( $bindex, $bcount ) = $this->parsePrerelease( $bb['prerelease'] );
177
		if ( $aindex !== $bindex ) {
178
			return $aindex - $bindex;
179
		}
180
		return $acount - $bcount;
181
	}
182
183
}
184