Completed
Push — renovate/css-loader-5.x ( 70942c...c43e3d )
by
unknown
124:26 queued 114:47
created

ValidateCommand   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 198
Duplicated Lines 7.58 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
dl 15
loc 198
rs 9.92
c 0
b 0
f 0
wmc 31
lcom 1
cbo 2

4 Methods

Rating   Name   Duplication   Size   Complexity  
A configure() 0 12 1
A msg() 0 20 4
F validateFile() 9 65 17
B execute() 6 37 9

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;
11
12
use Symfony\Component\Console\Command\Command;
13
use Symfony\Component\Console\Input\InputArgument;
14
use Symfony\Component\Console\Input\InputInterface;
15
use Symfony\Component\Console\Input\InputOption;
16
use Symfony\Component\Console\Output\OutputInterface;
17
18
/**
19
 * "Validate" command for the changelogger tool CLI.
20
 */
21
class ValidateCommand extends Command {
22
23
	/**
24
	 * The default command name
25
	 *
26
	 * @var string|null
27
	 */
28
	protected static $defaultName = 'validate';
29
30
	/**
31
	 * The InputInterface to use.
32
	 *
33
	 * @var InputInterface|null
34
	 */
35
	private $input;
36
37
	/**
38
	 * The OutputInterface to use.
39
	 *
40
	 * @var OutputInterface|null
41
	 */
42
	private $output;
43
44
	/**
45
	 * Counts of errors and warnings output.
46
	 *
47
	 * @var int[]
48
	 */
49
	private $counts;
50
51
	/**
52
	 * Base directory regex.
53
	 *
54
	 * @var string
55
	 */
56
	private $basedirRegex;
57
58
	/**
59
	 * Configures the command.
60
	 */
61
	protected function configure() {
62
		$this->setDescription( 'Validates changelog entry files' )
63
			->addOption( 'gh-action', null, InputOption::VALUE_NONE, 'Output validation issues using GitHub Action command syntax.' )
64
			->addOption( 'basedir', null, InputOption::VALUE_REQUIRED, 'Output file paths in this directory relative to it.' )
65
			->addOption( 'no-strict', null, InputOption::VALUE_NONE, 'Do not exit with a failure code if only warnings are found.' )
66
			->addArgument( 'files', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Files to check. By default, all change files in the changelog directory are checked.' )
67
			->setHelp(
68
				<<<EOF
69
The <info>validate</info> command validates change files.
70
EOF
71
			);
72
	}
73
74
	/**
75
	 * Output an error or warning.
76
	 *
77
	 * @param string   $type 'error' or 'warning'.
78
	 * @param string   $file Filename with the error/warning.
79
	 * @param int|null $line Line number of the error/warning.
80
	 * @param string   $msg Error message.
81
	 */
82
	private function msg( $type, $file, $line, $msg ) {
83
		$file = preg_replace( $this->basedirRegex, '', $file );
84
		if ( $this->input->getOption( 'gh-action' ) ) {
85
			$prefix = "::$type file=$file";
86
			if ( null !== $line ) {
87
				$prefix .= ",line=$line";
88
			}
89
			$prefix .= '::';
90
			$postfix = '';
91
		} else {
92
			$prefix = "<$type>$file";
93
			if ( null !== $line ) {
94
				$prefix .= ":$line";
95
			}
96
			$prefix .= ': ';
97
			$postfix = '</>';
98
		}
99
		$this->output->writeln( $prefix . $msg . $postfix );
100
		$this->counts[ $type ]++;
101
	}
102
103
	/**
104
	 * Validate a file.
105
	 *
106
	 * @param string $filename Filename.
107
	 */
108
	public function validateFile( $filename ) {
109
		try {
110
			$diagnostics = null; // Make phpcs happy.
111
			$data        = Utils::loadChangeFile( $filename, $diagnostics );
112
		} catch ( \RuntimeException $ex ) {
113
			$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...
114
			return false;
115
		}
116
117
		$messages = array();
118
119
		foreach ( $diagnostics['warnings'] as list( $msg, $line ) ) {
120
			$messages[] = array( 'warning', $msg, $line );
121
		}
122
123
		foreach ( $diagnostics['lines'] as $header => $line ) {
124
			if ( ! in_array( $header, array( 'Significance', 'Type', 'Comment', '' ), true ) ) {
125
				$messages[] = array( 'warning', "Unrecognized header \"$header\".", $line );
126
			}
127
		}
128
129
		if ( ! isset( $data['Significance'] ) ) {
130
			$messages[] = array( 'error', 'File does not contain a Significance header.', null );
131
		} elseif ( ! in_array( $data['Significance'], array( 'patch', 'minor', 'major' ), true ) ) {
132
			$messages[] = array( 'error', 'Significance must be "patch", "minor", or "major".', $diagnostics['lines']['Significance'] );
133
		} elseif ( 'patch' !== $data['Significance'] && '' === $data[''] ) {
134
			$messages[] = array( 'error', 'Changelog entry may only be empty when Significance is "patch".', $diagnostics['lines'][''] );
135
		}
136
137
		$types = Config::types();
138
		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...
139
			if ( ! isset( $data['Type'] ) ) {
140
				$messages[] = array( 'error', 'File does not contain a Type header.', null );
141
			} elseif ( ! isset( $types[ $data['Type'] ] ) ) {
142
				$list = array_map(
143
					function ( $v ) {
144
						return "\"$v\"";
145
					},
146
					array_keys( $types )
147
				);
148 View Code Duplication
				if ( count( $list ) > 1 ) {
149
					$list[ count( $list ) - 1 ] = 'or ' . $list[ count( $list ) - 1 ];
150
				}
151
				$messages[] = array( 'error', 'Type must be ' . implode( count( $list ) > 2 ? ', ' : ' ', $list ) . '.', $diagnostics['lines']['Type'] );
152
			}
153
		}
154
155
		usort(
156
			$messages,
157
			function ( $a, $b ) {
158
				// @codeCoverageIgnoreStart
159 View Code Duplication
				if ( $a[2] !== $b[2] ) {
160
					return $a[2] - $b[2];
161
				}
162 View Code Duplication
				if ( $a[0] !== $b[0] ) {
163
					return strcmp( $a[0], $b[0] );
164
				}
165
				return strcmp( $a[1], $b[1] );
166
				// @codeCoverageIgnoreEnd
167
			}
168
		);
169
		foreach ( $messages as list( $type, $msg, $line ) ) {
170
			$this->msg( $type, $filename, $line, $msg );
171
		}
172
	}
173
174
	/**
175
	 * Executes the command.
176
	 *
177
	 * @param InputInterface  $input InputInterface.
178
	 * @param OutputInterface $output OutputInterface.
179
	 * @return int 0 if everything went fine, or an exit code.
180
	 */
181
	protected function execute( InputInterface $input, OutputInterface $output ) {
182
		$this->input  = $input;
183
		$this->output = $output;
184
		$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...
185
			'error'   => 0,
186
			'warning' => 0,
187
		);
188
189
		if ( $input->getOption( 'basedir' ) ) {
190
			$basedir            = rtrim( $input->getOption( 'basedir' ), '/' );
191
			$basedir            = rtrim( $basedir, DIRECTORY_SEPARATOR );
192
			$this->basedirRegex = '#^' . preg_quote( $basedir, '#' ) . '[/' . preg_quote( DIRECTORY_SEPARATOR, '#' ) . ']#';
193
		} else {
194
			$this->basedirRegex = '/(?!)/';
195
		}
196
197
		$files = $input->getArgument( 'files' );
198
		if ( ! $files ) {
199
			$files = array();
200 View Code Duplication
			foreach ( new \DirectoryIterator( Config::changesDir() ) as $file ) {
201
				$name = $file->getBasename();
202
				if ( '.' !== $name[0] ) {
203
					$files[] = $file->getPathname();
204
				}
205
			}
206
			sort( $files );
207
		}
208
209
		foreach ( $files as $filename ) {
210
			$file = preg_replace( $this->basedirRegex, '', $filename );
211
			$output->writeln( "Checking $file...", OutputInterface::VERBOSITY_VERBOSE );
212
			$this->validateFile( $filename );
213
		}
214
215
		$output->writeln( sprintf( 'Found %d error(s) and %d warning(s)', $this->counts['error'], $this->counts['warning'] ), OutputInterface::VERBOSITY_VERBOSE );
216
		return $this->counts['error'] || $this->counts['warning'] && ! $input->getOption( 'no-strict' ) ? 1 : 0;
217
	}
218
}
219