Completed
Push — master ( 45e7bb...57e088 )
by Morris
13:03
created

CheckCode::configure()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 29
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 24
nc 1
nop 0
dl 0
loc 29
rs 8.8571
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Joas Schilling <[email protected]>
6
 * @author Morris Jobke <[email protected]>
7
 * @author Robin McCorkell <[email protected]>
8
 * @author Thomas Müller <[email protected]>
9
 *
10
 * @license AGPL-3.0
11
 *
12
 * This code is free software: you can redistribute it and/or modify
13
 * it under the terms of the GNU Affero General Public License, version 3,
14
 * as published by the Free Software Foundation.
15
 *
16
 * This program is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
 * GNU Affero General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU Affero General Public License, version 3,
22
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
23
 *
24
 */
25
26
namespace OC\Core\Command\App;
27
28
use OC\App\CodeChecker\CodeChecker;
29
use OC\App\CodeChecker\DatabaseSchemaChecker;
30
use OC\App\CodeChecker\EmptyCheck;
31
use OC\App\CodeChecker\InfoChecker;
32
use OC\App\CodeChecker\LanguageParseChecker;
33
use OC\App\InfoParser;
34
use Stecman\Component\Symfony\Console\BashCompletion\Completion\CompletionAwareInterface;
35
use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext;
36
use Symfony\Component\Console\Command\Command;
37
use Symfony\Component\Console\Input\InputArgument;
38
use Symfony\Component\Console\Input\InputInterface;
39
use Symfony\Component\Console\Input\InputOption;
40
use Symfony\Component\Console\Output\OutputInterface;
41
use OC\App\CodeChecker\StrongComparisonCheck;
42
use OC\App\CodeChecker\DeprecationCheck;
43
use OC\App\CodeChecker\PrivateCheck;
44
45
class CheckCode extends Command implements CompletionAwareInterface  {
46
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) {
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
		$this->analyseUpdateFile($appId, $output);
161
162
		if (empty($errors)) {
163
			$output->writeln('<info>App is compliant - awesome job!</info>');
164
			return 0;
165
		} else {
166
			$output->writeln('<error>App is not compliant</error>');
167
			return 101;
168
		}
169
	}
170
171
	/**
172
	 * @param string $appId
173
	 * @param $output
174
	 */
175
	private function analyseUpdateFile($appId, OutputInterface $output) {
176
		$appPath = \OC_App::getAppPath($appId);
177
		if ($appPath === false) {
178
			throw new \RuntimeException("No app with given id <$appId> known.");
179
		}
180
181
		$updatePhp = $appPath . '/appinfo/update.php';
182
		if (file_exists($updatePhp)) {
183
			$output->writeln("<info>Deprecated file found: $updatePhp - please use repair steps</info>");
184
		}
185
	}
186
187
	/**
188
	 * @param string $optionName
189
	 * @param CompletionContext $context
190
	 * @return string[]
191
	 */
192
	public function completeOptionValues($optionName, CompletionContext $context) {
193
		if ($optionName === 'checker') {
194
			return ['private', 'deprecation', 'strong-comparison'];
195
		}
196
		return [];
197
	}
198
199
	/**
200
	 * @param string $argumentName
201
	 * @param CompletionContext $context
202
	 * @return string[]
203
	 */
204
	public function completeArgumentValues($argumentName, CompletionContext $context) {
205
		if ($argumentName === 'app-id') {
206
			return \OC_App::getAllApps();
207
		}
208
		return [];
209
	}
210
}
211