├── tests ├── fail │ ├── sql │ │ └── invalid.sql │ ├── php │ │ └── invalid.php │ ├── mixed │ │ ├── invalid.php │ │ ├── invalid.yml │ │ └── invalid.json │ ├── yml │ │ └── invalid.yml │ └── json │ │ └── invalid.json └── succeed │ ├── valid.php │ ├── sub │ └── subsub │ │ └── vendor │ │ └── invalid.php │ ├── .idea │ └── fileTemplates │ │ └── internal │ │ └── PHP Class.php │ ├── package.yml │ ├── composer.json │ ├── install.sql │ └── default.config.yml ├── .gitignore ├── bin ├── lint-file.sh └── rexlint ├── composer.json ├── .travis.yml ├── README.md ├── .php_cs.dist └── lib └── Command └── LintCommand.php /tests/fail/sql/invalid.sql: -------------------------------------------------------------------------------- 1 | abcd 2 | -------------------------------------------------------------------------------- /tests/fail/php/invalid.php: -------------------------------------------------------------------------------- 1 | add($lintCmd); 22 | $application->setDefaultCommand($lintCmd->getName(), true); 23 | 24 | $application->run(); -------------------------------------------------------------------------------- /tests/succeed/install.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%cronjob` ( 2 | `id` int(10) unsigned NOT NULL auto_increment, 3 | `name` varchar(255) default NULL, 4 | `description` varchar(255) default NULL, 5 | `type` varchar(255) default NULL, 6 | `parameters` text default NULL, 7 | `interval` text NOT NULL, 8 | `nexttime` datetime default NULL, 9 | `environment` varchar(255) NOT NULL, 10 | `execution_moment` tinyint(1) NOT NULL, 11 | `execution_start` datetime NOT NULL, 12 | `status` tinyint(1) NOT NULL, 13 | `createdate` datetime NOT NULL, 14 | `createuser` varchar(255) NOT NULL, 15 | `updatedate` datetime NOT NULL, 16 | `updateuser` varchar(255) NOT NULL, 17 | PRIMARY KEY (`id`) 18 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ; 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - '7.1' 5 | - '8.0' 6 | 7 | before_install: 8 | - phpenv config-rm xdebug.ini || echo "xdebug not available" 9 | 10 | script: 11 | # remove ignore-platform-req after all dependencies are php8 compatible 12 | - if [[ ${TRAVIS_PHP_VERSION:0:3} != "8.0" ]]; then composer install; fi; 13 | - if [[ ${TRAVIS_PHP_VERSION:0:3} == "8.0" ]]; then composer install --ignore-platform-req=php; fi; 14 | # negative tests 15 | - php bin/rexlint tests/fail/yml/ > /dev/null 2>&1 ; lintstatus=$?; if [ $lintstatus -ne 1 ]; then echo "expected fail, got $lintstatus" && exit 50; fi 16 | - php bin/rexlint tests/fail/php/ > /dev/null 2>&1 ; lintstatus=$?; if [ $lintstatus -ne 2 ]; then echo "expected fail, got $lintstatus" && exit 50; fi 17 | - php bin/rexlint tests/fail/json/ > /dev/null 2>&1 ; lintstatus=$?; if [ $lintstatus -ne 4 ]; then echo "expected fail, got $lintstatus" && exit 50; fi 18 | - php bin/rexlint tests/fail/sql/ > /dev/null 2>&1 ; lintstatus=$?; if [ $lintstatus -ne 16 ]; then echo "expected fail, got $lintstatus" && exit 50; fi 19 | - php bin/rexlint tests/fail/mixed/ > /dev/null 2>&1 ; lintstatus=$?; if [ $lintstatus -ne 7 ]; then echo "expected fail, got $lintstatus" && exit 50; fi 20 | 21 | # positive tests 22 | - php bin/rexlint tests/succeed/ 23 | - cd / && php $TRAVIS_BUILD_DIR/bin/rexlint $TRAVIS_BUILD_DIR/tests/succeed/ 24 | -------------------------------------------------------------------------------- /tests/succeed/default.config.yml: -------------------------------------------------------------------------------- 1 | setup: true 2 | debug: 3 | enabled: false 4 | throw_always_exception: false # `true` for all error levels, `[E_WARNING, E_NOTICE]` for subset 5 | instname: null 6 | server: https://www.redaxo.org/ 7 | servername: REDAXO 8 | error_email: null 9 | fileperm: '0664' 10 | dirperm: '0775' 11 | session_duration: 7200 12 | session_keep_alive: 21600 13 | # using separate cookie domains for frontend and backend is more secure, 14 | # but be warned that some features like detecting a backend user in the frontend 15 | # will no longer work. 16 | session: 17 | backend: 18 | cookie: 19 | lifetime: null 20 | path: null 21 | domain: null 22 | secure: null 23 | httponly: true 24 | samesite: 'Strict' 25 | frontend: 26 | cookie: 27 | lifetime: null 28 | path: null 29 | domain: null 30 | secure: null 31 | httponly: true 32 | samesite: 'Strict' 33 | password_policy: 34 | length: {min: 8, max: 4096} 35 | lowercase: {min: 0} 36 | uppercase: {min: 0} 37 | digit: {min: 0} 38 | lang: de_de 39 | lang_fallback: [en_gb, de_de] 40 | use_https: false 41 | use_hsts: false 42 | use_gzip: true 43 | use_etag: true 44 | use_last_modified: false 45 | start_page: structure 46 | timezone: Europe/Berlin 47 | socket_proxy: null 48 | setup_addons: 49 | - backup 50 | - be_style 51 | system_addons: 52 | - backup 53 | - mediapool 54 | - structure 55 | - metainfo 56 | - be_style 57 | - media_manager 58 | - users 59 | - install 60 | - project 61 | table_prefix: rex_ 62 | temp_prefix: tmp_ 63 | db: 64 | 1: 65 | host: localhost 66 | login: root 67 | password: '' 68 | name: redaxo_5_0 69 | persistent: false 70 | 2: 71 | host: '' 72 | login: '' 73 | password: '' 74 | name: '' 75 | persistent: false 76 | use_accesskeys: true 77 | accesskeys: 78 | save: s 79 | apply: x 80 | delete: d 81 | add: a 82 | add_2: y 83 | editor: null 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecated 2 | 3 | use https://github.com/github/super-linter or https://github.com/FriendsOfREDAXO/rexstan instead! 4 | 5 | 6 | # linter 7 | Linter commandline für REDAXO. 8 | 9 | Mit diesem Tool ist es möglich beliebige Dateien bzgl. gängigen Fehlern zu überprüfen. 10 | Aktuell werden folgende Dateien überprüft: 11 | 12 | - PHP Dateien 13 | - YAML Dateien 14 | - JSON Dateien 15 | - SQL Dateien 16 | - CSS Dateien 17 | 18 | ## Setup in Travis CI 19 | 20 | 21 | ### Datei `.travis.yml` im gewünschten github repository erzeugen 22 | 23 | .. wenn man noch keine `.travis.yml` hat.. 24 | 25 | ```yml 26 | language: php 27 | 28 | php: 29 | - '7.1' # REDAXO5.8+ min-php version 30 | 31 | cache: 32 | directories: 33 | - $HOME/.composer/cache 34 | 35 | before_install: 36 | - phpenv config-rm xdebug.ini || echo "xdebug not available" 37 | 38 | script: 39 | - composer require --dev friendsofredaxo/linter 40 | - vendor/bin/rexlint 41 | ``` 42 | 43 | ### Auf https://travis-ci.org via github-login anmelden und das Repository für TravisCI aktivieren. 44 | 45 | Beispiel für FriendsOfREDAXO/minibar: 46 | 47 | Account-Settings öffnen: 48 | ![image](https://user-images.githubusercontent.com/120441/55288765-b8268500-53bc-11e9-9139-6e904c4fa3c8.png) 49 | 50 | Repository aktivieren: 51 | ![image](https://user-images.githubusercontent.com/120441/55288776-dc826180-53bc-11e9-9625-27a87c4d1544.png) 52 | 53 | -> Wenn man jetzt ein neues Pull Request öffnet, laufen die Checks und man bekommt entweder ein OK oder ein KO: 54 | 55 | ![image](https://user-images.githubusercontent.com/120441/55288790-050a5b80-53bd-11e9-90aa-455464003fb8.png) 56 | 57 | 58 | ## Setup in GithubActions 59 | 60 | ```yml 61 | # ... snip 62 | 63 | jobs: 64 | 65 | rex-lint: 66 | name: REX Linting 67 | runs-on: ubuntu-latest 68 | steps: 69 | - uses: actions/checkout@v2 70 | - name: Setup PHP 71 | uses: shivammathur/setup-php@v2 72 | with: 73 | php-version: 7.1 # adjust accordingly 74 | extensions: intl 75 | coverage: none # disable xdebug, pcov 76 | - name: Install Dependencies 77 | run: composer install --prefer-dist 78 | - run: | 79 | composer require --dev friendsofredaxo/linter 80 | vendor/bin/rexlint 81 | ``` 82 | -------------------------------------------------------------------------------- /.php_cs.dist: -------------------------------------------------------------------------------- 1 | exclude('tests') 6 | ->in($src) 7 | ; 8 | return PhpCsFixer\Config::create() 9 | ->setUsingCache(true) 10 | ->setRiskyAllowed(true) 11 | ->setRules([ 12 | '@Symfony' => true, 13 | '@Symfony:risky' => true, 14 | '@PHP71Migration' => true, 15 | '@PHP71Migration:risky' => true, 16 | '@PHPUnit60Migration:risky' => true, 17 | 'array_indentation' => true, 18 | 'array_syntax' => ['syntax' => 'short'], 19 | 'blank_line_before_statement' => false, 20 | 'braces' => ['allow_single_line_closure' => false], 21 | 'comment_to_phpdoc' => true, 22 | 'concat_space' => false, 23 | 'declare_strict_types' => false, 24 | 'function_to_constant' => ['functions' => ['get_class', 'get_called_class', 'php_sapi_name', 'phpversion', 'pi']], 25 | 'heredoc_to_nowdoc' => true, 26 | 'list_syntax' => ['syntax' => 'short'], 27 | 'logical_operators' => true, 28 | 'native_constant_invocation' => false, 29 | 'no_blank_lines_after_phpdoc' => false, 30 | 'no_null_property_initialization' => true, 31 | 'no_php4_constructor' => true, 32 | 'no_superfluous_elseif' => true, 33 | 'no_unreachable_default_argument_value' => true, 34 | 'no_useless_else' => true, 35 | 'no_useless_return' => true, 36 | 'ordered_imports' => true, 37 | 'php_unit_internal_class' => true, 38 | 'php_unit_method_casing' => true, 39 | 'php_unit_set_up_tear_down_visibility' => true, 40 | 'php_unit_test_case_static_method_calls' => ['call_type' => 'this'], 41 | 'phpdoc_annotation_without_dot' => false, 42 | 'phpdoc_no_package' => false, 43 | 'phpdoc_order' => true, 44 | 'phpdoc_trim_consecutive_blank_line_separation' => true, 45 | 'phpdoc_types_order' => false, 46 | 'phpdoc_var_annotation_correct_order' => true, 47 | 'psr4' => false, 48 | 'semicolon_after_instruction' => false, 49 | 'space_after_semicolon' => true, 50 | 'static_lambda' => true, 51 | 'string_line_ending' => true, 52 | 'void_return' => false, 53 | 'yoda_style' => false, 54 | ]) 55 | ->setFinder($finder) 56 | ; 57 | -------------------------------------------------------------------------------- /lib/Command/LintCommand.php: -------------------------------------------------------------------------------- 1 | setName('rexlint') 21 | ->addArgument('dir', InputArgument::OPTIONAL, 'The directory', '.') 22 | ; 23 | } 24 | 25 | protected function execute(InputInterface $input, OutputInterface $output) 26 | { 27 | $rootPath = dirname(__FILE__, 3); 28 | 29 | // If package vendor folder isn't available use project vendor folder 30 | // In Dev Env the vendor-binaries folder is located in the project root 31 | // If this package is loaded via composer the vendor-binaries folder is located in project root and not in linter root 32 | if (!is_dir($rootPath.'/vendor')) { 33 | $rootPath = dirname($rootPath, 3); 34 | } 35 | 36 | // some lecture on "find -exec vs. find | xargs" 37 | // https://www.everythingcli.org/find-exec-vs-find-xargs/ 38 | 39 | $style = new SymfonyStyle($input, $output); 40 | $dir = rtrim($input->getArgument('dir'), DIRECTORY_SEPARATOR); 41 | 42 | // find recursive all vendor and .idea directories and exclude them from parallel-lint 43 | $iterator = new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS); 44 | $iterator = new RecursiveCallbackFilterIterator($iterator, function (SplFileInfo $current, string $path, RecursiveDirectoryIterator $iterator) { 45 | if (!$current->isDir()) { 46 | return false; 47 | } 48 | 49 | if (in_array($current->getFilename(), ['vendor', '.idea'], true)) { 50 | return true; 51 | } 52 | 53 | if (!$iterator->hasChildren()) { 54 | return false; 55 | } 56 | 57 | return false === strpos($path, '/vendor/') && false === strpos($path, '/.idea/'); 58 | }); 59 | $iterator = new RecursiveIteratorIterator($iterator, RecursiveIteratorIterator::SELF_FIRST); 60 | 61 | $proc = [$rootPath.'/vendor/bin/parallel-lint']; 62 | foreach($iterator as $path => $current){ 63 | if (!in_array($current->getFilename(), ['vendor', '.idea'], true)) { 64 | continue; 65 | } 66 | 67 | $proc[] = '--exclude'; 68 | $proc[] = $path; 69 | } 70 | $proc[] = $dir; 71 | 72 | $processes[] = [ 73 | self::ERR_PHP, 74 | 'PHP checks', 75 | $this->asyncProc($proc), 76 | ]; 77 | $processes[] = [ 78 | self::ERR_JSON, 79 | 'JSON checks', 80 | $this->asyncProc(['find', $dir, '-type', 'f', '-name', '*.json', '!', '-path', '*/vendor/*', '-exec', $rootPath.'/vendor/bin/jsonlint', '{}', '+']), 81 | ]; 82 | $processes[] = [ 83 | self::ERR_SQL, 84 | 'SQL checks', 85 | $this->asyncProc(['find', $dir, '-name', '*.sql', '!', '-path', '*/vendor/*', '-exec', __DIR__.'/../../bin/lint-file.sh', '{}', '+']), 86 | ]; 87 | 88 | // $this->syncProc(['npm', 'install', 'csslint']); 89 | 90 | // we only want to find errors, no style checks 91 | $csRules = 'order-alphabetical,important,ids,font-sizes,floats'; 92 | //$processes[] = [ 93 | // self::ERR_CSS, 94 | // 'CSS checks', 95 | // $this->asyncProc(['find', $dir, '-name', '*.css', '!', '-path', '*/vendor/*', '-exec', 'node_modules/.bin/csslint', '--ignore='.$csRules, '{}', '+']), 96 | //]; 97 | 98 | $exit = 0; 99 | foreach ($processes as $struct) { 100 | [ 101 | $exitCode, 102 | $label, 103 | $process 104 | ] = $struct; 105 | 106 | $process->wait(); 107 | 108 | $succeed = $process->isSuccessful(); 109 | if ($exitCode == self::ERR_PHP) { 110 | if (strpos($process->getOutput(), 'No file found to check') !== false) { 111 | $succeed = true; 112 | } 113 | } 114 | 115 | if (!$succeed) { 116 | $style->section($label); 117 | echo $process->getOutput(); 118 | echo $process->getErrorOutput(); 119 | $style->error("$label failed\n"); 120 | $exit = $exit | $exitCode; 121 | } else { 122 | if ($output->isVerbose()) { 123 | echo $process->getCommandLine()."\n"; 124 | echo $process->getOutput(); 125 | echo $process->getErrorOutput(); 126 | } 127 | $style->success("$label successfull\n"); 128 | } 129 | } 130 | 131 | // yaml-lint only supports one file at a time 132 | $label = 'YAML checks'; 133 | $succeed = $this->syncFindExec(['find', $dir, '-type', 'f', '-name', '*.yml', '!', '-path', '*/vendor/*'], [$rootPath.'/vendor/bin/yaml-lint']); 134 | 135 | if (!$succeed) { 136 | $style->section('YAML checks'); 137 | $style->error("$label failed\n"); 138 | $exit = $exit | self::ERR_YAML; 139 | } else { 140 | $style->success("$label successfull\n"); 141 | } 142 | 143 | return $exit; 144 | } 145 | 146 | private function syncFindExec(array $findCmd, array $execCmd) 147 | { 148 | $processes = []; 149 | 150 | $process = new Process($findCmd); 151 | $process->mustRun(static function ($type, $buffer) use (&$processes, $execCmd) { 152 | if (Process::ERR === $type) { 153 | throw new Exception($buffer); 154 | } 155 | foreach (explode("\n", trim($buffer)) as $ymlFile) { 156 | $cmd = $execCmd; 157 | $cmd[] = $ymlFile; 158 | 159 | $process = new Process($cmd); 160 | $process->start(); 161 | $processes[] = $process; 162 | } 163 | }); 164 | 165 | foreach ($processes as $subProcess) { 166 | $subProcess->wait(); 167 | 168 | if (!$subProcess->isSuccessful()) { 169 | echo $subProcess->getCommandLine()."\n"; 170 | echo $subProcess->getOutput(); 171 | echo $subProcess->getErrorOutput(); 172 | 173 | return false; 174 | } 175 | } 176 | return true; 177 | } 178 | 179 | private function asyncProc(array $cmd): Process 180 | { 181 | $process = new Process($cmd); 182 | $process->start(); 183 | return $process; 184 | } 185 | 186 | private function syncProc(array $cmd) 187 | { 188 | $syncP = new Process($cmd); 189 | $syncP->mustRun(); 190 | echo $syncP->getOutput(); 191 | } 192 | } 193 | --------------------------------------------------------------------------------