Passed
Push — master ( 344eac...9a3cc0 )
by Christoph
12:50 queued 28s
created

CheckCode::analyseUpdateFile()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 6
nc 3
nop 2
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Christoph Wurst <[email protected]>
6
 * @author Joas Schilling <[email protected]>
7
 * @author Morris Jobke <[email protected]>
8
 * @author Robin McCorkell <[email protected]>
9
 * @author Roeland Jago Douma <[email protected]>
10
 * @author Thomas Müller <[email protected]>
11
 *
12
 * @license AGPL-3.0
13
 *
14
 * This code is free software: you can redistribute it and/or modify
15
 * it under the terms of the GNU Affero General Public License, version 3,
16
 * as published by the Free Software Foundation.
17
 *
18
 * This program is distributed in the hope that it will be useful,
19
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
 * GNU Affero General Public License for more details.
22
 *
23
 * You should have received a copy of the GNU Affero General Public License, version 3,
24
 * along with this program. If not, see <http://www.gnu.org/licenses/>
25
 *
26
 */
27
28
namespace OC\Core\Command\App;
29
30
use OC\App\CodeChecker\CodeChecker;
31
use OC\App\CodeChecker\DatabaseSchemaChecker;
32
use OC\App\CodeChecker\DeprecationCheck;
33
use OC\App\CodeChecker\EmptyCheck;
34
use OC\App\CodeChecker\InfoChecker;
35
use OC\App\CodeChecker\LanguageParseChecker;
36
use OC\App\CodeChecker\PrivateCheck;
37
use OC\App\CodeChecker\StrongComparisonCheck;
38
use Stecman\Component\Symfony\Console\BashCompletion\Completion\CompletionAwareInterface;
39
use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext;
40
use Symfony\Component\Console\Command\Command;
41
use Symfony\Component\Console\Input\InputArgument;
42
use Symfony\Component\Console\Input\InputInterface;
43
use Symfony\Component\Console\Input\InputOption;
44
use Symfony\Component\Console\Output\OutputInterface;
45
46
class CheckCode extends Command implements CompletionAwareInterface {
47
	protected $checkers = [
48
		'private' => PrivateCheck::class,
49
		'deprecation' => DeprecationCheck::class,
50
		'strong-comparison' => StrongComparisonCheck::class,
51
	];
52
53
	protected function configure() {
54
		$this
55
			->setName('app:check-code')
56
			->setDescription('check code to be compliant')
57
			->addArgument(
58
				'app-id',
59
				InputArgument::REQUIRED,
60
				'check the specified app'
61
			)
62
			->addOption(
63
				'checker',
64
				'c',
65
				InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
66
				'enable the specified checker(s)',
67
				[ 'private', 'deprecation', 'strong-comparison' ]
68
			)
69
			->addOption(
70
				'--skip-checkers',
71
				null,
72
				InputOption::VALUE_NONE,
73
				'skips the the code checkers to only check info.xml, language and database schema'
74
			)
75
			->addOption(
76
				'--skip-validate-info',
77
				null,
78
				InputOption::VALUE_NONE,
79
				'skips the info.xml/version check'
80
			);
81
	}
82
83
	protected function execute(InputInterface $input, OutputInterface $output): int {
84
		$appId = $input->getArgument('app-id');
85
86
		$checkList = new EmptyCheck();
87
		foreach ($input->getOption('checker') as $checker) {
88
			if (!isset($this->checkers[$checker])) {
89
				throw new \InvalidArgumentException('Invalid checker: '.$checker);
90
			}
91
			$checkerClass = $this->checkers[$checker];
92
			$checkList = new $checkerClass($checkList);
93
		}
94
95
		$codeChecker = new CodeChecker($checkList, !$input->getOption('skip-validate-info'));
96
97
		$codeChecker->listen('CodeChecker', 'analyseFileBegin', function ($params) use ($output) {
98
			if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
99
				$output->writeln("<info>Analysing {$params}</info>");
100
			}
101
		});
102
		$codeChecker->listen('CodeChecker', 'analyseFileFinished', function ($filename, $errors) use ($output) {
103
			$count = count($errors);
104
105
			// show filename if the verbosity is low, but there are errors in a file
106
			if ($count > 0 && OutputInterface::VERBOSITY_VERBOSE > $output->getVerbosity()) {
107
				$output->writeln("<info>Analysing {$filename}</info>");
108
			}
109
110
			// show error count if there are errors present or the verbosity is high
111
			if ($count > 0 || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
112
				$output->writeln(" {$count} errors");
113
			}
114
			usort($errors, function ($a, $b) {
115
				return $a['line'] > $b['line'];
116
			});
117
118
			foreach ($errors as $p) {
119
				$line = sprintf("%' 4d", $p['line']);
120
				$output->writeln("    <error>line $line: {$p['disallowedToken']} - {$p['reason']}</error>");
121
			}
122
		});
123
		$errors = [];
124
		if (!$input->getOption('skip-checkers')) {
125
			$errors = $codeChecker->analyse($appId);
126
		}
127
128
		if (!$input->getOption('skip-validate-info')) {
129
			$infoChecker = new InfoChecker();
130
			$infoChecker->listen('InfoChecker', 'parseError', function ($error) use ($output) {
131
				$output->writeln("<error>Invalid appinfo.xml file found: $error</error>");
132
			});
133
134
			$infoErrors = $infoChecker->analyse($appId);
135
136
			$errors = array_merge($errors, $infoErrors);
137
138
			$languageParser = new LanguageParseChecker();
139
			$languageErrors = $languageParser->analyse($appId);
140
141
			foreach ($languageErrors as $languageError) {
142
				$output->writeln("<error>$languageError</error>");
143
			}
144
145
			$errors = array_merge($errors, $languageErrors);
146
147
			$databaseSchema = new DatabaseSchemaChecker();
148
			$schemaErrors = $databaseSchema->analyse($appId);
149
150
			foreach ($schemaErrors['errors'] as $schemaError) {
151
				$output->writeln("<error>$schemaError</error>");
152
			}
153
			foreach ($schemaErrors['warnings'] as $schemaWarning) {
154
				$output->writeln("<comment>$schemaWarning</comment>");
155
			}
156
157
			$errors = array_merge($errors, $schemaErrors['errors']);
158
		}
159
160
		if (empty($errors)) {
161
			$output->writeln('<info>App is compliant - awesome job!</info>');
162
			return 0;
163
		} else {
164
			$output->writeln('<error>App is not compliant</error>');
165
			return 101;
166
		}
167
	}
168
169
	/**
170
	 * @param string $optionName
171
	 * @param CompletionContext $context
172
	 * @return string[]
173
	 */
174
	public function completeOptionValues($optionName, CompletionContext $context) {
175
		if ($optionName === 'checker') {
176
			return ['private', 'deprecation', 'strong-comparison'];
177
		}
178
		return [];
179
	}
180
181
	/**
182
	 * @param string $argumentName
183
	 * @param CompletionContext $context
184
	 * @return string[]
185
	 */
186
	public function completeArgumentValues($argumentName, CompletionContext $context) {
187
		if ($argumentName === 'app-id') {
188
			return \OC_App::getAllApps();
189
		}
190
		return [];
191
	}
192
}
193