2015-01-29 00:08:50 +03:00
< ? php
/**
2016-07-21 18:07:57 +03:00
* @ copyright Copyright ( c ) 2016 , ownCloud , Inc .
*
* @ author Joas Schilling < coding @ schilljs . com >
2015-03-26 13:44:34 +03:00
* @ author Morris Jobke < hey @ morrisjobke . de >
2016-01-12 17:02:16 +03:00
* @ author Robin McCorkell < robin @ mccorkell . me . uk >
2015-10-05 21:54:56 +03:00
* @ author Thomas Müller < thomas . mueller @ tmit . eu >
2015-03-26 13:44:34 +03:00
*
* @ license AGPL - 3.0
*
* This code is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License , version 3 ,
* as published by the Free Software Foundation .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License , version 3 ,
* along with this program . If not , see < http :// www . gnu . org / licenses />
*
2015-01-29 00:08:50 +03:00
*/
2015-02-26 13:37:37 +03:00
2015-01-29 00:08:50 +03:00
namespace OC\Core\Command\App ;
2015-07-07 13:08:12 +03:00
use OC\App\CodeChecker\CodeChecker ;
2017-05-09 17:49:56 +03:00
use OC\App\CodeChecker\DatabaseSchemaChecker ;
2015-07-07 16:37:56 +03:00
use OC\App\CodeChecker\EmptyCheck ;
2015-08-16 18:49:45 +03:00
use OC\App\CodeChecker\InfoChecker ;
2017-05-09 16:46:33 +03:00
use OC\App\CodeChecker\LanguageParseChecker ;
2015-08-16 18:49:45 +03:00
use OC\App\InfoParser ;
2016-09-21 01:48:05 +03:00
use Stecman\Component\Symfony\Console\BashCompletion\Completion\CompletionAwareInterface ;
use Stecman\Component\Symfony\Console\BashCompletion\CompletionContext ;
2015-01-29 00:08:50 +03:00
use Symfony\Component\Console\Command\Command ;
use Symfony\Component\Console\Input\InputArgument ;
use Symfony\Component\Console\Input\InputInterface ;
2015-06-15 13:51:22 +03:00
use Symfony\Component\Console\Input\InputOption ;
2015-01-29 00:08:50 +03:00
use Symfony\Component\Console\Output\OutputInterface ;
2016-09-21 01:48:05 +03:00
class CheckCode extends Command implements CompletionAwareInterface {
2015-08-16 18:49:45 +03:00
/** @var InfoParser */
private $infoParser ;
2015-07-11 00:17:55 +03:00
protected $checkers = [
'private' => '\OC\App\CodeChecker\PrivateCheck' ,
'deprecation' => '\OC\App\CodeChecker\DeprecationCheck' ,
'strong-comparison' => '\OC\App\CodeChecker\StrongComparisonCheck' ,
];
2015-08-16 18:49:45 +03:00
public function __construct ( InfoParser $infoParser ) {
parent :: __construct ();
$this -> infoParser = $infoParser ;
}
2015-01-29 00:08:50 +03:00
protected function configure () {
$this
-> setName ( 'app:check-code' )
-> setDescription ( 'check code to be compliant' )
-> addArgument (
'app-id' ,
InputArgument :: REQUIRED ,
2015-05-05 09:23:59 +03:00
'check the specified app'
2015-06-15 13:51:22 +03:00
)
-> addOption (
2015-07-11 00:17:55 +03:00
'checker' ,
'c' ,
InputOption :: VALUE_REQUIRED | InputOption :: VALUE_IS_ARRAY ,
2015-07-16 12:47:20 +03:00
'enable the specified checker(s)' ,
2015-07-11 00:17:55 +03:00
[ 'private' , 'deprecation' , 'strong-comparison' ]
2015-08-16 18:49:45 +03:00
)
2017-05-11 10:52:55 +03:00
-> addOption (
'--skip-checkers' ,
null ,
InputOption :: VALUE_NONE ,
'skips the the code checkers to only check info.xml, language and database schema'
)
2015-08-16 18:49:45 +03:00
-> addOption (
'--skip-validate-info' ,
null ,
InputOption :: VALUE_NONE ,
'skips the info.xml/version check'
2015-01-29 00:08:50 +03:00
);
}
protected function execute ( InputInterface $input , OutputInterface $output ) {
$appId = $input -> getArgument ( 'app-id' );
2015-07-11 00:17:55 +03:00
2015-07-07 16:37:56 +03:00
$checkList = new EmptyCheck ();
2015-07-11 00:17:55 +03:00
foreach ( $input -> getOption ( 'checker' ) as $checker ) {
if ( ! isset ( $this -> checkers [ $checker ])) {
throw new \InvalidArgumentException ( 'Invalid checker: ' . $checker );
}
$checkerClass = $this -> checkers [ $checker ];
$checkList = new $checkerClass ( $checkList );
2015-06-15 13:51:22 +03:00
}
2015-07-11 00:17:55 +03:00
2015-07-07 16:37:56 +03:00
$codeChecker = new CodeChecker ( $checkList );
2015-07-07 13:08:12 +03:00
2015-01-29 00:08:50 +03:00
$codeChecker -> listen ( 'CodeChecker' , 'analyseFileBegin' , function ( $params ) use ( $output ) {
2015-05-05 14:57:23 +03:00
if ( OutputInterface :: VERBOSITY_VERBOSE <= $output -> getVerbosity ()) {
$output -> writeln ( " <info>Analysing { $params } </info> " );
}
2015-01-29 00:08:50 +03:00
});
2015-05-05 14:57:23 +03:00
$codeChecker -> listen ( 'CodeChecker' , 'analyseFileFinished' , function ( $filename , $errors ) use ( $output ) {
$count = count ( $errors );
// show filename if the verbosity is low, but there are errors in a file
if ( $count > 0 && OutputInterface :: VERBOSITY_VERBOSE > $output -> getVerbosity ()) {
$output -> writeln ( " <info>Analysing { $filename } </info> " );
}
2015-08-16 18:49:45 +03:00
// show error count if there are errors present or the verbosity is high
2015-05-05 14:57:23 +03:00
if ( $count > 0 || OutputInterface :: VERBOSITY_VERBOSE <= $output -> getVerbosity ()) {
$output -> writeln ( " { $count } errors " );
}
usort ( $errors , function ( $a , $b ) {
2015-01-29 00:08:50 +03:00
return $a [ 'line' ] > $b [ 'line' ];
});
2015-05-05 14:57:23 +03:00
foreach ( $errors as $p ) {
2015-01-29 00:08:50 +03:00
$line = sprintf ( " %' 4d " , $p [ 'line' ]);
$output -> writeln ( " <error>line $line : { $p [ 'disallowedToken' ] } - { $p [ 'reason' ] } </error> " );
}
});
2017-05-17 00:18:02 +03:00
$errors = [];
if ( ! $input -> getOption ( 'skip-checkers' )) {
$errors = $codeChecker -> analyse ( $appId );
}
2015-08-16 18:49:45 +03:00
if ( ! $input -> getOption ( 'skip-validate-info' )) {
$infoChecker = new InfoChecker ( $this -> infoParser );
$infoChecker -> listen ( 'InfoChecker' , 'mandatoryFieldMissing' , function ( $key ) use ( $output ) {
$output -> writeln ( " <error>Mandatory field missing: $key </error> " );
});
$infoChecker -> listen ( 'InfoChecker' , 'deprecatedFieldFound' , function ( $key , $value ) use ( $output ) {
if ( $value === [] || is_null ( $value ) || $value === '' ) {
$output -> writeln ( " <info>Deprecated field available: $key </info> " );
} else {
$output -> writeln ( " <info>Deprecated field available: $key => $value </info> " );
}
});
2016-01-07 17:04:36 +03:00
$infoChecker -> listen ( 'InfoChecker' , 'missingRequirement' , function ( $minMax ) use ( $output ) {
2016-12-06 00:18:24 +03:00
$output -> writeln ( " <comment>Nextcloud $minMax version requirement missing (will be an error in Nextcloud 12 and later)</comment> " );
2016-01-07 17:04:36 +03:00
});
2015-11-09 13:10:37 +03:00
$infoChecker -> listen ( 'InfoChecker' , 'duplicateRequirement' , function ( $minMax ) use ( $output ) {
$output -> writeln ( " <error>Duplicate $minMax ownCloud version requirement found</error> " );
});
2015-08-16 18:49:45 +03:00
$infoChecker -> listen ( 'InfoChecker' , 'differentVersions' , function ( $versionFile , $infoXML ) use ( $output ) {
$output -> writeln ( " <error>Different versions provided (appinfo/version: $versionFile - appinfo/info.xml: $infoXML )</error> " );
});
$infoChecker -> listen ( 'InfoChecker' , 'sameVersions' , function ( $path ) use ( $output ) {
$output -> writeln ( " <info>Version file isn't needed anymore and can be safely removed ( $path )</info> " );
});
$infoChecker -> listen ( 'InfoChecker' , 'migrateVersion' , function ( $version ) use ( $output ) {
$output -> writeln ( " <info>Migrate the app version to appinfo/info.xml (add <version> $version </version> to appinfo/info.xml and remove appinfo/version)</info> " );
});
if ( OutputInterface :: VERBOSITY_VERBOSE <= $output -> getVerbosity ()) {
$infoChecker -> listen ( 'InfoChecker' , 'mandatoryFieldFound' , function ( $key , $value ) use ( $output ) {
$output -> writeln ( " <info>Mandatory field available: $key => $value </info> " );
});
$infoChecker -> listen ( 'InfoChecker' , 'optionalFieldFound' , function ( $key , $value ) use ( $output ) {
$output -> writeln ( " <info>Optional field available: $key => $value </info> " );
});
$infoChecker -> listen ( 'InfoChecker' , 'unusedFieldFound' , function ( $key , $value ) use ( $output ) {
$output -> writeln ( " <info>Unused field available: $key => $value </info> " );
});
}
$infoErrors = $infoChecker -> analyse ( $appId );
$errors = array_merge ( $errors , $infoErrors );
2017-05-09 16:46:33 +03:00
$languageParser = new LanguageParseChecker ();
$languageErrors = $languageParser -> analyse ( $appId );
foreach ( $languageErrors as $languageError ) {
$output -> writeln ( " <error> $languageError </error> " );
}
$errors = array_merge ( $errors , $languageErrors );
2017-05-09 17:49:56 +03:00
$databaseSchema = new DatabaseSchemaChecker ();
$schemaErrors = $databaseSchema -> analyse ( $appId );
foreach ( $schemaErrors [ 'errors' ] as $schemaError ) {
$output -> writeln ( " <error> $schemaError </error> " );
}
foreach ( $schemaErrors [ 'warnings' ] as $schemaWarning ) {
$output -> writeln ( " <comment> $schemaWarning </comment> " );
}
$errors = array_merge ( $errors , $schemaErrors [ 'errors' ]);
2015-08-16 18:49:45 +03:00
}
2016-04-21 18:47:26 +03:00
$this -> analyseUpdateFile ( $appId , $output );
2015-01-29 00:08:50 +03:00
if ( empty ( $errors )) {
$output -> writeln ( '<info>App is compliant - awesome job!</info>' );
2015-07-11 00:17:55 +03:00
return 0 ;
2015-01-29 00:08:50 +03:00
} else {
$output -> writeln ( '<error>App is not compliant</error>' );
2015-07-07 12:51:06 +03:00
return 101 ;
2015-01-29 00:08:50 +03:00
}
}
2016-04-21 18:47:26 +03:00
/**
* @ param string $appId
* @ param $output
*/
private function analyseUpdateFile ( $appId , OutputInterface $output ) {
$appPath = \OC_App :: getAppPath ( $appId );
if ( $appPath === false ) {
throw new \RuntimeException ( " No app with given id < $appId > known. " );
}
$updatePhp = $appPath . '/appinfo/update.php' ;
if ( file_exists ( $updatePhp )) {
$output -> writeln ( " <info>Deprecated file found: $updatePhp - please use repair steps</info> " );
}
}
2016-09-21 01:48:05 +03:00
/**
* @ param string $optionName
* @ param CompletionContext $context
* @ return string []
*/
public function completeOptionValues ( $optionName , CompletionContext $context ) {
if ( $optionName === 'checker' ) {
return [ 'private' , 'deprecation' , 'strong-comparison' ];
}
return [];
}
/**
* @ param string $argumentName
* @ param CompletionContext $context
* @ return string []
*/
public function completeArgumentValues ( $argumentName , CompletionContext $context ) {
if ( $argumentName === 'app-id' ) {
return \OC_App :: getAllApps ();
}
return [];
}
2015-01-29 00:08:50 +03:00
}