Completed
Push — add/changelog-tooling ( 3f93f3...c37726 )
by
unknown
250:15 queued 241:25
created

ValidateCommand   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 178
Duplicated Lines 5.06 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
dl 9
loc 178
rs 10
c 0
b 0
f 0
wmc 30
lcom 1
cbo 2

4 Methods

Rating   Name   Duplication   Size   Complexity  
A configure() 0 11 1
A msg() 0 19 4
F validateFile() 9 63 17
B execute() 0 28 8

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
 * "Validate" command for the changelogger tool CLI.
4
 *
5
 * @package automattic/jetpack-changelogger
6
 */
7
8
// phpcs:disable WordPress.NamingConventions.ValidVariableName
9
10
namespace Automattic\Jetpack\Changelogger\Console;
11
12
use Automattic\Jetpack\Changelogger\Config;
13
use Automattic\Jetpack\Changelogger\Utils;
14
use Symfony\Component\Console\Command\Command;
15
use Symfony\Component\Console\Input\InputArgument;
16
use Symfony\Component\Console\Input\InputInterface;
17
use Symfony\Component\Console\Input\InputOption;
18
use Symfony\Component\Console\Output\OutputInterface;
19
20
/**
21
 * "Validate" command for the changelogger tool CLI.
22
 */
23
class ValidateCommand extends Command {
24
25
	/**
26
	 * The default command name
27
	 *
28
	 * @var string|null
29
	 */
30
	protected static $defaultName = 'validate';
31
32
	/**
33
	 * The InputInterface to use.
34
	 *
35
	 * @var InputInterface|null
36
	 */
37
	private $input;
38
39
	/**
40
	 * The OutputInterface to use.
41
	 *
42
	 * @var OutputInterface|null
43
	 */
44
	private $output;
45
46
	/**
47
	 * Counts of errors and warnings output.
48
	 *
49
	 * @var int[]
50
	 */
51
	private $counts;
52
53
	/**
54
	 * Configures the command.
55
	 */
56
	protected function configure() {
57
		$this->setDescription( 'Validates changelog entry files' )
58
			->addOption( 'gh-action', null, InputOption::VALUE_NONE, 'Output validation issues using GitHub Action command syntax.' )
59
			->addOption( 'no-strict', null, InputOption::VALUE_NONE, 'Do not exit with a failure code if only warnings are found.' )
60
			->addArgument( 'files', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Files to check. By default, all change files in the changelog directory are checked.' )
61
			->setHelp(
62
				<<<EOF
63
The <info>validate</info> command validates change files.
64
EOF
65
			);
66
	}
67
68
	/**
69
	 * Output an error or warning.
70
	 *
71
	 * @param string   $type 'error' or 'warning'.
72
	 * @param string   $file Filename with the error/warning.
73
	 * @param int|null $line Line number of the error/warning.
74
	 * @param string   $msg Error message.
75
	 */
76
	private function msg( $type, $file, $line, $msg ) {
77
		if ( $this->input->getOption( 'gh-action' ) ) {
78
			$prefix = "::$type file=$file";
79
			if ( null !== $line ) {
80
				$prefix .= ",line=$line";
81
			}
82
			$prefix .= '::';
83
			$postfix = '';
84
		} else {
85
			$prefix = "<$type>$file";
86
			if ( null !== $line ) {
87
				$prefix .= ":$line";
88
			}
89
			$prefix .= ': ';
90
			$postfix = '</>';
91
		}
92
		$this->output->writeln( $prefix . $msg . $postfix );
93
		$this->counts[ $type ]++;
94
	}
95
96
	/**
97
	 * Validate a file.
98
	 *
99
	 * @param string $filename Filename.
100
	 */
101
	public function validateFile( $filename ) {
102
		try {
103
			$diagnostics = null; // Make phpcs happy.
104
			$data        = Utils::loadChangeFile( $filename, $diagnostics );
105
		} catch ( \RuntimeException $ex ) {
106
			$this->msg( 'error', $filename, $ex->fileLine, $ex->getMessage() );
0 ignored issues
show
Bug introduced by
The property fileLine does not seem to exist in RuntimeException.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
107
			return false;
108
		}
109
110
		$messages = array();
111
112
		foreach ( $diagnostics['warnings'] as list( $msg, $line ) ) {
113
			$messages[] = array( 'warning', $msg, $line );
114
		}
115
116
		foreach ( $diagnostics['lines'] as $header => $line ) {
117
			if ( ! in_array( $header, array( 'Significance', 'Type', 'Comment', '' ), true ) ) {
118
				$messages[] = array( 'warning', "Unrecognized header \"$header\".", $line );
119
			}
120
		}
121
122
		if ( ! isset( $data['Significance'] ) ) {
123
			$messages[] = array( 'error', 'File does not contain a Significance header.', null );
124
		} elseif ( ! in_array( $data['Significance'], array( 'patch', 'minor', 'major' ), true ) ) {
125
			$messages[] = array( 'error', 'Significance must be "patch", "minor", or "major".', $diagnostics['lines']['Significance'] );
126
		} elseif ( 'patch' !== $data['Significance'] && '' === $data[''] ) {
127
			$messages[] = array( 'error', 'Changelog entry may only be empty when Significance is "patch".', $diagnostics['lines'][''] );
128
		}
129
130
		$types = Config::types();
131
		if ( $types ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $types of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
132
			if ( ! isset( $data['Type'] ) ) {
133
				$messages[] = array( 'error', 'File does not contain a Type header.', null );
134
			} elseif ( ! isset( $types[ $data['Type'] ] ) ) {
135
				$list = array_map(
136
					function ( $v ) {
137
						return "\"$v\"";
138
					},
139
					array_keys( $types )
140
				);
141 View Code Duplication
				if ( count( $list ) > 1 ) {
142
					$list[ count( $list ) - 1 ] = 'or ' . $list[ count( $list ) - 1 ];
143
				}
144
				$messages[] = array( 'error', 'Type must be must be ' . implode( count( $list ) > 2 ? ', ' : ' ', $list ) . '.', $diagnostics['lines']['Type'] );
145
			}
146
		}
147
148
		usort(
149
			$messages,
150
			function ( $a, $b ) {
151 View Code Duplication
				if ( $a[2] !== $b[2] ) {
152
					return $a[2] - $b[2];
153
				}
154 View Code Duplication
				if ( $a[0] !== $b[0] ) {
155
					return strcmp( $a[0], $b[0] );
156
				}
157
				return strcmp( $a[1], $b[1] );
158
			}
159
		);
160
		foreach ( $messages as list( $type, $msg, $line ) ) {
161
			$this->msg( $type, $filename, $line, $msg );
162
		}
163
	}
164
165
	/**
166
	 * Executes the command.
167
	 *
168
	 * @param InputInterface  $input InputInterface.
169
	 * @param OutputInterface $output OutputInterface.
170
	 * @return int 0 if everything went fine, or an exit code.
171
	 */
172
	protected function execute( InputInterface $input, OutputInterface $output ) {
173
		$this->input  = $input;
174
		$this->output = $output;
175
		$this->counts = array(
0 ignored issues
show
Documentation Bug introduced by
It seems like array('error' => 0, 'warning' => 0) of type array<string,integer,{"e...","warning":"integer"}> is incompatible with the declared type array<integer,integer> of property $counts.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
176
			'error'   => 0,
177
			'warning' => 0,
178
		);
179
180
		$files = $input->getArgument( 'files' );
181
		if ( ! $files ) {
182
			$files = array();
183
			foreach ( new \DirectoryIterator( Config::changelogDir() ) as $file ) {
184
				$name = $file->getBasename();
185
				if ( '.' !== $name[0] ) {
186
					$files[] = $file->getPathname();
187
				}
188
			}
189
			sort( $files );
190
		}
191
192
		foreach ( $files as $filename ) {
193
			$output->writeln( "Checking $filename...", OutputInterface::VERBOSITY_VERBOSE );
194
			$this->validateFile( $filename );
195
		}
196
197
		$output->writeln( sprintf( 'Found %d error(s) and %d warning(s)', $this->counts['error'], $this->counts['warning'] ), OutputInterface::VERBOSITY_VERBOSE );
198
		return $this->counts['error'] || $this->counts['warning'] && ! $input->getOption( 'no-strict' ) ? 1 : 0;
199
	}
200
}
201